EC2を停止して開始した時はELBに再登録する

アイキャッチ
119件のシェア(ちょっぴり話題の記事)

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

こんにちは、虎塚です。

今日は、ELBに紐づけたEC2を停止・開始した時に、EC2をELBに再登録する必要があることについて書きます。

これはELBを使い始めるとすぐに遭遇する問題のため、お客様からのお問い合わせが多い話題です。今月(2014年9月)すでに2件の問い合わせがあったので、書いておくことにしました。

現象

ELBと紐づけたEC2が、ELBから正しく認識されていて、リクエストを分配できる状態の時、ELBのステータスは「In Service」となります。ELBにEC2を紐づけてすぐの状態だと「Out of Service」ですが、数分たつと「In Service」になりますね。

ところが、ELBと紐づけたEC2を停止して、一定時間後に開始した後、「Out of Service」のままになることがあります。

公式ドキュメントによる解説

公式ドキュメントでは、この件について次のように書かれています。

EC2-ClassicでEC2を利用する場合

Elastic Load Balancing は、関連付けられた IP アドレスを使用してロードバランサーを EC2 インスタンスに登録します。EC2-Classic で起動した EC2 インスタンスを停止して開始すると、インスタンスに関連付けられた IP アドレスが変わります。ロードバランサーは新しい IP アドレスを認識しません。EC2-Classic で起動した登録済みの EC2 インスタンスを停止してから起動する場合は、停止したインスタンスをロードバランサーから登録解除し、再起動したインスタンスを再登録する必要があります。そうしないと、再起動したインスタンスへのトラフィックをロードバランサーがルーティングできない可能性があります。

http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/US_DeReg_Reg_Instances.html

EC2の停止と開始を経て、EC2のIPアドレスが変わってしまうため、ELBから認識できなくなってしまうのですね。

EC2Classic

EC2-ClassicでEC2を利用している場合、ELBに登録したEC2を停止して再開した後には、ELBへの再登録が必須です。

VPC内でEC2を利用する場合

EC2-VPC 内でインスタンスを起動した場合、デフォルトでは、インスタンスに関連付けられた IP アドレスは、インスタンスを停止して起動しても変わりません。ただし、EC2-VPC インスタンスを停止して起動した場合、停止したインスタンスが起動されたことをロードバランサーが認識するまで時間がかかることがあります。その間、ロードバランサーは再起動したインスタンスに接続されていません。再起動したインスタンスをロードバランサーに再登録することをお勧めします。

http://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/US_DeReg_Reg_Instances.html

VPC内では、EC2の停止と開始によってIPアドレスは変わりませんが、停止状態から復帰したEC2をELBがすぐに見に行ってくれないようです。

VPC

VPC内でEC2を利用している場合、ELBに登録したEC2を停止して再開した後には、ELBへの再登録が推奨されています。

対処法

というわけで、EC2を停止する前に、ELBから外し、EC2を起動した後に、ELBに再登録しましょう。

EC2の停止と開始のたびにELBの設定も行うのが面倒、あるいは忘れてしまいそうな場合は、スクリプトで対処します。EC2の起動時スクリプトの中で、ELBからEC2を外す処理と、ELBにEC2を付ける処理を行います。

たとえば、次のようなスクリプトを使います。

#!/bin/bash
# re-register EC2 to ELB
#
# chkconfig: 2345 99 10
# description: Remove EC2 from ELB, and add EC2 with ELB

# Source function libarary.
. /etc/init.d/functions

prog=elb-ec2-re-register
add_prog=elb-ec2-add
remove_prog=elb-ec2-remove
add_exec=/root/${add_prog}.sh
remove_exec=/root/${remove_prog}.sh

add_log=/root/${add_prog}.log
remove_log=/root/${remove_prog}.log
lock=/var/lock/subsys/${prog}

if [ -f /etc/sysconfig/$prog ]; then
  . /etc/sysconfig/$prog
fi

case "$1" in
  start)
    touch $lock
    $remove_exec >> $remove_log 2>&1
    $add_exec >> $add_log 2>&1
    rm -f $lock
    ;;
  stop)
    ;;
  restart)
    ;;
  *)
    echo $"Usage: $0 {start|stop}"
    exit 2
esac

exit $?
  • 冒頭の「chkconfig:」行は自動起動の設定に必要です
  • 必要な設定があれば、/etc/sysconfig/以下に置いて、処理の最初に読み込みます

ec2-elb-re-registerから呼び出している2つのスクリプトは、次のようになります。

# cat /root/elb-ec2-add.sh
#!/bin/sh
ELB_NAME=elb_name
regi=ap-northeast-1

instance_id=`curl http://169.254.169.254/latest/meta-data/instance-id/`

aws --region=$regi elb register-instances-with-load-balancer --load-balancer-name=$ELB_NAME --instances=$instance_id
#!/bin/sh
ELB_NAME=elb_name
regi=ap-northeast-1

instance_id=`curl http://169.254.169.254/latest/meta-data/instance-id/`

aws --output=text --region=$regi elb deregister-instances-from-load-balancer --load-balancer-name=$ELB_NAME --instances=$instance_id
  • ELBの名前(elb_name)とリージョン(regi)は、用途に応じて変更します
  • メタデータを利用して自分自身のインスタンスIDを取得します(http://169.254.169.254/...)
  • スクリプトが自動起動するように設定します

スクリプには自動起動の設定をしておきましょう。

# chmod +x /etc/init.d/ec2-elb-re-register
# chkconfig --add ec2-elb-re-register
# chkconfig ec2-elb-re-register on
# /etc/init.d/ec2-elb-re-register start

注意点

ELBに登録したままのEC2を停止した際に、ELBのヘルスチェックが失敗することが気になる場合は、EC2を停止する前にELBから外しましょう。このとき、Management ConsoleやAWS CLIなど、インスタンスの外部から処理をするのがよいと思います。

OS終了時の処理としてスクリプトを書くのはやめておきましょう。/etc/init.d/haltに処理を書いたり、起動スクリプト内のstop分岐に終了時処理を書いたりする方法がありますが、終了時の処理は実行が保証されません。実際、起動スクリプト内のstop分岐に書いて試してみたところ、一度も成功しませんでした(シェルでSIGKILLを受け取った後に、trapでいかなる処理もできずに強制終了する振る舞いと似たようなものではないかと思っているのですが、本当かどうかは知りません。ご存じの方、お教えください)。

シャットダウンを開始したOSの内部から、ネットワークアクセスを伴う処理をするのは行儀が悪いといえます。さらに、自分が書くスクリプトだけが、このような行儀が悪い処理をするとは限らないのも怖いところです。終了時処理でメモリを確保する→メモリ解放したら終了時処理が走る→終了時処理でメモリを確保する……という無限ループに突入し、OSを終了できなくなるトラブルもありえます。

ELBへのEC2再登録(ELBから外して、ELBに付ける)処理は、安全かつ確実に実行するためにも、起動時処理として記述しましょう。

その他のポイント

EC2のWebサーバにあるindex.htmlにリクエストを送信してヘルスチェックをする設定をしていると、当然ながらヘルスチェックが成功するにはWebサーバが起動している必要があります。インスタンスの起動に合わせてWebサーバが自動起動するように設定しておきましょう。

おわりに

今日は中秋の名月ですね。ELBからEC2を付けたり外したりしながら、お月見をするのもよいのではないでしょうか。

それでは、また。

お月見くらめそちゃん

クラスメソッド非公式キャラクター・くらめそちゃん

  • Manabu Sakai

    こんにちは。いつも参考にさせていただいています!

    終了時にうまくいかないのは ec2-elb-re-register の呼ばれるタイミングが “chkconfig: 2345 99 10” となっているからではありませんか? “chkconfig: 3 98 1” で試しても失敗しますか?

    自分たちのプロダクション環境ではシャットダウン時にログを S3 に退避していますが、一度も失敗したことはありません。参考になるかわかりませんが GitHub にソースコードを上げています。
    https://github.com/manabusakai/s3-sync

    • torazuka

      こんにちは。コメントありがとうございます。

      “chkconfig: 3 98 1″は、「runlevelが3の時、起動処理の98番目、終了処理の1番目に実行する」という設定ですよね。スクリプトをこちらに書き換えて、chkconfigに登録し直してから試してみましたが、やはり実行されませんでした。

      詳しく書きますと、次のような挙動になりました。

      – start処理に何も書かず、stop処理にELBから外す設定を書く。その後、EC2を停止する。 → EC2はELBに紐づいたままOutOfServiceになる

      – start処理にELBから外す設定だけを書き、stop処理に何も書かない。その後、EC2を停止し、数分待った後、起動する。 → EC2が起動した後、ELBから外れる

      記事内でお伝えしたかったことは、「(たとえ上手く実行されたとしても)終了時処理で終了以外の処理をするのはやめましょう」ですが、どのような理由で(終了処理以外の)終了時スクリプトが実行されるか/あるいはされないかには興味がありますので、スクリプトを参考にさせていただきます。ありがとうございます!