VPCでアベイラビリティゾーン越しにプライベートIPを共有する(Source/Descチェック外し)

久々のblogです。片山です。
先日@さんと、ENI出たけどAZは越せませんねー、という話をしていたときに思いついたアイデアを試してみました。

ENIについて

先日発表されたAWSの仮想ネットワークカード機能、Elastic Network Intarface(通称ENI)ですが、プライベートIPはもちろんのこと、セキュリティグループやグローバルIP、さらにMACアドレスまで別のサーバにつけたり外したりできる、かなりイケてる機能です。
EC2インスタンスにENIをひっつけることで、元々持っているeth0と合わせて複数のネットワークカードを持つことが出来るようになるので、マルチホームを実現できます。またあるEC2インスタンスから別のEC2インスタンスへ付け替えできるので、たとえばDBサーバにひっつけたENIに対してWebサーバからアクセスさせておけば、DBが死んだときにも別で立てたDBにENIを付け替えることで、Webサーバの変更を行うことなくそのままアクセスできるように出来ます。いわゆるVirtualIP的なことがAWS上で構築できる、ということです。


さてこの機能ですが、1つだけ欠点があります。それは「AWSアベイラビリティゾーン(AZ)を越えて移動できない事」です。
つまり、ENIを使ってDBのフェイルオーバー機能を実装しても、AZを超えたフェイルオーバーは行えないということです。
(現在の仕様で、VPC内に作るサブネットがAZをまたげないため、これに準じてENIもAZまたぎが出来ないのだと思います)


ということで何かいい方法はないかということで、次のようなことを行ってみました。

Source/Desc Check

AWS Management Console から、VPC内にあるEC2インスタンスや、Network Interfacesの一覧のネットワークカードを右クリックすると、「Change Source/Dest Check」という項目があるのがわかるかと思います。



この設定はデフォルトでONになっていますが、このメニューからOFFにすることが出来ます。これは何かというと、「自分宛の通信以外を受け入れるかどうかをチェックするかどうか」を決める項目です。


通常、EC2のインスタンスには自分宛の通信しかこないので、このチェックがONでもOFFでも何も変わりません。元々この機能は(多分)VPCの中で使うNATインスタンスために用意されています。
NATインスタンスとは、インターネットに面していないサブネットから、インターネットに向かって通信する際に代理で通信をするためのサーバです。つまりNATインスタンスでは、インターネットに向けた通信を受け入れて、自分が代理で通信をする必要があるため、このSource/DestチェックがOFFである必要があります。


今回の仕組みでは、この機能の特性を使って、VPC内にシステムを構築します。

構成図

今回作る仕組みは次のような絵になります。なお、前段で話しておいてなんですが、ENIは今回登場しません。


WebサーバとDBサーバのサブネットを、各AZに作成します。作成したサブネットに、1つづつインスタンスを配置します。Webサーバの入っているサブネット2つには、1つルーティングテーブルを作成して割り当てます。(今回ELBはおまけです)

仕掛けの説明

VPCでルーティングを作る際に、指定したIPアドレスレンジ宛ての通信を特定のインスタンス(もしくはネットワークインターフェース)に渡すという設定をすることができます。この設定をしても通常、EC2インスタンスは自分宛以外の通信は受け入れないのですが、Source/Destチェックを外したEC2インスタンスは、どこ宛の通信でも受け入れてくれます。つまり適当なIPアドレスを決めて、そのIPアドレスの場合は指定のEC2インスタンスに渡すようルーティングしてやれば、EC2インスタンスに付与したIPアドレスとは関係なく、EC2インスタンスにパケットを到達させることができます。


たとえば今回の構成では、「10.0.0.2」を仮想的なIPとみなし、WebサーバからのDBにアクセスする際は、10.0.0.2へアクセスするように設定します。この絵のVPCは192.168.0.0/16のネットワークなので、何もしなければ、デフォルトゲートウェイから出て行ってあて先不明でロストしますが、Webサーバのサブネットに付けたルーティングテーブルにルーティングを書いてあげれば、DBサーバに到達するようになります。
ルーティングはつぎのような感じです。


10.0.0.2の通信が来たときに、指定のDBインスタンスに行くように設定します。もしDBサーバに障害が発生した場合は、このDBインスタンスのあて先を変えることで、DBを切り替えることが出来ます。切り替えはAWS SDKコマンドラインから行えるため、スタンバイしているDBに監視機能を入れておいて、failを検知したら切り替えコマンドを発行します。ルーティング切り替えは瞬時に終わるので、DNS切り替えよりもフェイルオーバーに要する時間は短縮できます。

iptables

ここまで構築するとうまく行きそうですが、WebサーバからDBにアクセスしても通信が帰ってきません。自分以外のあて先でないので、どこかで通信が破棄されているようです。
ということで、各DBサーバのiptablesに次のような設定をしました。

iptables -t nat -A PREROUTING -i eth0 ! -d DBサーバのプライベートIP -p tcp --dport 3306 -j DNAT --to-destination DBサーバのプライベートIP:3306

設定としては、「自分宛以外のIPアドレスが来たら、自分のIPアドレスの3306に振り変える」というものです。これであれば、DBサーバのプライベートIPで来た場合はそのまま、それ以外はDBサーバ内で自分のプライベートIPに変換することでアクセスを行えます。
(今回はこの方法でうまく動きましたが、もっといい方法はあるかもしれません。。)


※追記
@さんに、ループバックインターフェースにIPつけたほうがかっこいいよと教えてもらいました。
上記のiptablesの設定の代わりに、

ifconfig lo:0 10.0.0.2/32

という感じでループバックにIPを振っても、同じ事が実現できます。こちらの方がネットワークカードが明確になるのでよいですね。

テスト

192.168.30.10のサーバには「DB30」という名前のDB、192.168.30.10のサーバには「DB31」という名前のDBを作っておき、Webサーバからつないでみます。


mysqlで10.0.0.2に対して接続。



つながりました。DBの一覧を取得。



「DB30」が見えます。次にルーティングテーブルを変更します。



変更後、再度DB一覧を取得。



接続エラーになります。ルーティングが変わっているので、これは想定内の挙動。再度10.0.0.2につないて、DB一覧を取得。



同じ接続情報で接続し、「DB31」が見えました。うまく切り替わっています。

最後に

多少トリッキーですが、Webサーバで使うIPアドレスの変更なしで、DBの切り替えを行うことが出来ました。今回はDBのレプリケーションは試していませんが、DBには各サーバのプライベートIPでアクセスできるため、特に問題なくレプリケーションできると思います。今回ENIは使わなかったのですが、ENIと組み合わせてあげれば、プロキシサーバの冗長化や、ファイルサーバの冗長化などが行えるかもしれません。何かいい活用法や、内容のご指摘があれば、お教えいただければと思いますm(_ _)m