オートヒーリングでゆるーくEIPもヒーリングしてみた

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

こんにちは、臼田です。

今回はAutoScalingを利用したインスタンスの自動復旧(オートヒーリング)時にEIPもヒーリングする方法を考えたのでご紹介します。

目的

例えば下記のようにWebサーバの環境で、低コストに抑えるために可用性を犠牲にしてシングルAZで構成するような場合があると思います。

この際に、インスタンスに異常が発生したら自動的にインスタンスを入れ替えるようにAutoScalingを利用してオートヒーリングする構成を取る場合があります。

しかし、通常ではオートヒーリングしてインスタンスが変更されると、インスタンスのパブリックIPが変更されます。

あまりインパクトが大きいわけではなく、マネジメントコンソールから新しいパブリックIPを確認したり、EIPを振り直せば済む話なのですが、これらの手間を省くためにEIPもオートヒーリングしてもらうことにしました。

なお、ゆるめにスクリプトを書いているので、ゆるめに利用してもらえればと思います。

[追記]EC2 APIを叩くためにはパブリックIP自動付与をオンにするかEC2 APIをVPC Endpointに設定しておく必要があります。

実装

スクリプト自体は以下のとおりです。シェルスクリプトをユーザデータに仕込んで利用することを想定しています。なお、都合上CentOS6.9でのみ動作確認を行なっていますので、AmazonLinuxやその他OSでの利用の際にはよく検証していただければと思います。

スクリプト

#!/bin/bash
RETRY=0
while :; do
	instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
	region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
	export AWS_DEFAULT_REGION=${region}
	allocation_id=$(aws ec2 describe-instances --instance-id ${instance_id} | jq -r '.Reservations[].Instances[].Tags[] | select(.Key == "allocation-id").Value')
	aws ec2 associate-address --allocation-id ${allocation_id} --instance ${instance_id}
	[ $? -eq 0 ] && break
	[ $RETRY -eq 4 ] && break
	RETRY=$(( RETRY+=1 ))
	sleep 1m
done

動作は以下のとおりです。

  1. メタデータからinstance_idとregionを取得
  2. regionを環境変数に設定(awscliのため)
  3. 「allocation-id」タグに設定されたEIPのallocation-idを取得
  4. associate-addressによりEIPの設定
  5. 失敗したら1分間待って4回までリトライ(計5回)

起動後すぐにAPIコールが失敗したり、オートヒーリング時に前のインスタンスがEIPを開放していない場合も考慮してリトライを設定しています。が、今のところ手元の環境では失敗せず動作しています。

AMIの準備

上記スクリプトではawscliとjqを利用しているのでこれを導入しておきます。

なお、ユーザデータで実行するためroot権限でパスが通っている必要があります。

# rootでの$PATH
[root@ip-172-31-23-66 centos]# echo $PATH
/sbin:/bin:/usr/sbin:/usr/bin

awscliは下記の方法で導入すれば問題ありません。

AWS CLI のインストールと設定 - Amazon Kinesis Data Streams

$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
$ sudo python get-pip.py
$ sudo pip install awscli
$ whereis aws
aws: /usr/bin/aws /usr/bin/aws.cmd

jqについては下記の「localがおきらい」を参考にすると/usr/binに導入されます。

jq コマンドの Linux への速攻インストール - Qiita

$ sudo curl -o /usr/bin/jq -L https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && sudo chmod +x /usr/bin/jq

ツールのインストールが完了したらインスタンスを停止し、AMIを作成しておきます。

IAM Roleの準備

EC2に対して操作を実行するためIAM Roleを用意する必要があります。

必要なのはec2:DescribeInstancesec2:AssociateAddressです。直接ポリシーを書いてもいいですが、最近下記のようにビジュアルエディターでポリシーを作成することができるようになったので、利用してみます。

[新機能]IAMのVisual Editorを使って見た[便利!]

IAMのポリシー画面から「ポリシーの作成」を押します。

サービスでは「EC2」を選択します。

アクションでは「リスト」を展開して「DescribeInstances」を選択します。

もう一つ、「書き込み」を展開して「AssociateAddress(アソシエイトアドレス)」を選択します。なお、近くに「AllocateAddress(アロケートアドレス)」があるので間違えないようにしましょう。アソシエイトです。

右下の「Review policy」を押します。

名前を入れて「Create policy」を押したらポリシー作成完了です。

ちなみに作成されたポリシーは下記のようになっています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:AssociateAddress"
            ],
            "Resource": "*"
        }
    ]
}

作成されたポリシーを別途IAM Roleを作成してアタッチしましょう。

Launch Configの設定

準備が整ったらLaunch Configの設定を行います。

作成したAMIを選択し、作成したIAM Roleを設定します。

ユーザデータで上記スクリプトを仕込みます。

後は環境に合わせて作成します。

AutoScaling Groupの設定

作成したLaunch Configを利用したAutoScaling Groupを作成します。

タグの設定にて、EIP割り当てに利用するための「allocation-id」タグを設定します。なお、事前にEIPを作成して「allocation-id」を控えておいて下さい。

起動してみる

起動中にEIPが確認されました。

起動したEC2を停止にして、自動的に新しいインスタンスが上がってくるのを待ちます。

新しいEC2の方に自動的に同じEIPが付きました。

想定したとおり、インスタンスが入れ替わっても同一のEIPでアクセスできるようになりました。

まとめ

今回はゆるーくEIPのオートヒーリングを実装してみました。

例外処理をいろいろ考えだしたらキリがないですが、シングルAZの構成ですから、このくらいのゆるさがちょうどいい気もします。

誰かのお役に立てれば幸いです。