Amazon ELBでSorryサーバへのフェイルオーバーを実現する
AWSにおいて、ELBによるSorryサーバへのフェイルオーバーは、長らく待ち望まれている機能です。先日、Route53にDNSレベルでのフェイルオーバー機能が実装(下記)されました。
- Amazon Route 53のDNSフェイルオーバー機能を利用したリージョンを跨いだバックアップサイトの構築(EC2 to S3編)
- Amazon Route 53のDNSフェイルオーバー機能を利用したリージョンを跨いだバックアップサイトの構築(EC2 to EC2編)
ただ、Route53を利用していない場合は、未だにヘルスチェック及びフェイルオーバーの機能は手組みする必要があります。無いなら作るしかないのです。わかりました、作りますよ。
前提
まず確認しておきたいのは、今回ご紹介するテクニックは、前述のRoute53によるフェイルオーバーと比べると、オモチャみたいなもんです。知ってますか? 何と、Route53のSLAは100%です。さらに、フェイルオーバー先をS3にした場合、そのSLAは99.9%です。
それに対し、今回ご紹介するテクニックは、フェイルオーバー先はEC2限定です。S3は選択できません。また、ヘルスチェック機能も、同じEC2上に構築します。Sorryサーバや、フェイルオーバー機能の可用性をどの程度確保するかによって、これらの機能の冗長化構成も検討する必要があるでしょう。もし仮に、Route53によるDNSフェイルオーバー機能を使える環境であるならば、迷わずそちらを使ってください。お兄さんとのお約束です。
アーキテクチャ
図をご覧下さい。左側のProduction serversが本来の機能を提供するサーバ(群)です。これらのサーバはELBの配下に登録済みで、ELBによるヘルスチェック(Health Check by ELB)を受けています。Production serversは正常に稼働しているため、これらはELBに対して異常なしの応答をしています。
右側のSorry serverは、障害発生時のSorryページをホストしています。それと共に、一定時間毎にELBのAPIを叩き、その配下にあるProduction serversのステータスを確認(Health Check by the script)し続けるスクリプトが動いています。この時、スクリプトは「InService」ステータスのインスタンスが1つ以上あること *1(図中の緑色のフラグ)によって、正常運用を確認しています。
次に、障害発生時について。障害が発生すると、Production serversは、ELBによるヘルスチェックに対し、エラー応答を返します。複数のProduction serversがあった場合、1つでも正常に稼働しているインスタンスがあれば、最低限サービスは稼働できます。しかし、全てがエラー応答をした場合を考えます。
その時、スクリプトは、ELBに対するヘルスチェックの応答によって、全てのProduction serverが「OutOfService」となったこと(図中の赤色のフラグ)を検知します。すると、スクリプトは、自分自身のインスタンス *2をELBに対して登録します。これにより、自分自身がELB配下で唯一healthyなインスタンスとなり、アクセストラフィックが全て自分に向くようになります。これにより、フェイルオーバーが実現します。
さらに障害検知してフェイルオーバーした後も定期的にELBのステータスを確認し続け、1つでもInServiceステータスのProduction serverが復活したら、自分自身のインスタンスをELBから外す処理を行います。この処理が行われると、障害からの復旧となります。
チュートリアル
では上記の構成を試してみましょう。まず、EC2インスタンス(Amazon Linux最新版、t1.micro)を2台立ち上げてください。Security Groupは 80/tcp と 22/tcp を開けておきます。片方をelbfailover-prod、もう片方をelbfailover-sorryと呼ぶことにします。
elbfailover-prodのセットアップ
Apacheインストールして、サービスをシミュレートするHTMLを置き、起動するだけです。
$ sudo yum -y install httpd $ echo "<html><head><title>OK</title><body><p>hello</p></body>" | sudo tee /var/www/html/index.html $ sudo chkconfig httpd on $ sudo service httpd start
ELBの作成及びセットアップ
新規でELBelbfailoverを作成し、その配下にelbfailover-prodを登録 *3します。
ヘルスチェックの間隔は、検証のため、一番短くしておきます。
ELBの配下に、elbfailover-prodを追加します。(一番上に見えているインスタンスは今回の件とは無関係です)
ELBの起動はしばらく時間が掛かるため、下記に進んでしまいましょう。elbfailover-sorryのセットアップが済み次第、ELBのステータスが 1 of 1 instances in service となっていることを確認しましょう。
elbfailover-sorryのセットアップ
同様にApacheをインストールし、Sorry画面をシミュレートするHTMLを置き、起動しています。
$ sudo yum -y install httpd $ echo "<html><head><title>NG</title><body><p>sorry</p></body>" | sudo tee /var/www/html/index.html $ sudo chkconfig httpd on $ sudo service httpd start
awscliを利用するので、インストールします。aws.configの内容は下記を参考に、適切に設定 *4してください。
参考: AWS Command Line Tool Python版
$ sudo easy_install pip $ sudo pip install awscli $ vi /home/ec2-user/aws.config $ echo "export AWS_CONFIG_FILE=/home/ec2-user/aws.config">> ~/.bash_profile $ echo "complete -C aws_completer aws">> ~/.bash_profile $ . ~/.bash_profile
jqも必須ですので、インストール。
$ sudo yum -y install git gcc make flex bison rake $ git clone https://github.com/stedolan/jq.git $ cd jq $ make $ sudo make install
以下を~/elbfailover.shとして作成します。
#! /bin/bash SORRY_INSTANCE=`curl -s http://169.254.169.254/latest/meta-data/instance-id` ELB_NAME=elbfailover SLEEPTIME=2 while :; do TIMESTAMP=`date -u +"%Y-%m-%dT%H:%M:%SZ"` ELB_STATUS=`aws elb describe-instance-health --load-balancer-name $ELB_NAME` HEALTHY_HOST_COUNT=`echo $ELB_STATUS | jq "[ .InstanceStates[] | select(.InstanceId != \"$SORRY_INSTANCE\" and .State == \"InService\") ] | length"` SORRY_HOST_COUNT=`echo $ELB_STATUS | jq "[ .InstanceStates[] | select(.InstanceId == \"$SORRY_INSTANCE\") ] | length"` if [ $HEALTHY_HOST_COUNT -gt 0 ]; then echo $TIMESTAMP : $HEALTHY_HOST_COUNT healty host\(s\) found on $ELB_NAME if [ $SORRY_HOST_COUNT -gt 0 ]; then echo deregistering sorry host: $SORRY_INSTANCE from $ELB_NAME RESULT=`aws elb deregister-instances-from-load-balancer \ --instances "{\"instance_id\": \"$SORRY_INSTANCE\"}" --load-balancer-name $ELB_NAME` if [ $? -eq 0 ]; then echo sorry host was deregistered successfully else echo "ERROR: fail to deregister sorry host" echo $RESULT | jq "." fi fi else echo $TIMESTAMP : healthy host not found on $ELB_NAME if [ $SORRY_HOST_COUNT -eq 0 ]; then echo registering sorry host: $SORRY_INSTANCE to $ELB_NAME RESULT=`aws elb register-instances-with-load-balancer \ --instances "{\"instance_id\": \"$SORRY_INSTANCE\"}" --load-balancer-name $ELB_NAME` if [ $? -eq 0 ]; then echo sorry host was registered successfully else echo "ERROR: fail to register sorry host" echo $RESULT | jq "." fi fi fi sleep $SLEEPTIME done
chmodで実行権限を与えて実行すると、約2秒に一度のヘルスチェックが始まります。頻度はスクリプトのSLEEPTIME変数で調整できます。
$ chmod +x ~/elbfailover.sh $ ~/elbfailover.sh 2013-03-04T06:56:04Z : 1 healty host(s) found on elbfailover 2013-03-04T06:56:06Z : 1 healty host(s) found on elbfailover ...
動作確認
まず、ブラウザでELBにアクセスすると、helloが帰ってくることを確認します。
次に、模擬障害として、elbfailover-prodにおいてhttpdをstopしてみます。すると、しばらくの間、ELBに対するアクセスは503を返すまたはタイムアウトするようになります。ELBによるヘルスチェックの周期及び障害判定閾値は最低(周期は0.1分=6秒、閾値は2)に指定してありますので、ELBが障害を認識するまでには少なくとも12秒掛かります。
ELBが障害を認識すると、API経由でスクリプトにも障害の情報が伝わります。スクリプトは2秒おきにステータスをチェックしているため、ここでも最大2秒の遅れが発生するはずです。スクリプトは下記のログを出力し、自分自身をELBに登録します。
... 2013-03-04T06:56:13Z : 1 healty host(s) found on elbfailover 2013-03-04T06:56:16Z : healthy host not found on elbfailover registering sorry host: i-XXXXXXXX to elbfailover sorry host was registered successfully 2013-03-04T06:56:19Z : healthy host not found on elbfailover 2013-03-04T06:56:21Z : healthy host not found on elbfailover ...
これにより、ELBのレスポンスがsorryを返し始めます。実測の結果、模擬障害の発生からsorryを返し始めるまで、30秒弱掛かりました。
続いて、模擬障害から復旧させてみます。elbfailover-prodにおいてhttpdをstartします。この場合も約12秒後にELBが復旧を認識し、スクリプトに情報が伝わります。スクリプトは下記のログを出力し、自分自身をELBから外します。
... 2013-03-04T06:57:07Z : healthy host not found on elbfailover 2013-03-04T06:57:10Z : 1 healty host(s) found on elbfailover deregistering sorry host: i-XXXXXXXX from elbfailover sorry host was deregistered successfully 2013-03-04T06:57:12Z : 1 healty host(s) found on elbfailover 2013-03-04T06:57:15Z : 1 healty host(s) found on elbfailover ...
ELBのレスポンスは、最初はsorryとhelloを等確率で返すようになりますが、間もなくsorryを返さなくなります。実測の結果、模擬障害から復旧後約20秒でhelloのみを返すようになりました。
まとめ
実際に運用する場合は、ELBのチェック間隔や、スクリプトの周期など、様々な検討が必要かと思います。また、今回紹介したスクリプトは無限ループを用いてポーリングを実現しましたが、cron等に書き直す必要はありそうです。また、Sorryサーバについては、あらゆるページに対するアクセスに対してsorryを返すように設定しなければなりませんし、その際のHTTPレスポンスのステータスは、200や404ではなく、503を返すように実装すべきでしょう。
さて、というわけで、今回はELBにおけるSorryサーバへのフェイルオーバーを手組みで実装してみました。AWS様におかれましては、この検証とブログエントリを水泡に帰すようなお知らせ「【AWS発表】Elastic Load Balancingにフェイルオーバー機能を追加。」を出してくれてもよくってよ。むしろ、早いとこ実装お願いします!!