AutoScaling時にAnsibleで環境構築を行う

2015.03.05

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

渡辺です。 自動化にはロマンがありますが、ロマンを求めすぎると現実に凹みます。 今回は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に追加されます。

autoscaling-ansible

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