AutoScaling時にAnsibleで環境構築を行う
渡辺です。 自動化にはロマンがありますが、ロマンを求めすぎると現実に凹みます。 今回はAuto ScalingとAnsibleを混ぜてみました。
Auto Scalingとは?
Auto Scalingは、一言で言えばEC2インスタンスを自動拡張/縮退を実現する機能です。 Auto Scalingを活用する事で耐障害性や耐障害性を高めることができます。 また、処理能力を必要に応じて増加させることもできるのも魅力のひとつです。
Auto Scalingは非常に便利で強力な機能ですが、どんなシステムでも簡単に利用できるとは限りません。 インスタンスが自動的に起動したり破棄されたりするため、インスタンスが有効になるまでの手順が自動化することが必要です。 また、インスタンスは使い捨てできるような状態、すなわちステートレスに設計・構築しなければなりません。 これらの注意点については、AutoScalingを使う際の注意点についてまとめてみましたを参照してください。
インスタンス構築の自動化
これまでは、インスタンスの起動からサービスとして有効になるまで自動セットアップするには大きくふたつの方法がありました。 cloud-initとGoleden AMIです。
cloud-init
EC2にはcloud-initと呼ばれる機能があり、Launch時に実行するスクリプトを登録することができます。 ここにセットアップスクリプトを埋め込むことができればインスタンス起動時にサービスとして有効化できることになります。 ただ、実行するスクリプトが複雑になってくると、cloud-initでは現実的ではなくなってきます。
Golden AMI
予めセットアップ済みインスタンスからカスタムAMIを作成し、Auto Scaling時にはそのAMI(Golden AMI)からインスタンスを構築することができます。 これならばセットアップ時の時間もかかりません。 Golden AMIを作る手間はありますが、これまでのベストプラクティスといえるでしょう。
Golden AMIは手動でセットアップすることもできます。 しかし、最近の流行としてはAnsibleなどの構成管理ツールを使うと自動化するのがオススメです。
Lifecycle Hookでの構成管理ツール
以前はAuto Scaling時にインスタンスを初期化するためにはcloud-initを利用するかGolden AMIを利用するか、あるいは併用するかしかありませんでした。 しかし、現在はLifecycle Hook機能の登場により、構成管理ツールを実行してインスタンスをセットアップすることも簡単に実現できます。 今回は、Ansibleを利用してAuto Scalingで起動したインスタンスをセットアップする方法を紹介します。
Lifecycle HookによるAnsible実行
Lifecycle HookはAuto Scaling時にインスタンスの起動や破棄時に通知を行う仕組みです。 この仕組みを利用し、インスタンスの起動からAuto Scaling Groupへの追加の間に、Ansibleを実行しインスタンスのセットアップをすることにしてみます。
構成と仕組み
Auto Scaling Groupのインスタンスの他にAnsibleを実行しセットアップを制御するインスタンスを用意します(AutoScalingController)。 Auto ScalingにはLifecycle Hookを登録し、インスタンスのLaunch時にSQSにメッセージを投げるように設定しておきます。 AutoScalingControllerではSQSをPollingし、Launchを検知したならばAnsibleを走らせ、インスタンスをセットアップします。 インスタンスのセットアップが完了したならば、Auto Scalingに通知を行い、インスタンスはAuto Scaling Groupに追加されます。
Auto Scaling Groupの作成
LifeCycleHook機能のご紹介を参考にAuto Scaling Groupを作成してください(AnsibleGroup)。 AMIは素のAmazon Linuxを使ってください。 とりあえず、インスタンスを起動しないようにGroup sizeを「0」としておきます。
Amazon SQSの作成
Lifecycle Hookの通知先として利用するSQSを作成します(ansible-auto-scaling-queue)。 後で必要になるのでARNをメモしておきましょう。
IAM Roleの作成
LifeCycleHook機能のご紹介を参考に、SQSへのアクセス権限を持ったIAM Role(notifyAutoScalingRole)を作ります。 LifeCycleHook機能のご紹介ではSNSを利用していますが同じ操作でOKです。
Lifecycle Hookの登録
AWS CLIを利用し、Lifecycle Hookを登録します。
$ aws autoscaling put-lifecycle-hook \ --lifecycle-hook-name AnsibleGroupLauncingHook \ --auto-scaling-group-name AnsibleGroup \ --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING \ --notification-target-arn arn:aws:sqs:ap-northeast-1:000000000000:ansible-auto-scaling-queue \ --role-arn arn:aws:iam::000000000000:role/notifyAutoScalingRole
これで、Auto Scaling Group(AnsibleGroup)で新しいインスタンス起動した時、Groupに追加される前にSQS(ansible-auto-scaling-queue)に通知が送られるようになりました。 Lifecycle Hookを登録した場合、complete-lifecycle-actionを行うまでGroupに追加されません。
AutoScalingControllerのセットアップ
AutoScalingControllerはAmazon Linuxで通常のEC2インスタンスとして起動します。 AutoScalingController自体が重い処理を行うワケでもないため、microインスタンスでも充分でしょう。
Ansibleのインストール
今回は構成管理ツールとしてAnsibleを使います。 yumでインストールしましょう。
$ sudo yum -y --enablerepo=epel install ansible
秘密鍵の配置
AnsibleがAutoScalingしたインスタンスにSSH接続できるように秘密鍵(~/.ssh/autoscaling.pem)を配置しておきます。
Ansibleの設定
ansible.cfgを配置し、SSH実行時のホストキーのチェックを無効化しておきます。
[defaults] host_key_checking = False
Playbookの配置
今回はTomcat8を配置するためのPlaybook(tomcat8.yml)を配置します。 システムに応じて調整しましょう。
- hosts: all user: ec2-user sudo: True tasks: - name: Install JDK8, Tomcat8 yum: name={{item}} state=latest with_items: - java-1.8.0-openjdk-devel - tomcat8 - name: Remove JRE7 yum: name=java-1.7.0-openjdk state=absent - name: Start Tomcat and enable service service: name=tomcat8 enabled=yes state=started
構築スクリプトの配置
今回は次のようなスクリプトを準備しました。
#!/bin/sh QUEUE_URL="https://ap-northeast-1.queue.amazonaws.com/000000000000/ansible-auto-scaling-queue" AUTO_SCALING_HOOK_NAME=AnsibleGroupLauncingHook AUTO_SCALING_GROUP_NAME=AnsibleGroup Messages=`aws sqs receive-message --queue-url $QUEUE_URL` ReceiptHandle=`echo $Messages | jq -r '.Messages[].ReceiptHandle'` Body=`echo $Messages | jq -r '.Messages[].Body' | sed -e 's/\\\"/\"/g'` if [ -z "$Body" ]; then echo 'No messages' exit 0 fi LifecycleTransition=`echo $Body | jq -r '.LifecycleTransition'` LifecycleActionToken=`echo $Body | jq -r '.LifecycleActionToken'` EC2InstanceId=`echo $Body | jq -r '.EC2InstanceId'` if [ "$LifecycleActionToken" = "null" ]; then echo "Not Lifecyle message" exit 0 fi State=`aws ec2 monitor-instances --instance-ids $EC2InstanceId | jq -r '.InstanceMonitorings[].Monitoring.State'` PrivateIp=`aws ec2 describe-instances --instance-ids $EC2InstanceId | jq -r '.Reservations[].Instances[].PrivateIpAddress'` if [ "$State" != "enabled" ]; then echo 'Not enabled instace (retry latter).' exit 0 fi echo $PrivateIp > hosts_$EC2InstanceId echo "setup instance: $EC2InstanceId ($PrivateIp)" ansible-playbook -i hosts_$EC2InstanceId --private-key=.ssh/autoscaling.key tomcat8.yml aws autoscaling complete-lifecycle-action --lifecycle-hook-name $AUTO_SCALING_HOOK_NAME --auto-scaling-group-name $AUTO_SCALING_GROUP_NAME --lifecycle-action-token $LifecycleActionToken --lifecycle-action-result CONTINUE aws sqs delete-message --queue-url $QUEUE_URL --receipt-handle $ReceiptHandle aws ec2 create-tags --resources $EC2InstanceId --tags Key=Name,Value=Tomcat8 echo "Finish"
このスクリプトでは、はじめにSQSからメッセージを取得します。 メッセージがあれば内部をjqを利用して解析し、対象となるEC2のInstanceIdを取得します。 InstanceIdが取得できたならば、インスタンスの状態とPrivateIPを取得します。 Auto Scalingではインスタンスがrunningになった時点でSQSに通知されますが、runningになっただけでは初期化(initalizing)が完了していない可能性が高いです。 そのため、stateがenabledになるまでは処理を行わないようにしています。
Auto Scalingで起動したインスタンスがenabledになったならば、ansible-playbookでTomcatのセットアップを行います。 この時、hostファイル(Inventoryファイル)を必要とするため、PrivateIpを書き出したファイルを作成することにしました。
Ansibleによるセットアップが完了したならば、Lifecycleの完了をAuto Scaling Groupに通知し、メッセージをSQSから削除します。また、ついでにインスタンスにタグを設定しています。
構築スクリプトのcron登録
仕上げに作成した構築スクリプトをcronに登録し、定期的に(例えば1分毎)に実行するように設定します。
実行
準備ができたならば、Auto Scalingのmin/maxなどを調整し、インスタンスを起動させてください。 SQSで連動し構築スクリプトが実行され、完了後にAuto Scaling Groupに参加することが確認できます。
まとめ
Auto ScalingのLifecycle Hookを利用すると、Ansibleなどの構成管理ツールと簡単に連携することができます。 Golden AMIを作成してしまうのもひとつの解決方法ですが、あわせてAWS CLIでタグをつけるなど追加処理を行いたいならば、AutoScalingControllerを用意して外部処理をするのも良いでしょう。 Ansibleと連動し、完全に自動化されたAuto Scalingも夢ではありません!
ただし、夢を見すぎて自動化しすぎるとかえってコストが高くなる場合もあるのでご注意ください:p