ちょっと話題の記事

Amazon ELBでSorryサーバへのフェイルオーバーを実現する

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

AWSにおいて、ELBによるSorryサーバへのフェイルオーバーは、長らく待ち望まれている機能です。先日、Route53にDNSレベルでのフェイルオーバー機能が実装(下記)されました。

ただ、Route53を利用していない場合は、未だにヘルスチェック及びフェイルオーバーの機能は手組みする必要があります。無いなら作るしかないのです。わかりました、作りますよ。

前提

まず確認しておきたいのは、今回ご紹介するテクニックは、前述のRoute53によるフェイルオーバーと比べると、オモチャみたいなもんです。知ってますか? 何と、Route53のSLAは100%です。さらに、フェイルオーバー先をS3にした場合、そのSLAは99.9%です。

それに対し、今回ご紹介するテクニックは、フェイルオーバー先はEC2限定です。S3は選択できません。また、ヘルスチェック機能も、同じEC2上に構築します。Sorryサーバや、フェイルオーバー機能の可用性をどの程度確保するかによって、これらの機能の冗長化構成も検討する必要があるでしょう。もし仮に、Route53によるDNSフェイルオーバー機能を使える環境であるならば、迷わずそちらを使ってください。お兄さんとのお約束です。

アーキテクチャ

elb-failover

図をご覧下さい。左側の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します。

elbfailover-1

ヘルスチェックの間隔は、検証のため、一番短くしておきます。

elbfailover-2

ELBの配下に、elbfailover-prodを追加します。(一番上に見えているインスタンスは今回の件とは無関係です)

elbfailover-3

ELBの起動はしばらく時間が掛かるため、下記に進んでしまいましょう。elbfailover-sorryのセットアップが済み次第、ELBのステータスが 1 of 1 instances in service となっていることを確認しましょう。

elbfailover-status

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にフェイルオーバー機能を追加。」を出してくれてもよくってよ。むしろ、早いとこ実装お願いします!!

脚注

  1. 場合によっては、この条件を変更してもいいでしょう。
  2. 設定により調整しても良いでしょう。
  3. ELBの名前は、スクリプト内で参照しますので、異なった名前を付けた場合はスクリプトも修正してください。
  4. IAM Roleを使えばもっと楽にできますね。