EC2 Image Builder + Ansible + AWS CLI を用いたゴールデンAMIの更新/反映自動化の検討

Daichi Harada
Eureka Engineering
Published in
15 min readDec 19, 2019

--

re:Invent2019 で発表された EC2 Image Builder

この記事は「eureka Advent Calendar 2019」19日目の記事です。昨日の記事は

による Android開発におけるモジュール分割でした。

みなさんこんにちは! エウレカSRE チームの

です。

先日、念願の AWS re:Invent に行ってきました!様々な新サービス/新機能の発表がありワクワクしています。中でも一番刺さったサービスが、EC2 Image Builderでした。今回はこれを触ってみようと思います。

AWSさま主導 + Rettyさま、Metapsさま、C Channelさま、eurekaの4社で合同開催した住友不動産麻布十番ビルRecapイベントのレポート記事も合わせてご覧ください!

この記事で得られること

この記事では以下について検証しています。

  • EC2 Image BuilderでAnsible playbookを流す方法
  • ゴールデンAMIのASGへの反映をAWS CLIで自動化する方法

背景

弊社では2017年頃からゴールデンAMI方式のプロビジョニングを採用しており、ゴールデンAMIの作成には Packer + Ansible を使用しています。

以下記事でも指摘されているように、ゴールデンAMIのプロビジョニングと起動設定の変更は自動化されておらず、セキュリティパッチをあてる頻度や、反映作業の手間が問題となっていました。

PairsのAPIサーバはこの課題をコンテナ化で大枠解決したものの、その他のコンポーネントはまだEC2での運用となっているので、まだしばらくこのAMI反映のつらさとは付き合わないといけなさそうです。

今回はEC2 Image Builder と AWS CLI を使うことでゴールデンAMIの作成、反映までを省力化できないか検証してみました。

① EC2 Image BuilderでAnsible playbookを実行するPipelineの作成

EC2 Image Builderのパイプライン(Packerにあたる)と、テストの仕組み(Serverspecなどにあたる) には乗りたいものの、既存の Ansible playbook を書き直すのは大変なので、なるべくそのまま使いたいです。

そこで、EC2 Image BuilderでAnsibleを使えるのか試してみます。

Ansible playbook

cowsay を入れるだけの Ansible playbook です。 (本来は、これまでゴールデンAMIを作成するために使っていた複雑なplaybookが入る想定です)

---
- hosts: 127.0.0.1
tasks:
- name: install cowsay
yum:
name: cowsay
state: latest

https://github.com/dharada1/ansible-install-cowsay.git

EC2 Image Builder コンポーネント

EC2 Image Builderのコンポーネントのymlです。Ansible playbookを呼び出して実行するものです。

name: execute ansible playbook
schemaVersion: 1.0
phases:
- name: build
steps:
- name: install-ansible
action: ExecuteBash
inputs:
commands:
- sudo amazon-linux-extras install ansible2 -y
- name: install-git
action: ExecuteBash
inputs:
commands:
- sudo yum install git -y
- name: clone-playbook-repository
action: ExecuteBash
inputs:
commands:
- git clone https://github.com/dharada1/ansible-install-cowsay.git
- name: run-playbook
action: ExecuteBash
inputs:
commands:
- ansible-playbook ./ansible-install-cowsay/playbook.yml
- name: test
steps:
- name: test-cowsay
action: ExecuteBash
inputs:
commands:
- cowsay -f skeleton "I've finally installed with ansible playbook & EC2 Image Builder."

パイプラインの作成

以下手順でパイプラインを作成します。(詳細は割愛します。)

  • 上記ymlをコピペでコンポーネントの作成
  • コンポーネントを呼ぶレシピ作成
  • レシピからパイプラインを作成

実行

パイプラインを実行します。(コンソール上のRun Pipelineを押す)

Run Pipeline

実体としては裏でSSM Automationが動いていて、その様子はこちらで見ることができます。

SSM Automation

動作確認

cowsay

完成したamiでEC2を起動し、sshしてcowsayを打ってみます。

cowsay -f skeleton "I've finally installed with ansible playbook & EC2 Image Builder."

(テストを通っているので当たり前ですが) Ansible playbookが実行された結果、cowsayがインストールされていることが確認できます。

感想

  • ansible-vault での暗号化を使っている場合 SSM Parameter Store / Secrets Managerなどにpasswordを保存しておき、それを読み込むのがよいかと思います。
  • Packer + Ansibleモデルで採用していた、local環境/CodebuildからEC2へのAnsible playbook実行というのは実現しづらそうでした。playbookを1リポジトリで管理している場合、全体をEC2の中に全コピーするのは気持ち悪さがあるので、このパイプラインの手前に環境毎のplaybookを切り出してS3などに配置しておくレイヤーを挟むのもいいかもと思いました。

②AWS CLIでゴールデンAMIの反映を自動化

ここまで触ってみてわかったのですが、EC2 Image BuilderはいまのところゴールデンAMIの配布までしかしてくれないので、ASG / EC2への反映は別の仕組みでやる必要があります。

(アップデートに期待…)

今回はAWS CLIでスクリプトを組んでみたいと思います。

AWS CLIによるパイプライン実行からAMI取得までの自動化

Run Pipeline -> AMIの取得までは、コンソールを開かずとも以下のようなスクリプトで自動化可能です。

IMAGE_PIPELINE_ARN=<<パイプラインのARNをコピペしてください>># image builderの開始
IMAGE_BUILD_VERSION_ARN=`\
aws imagebuilder start-image-pipeline-execution \
--image-pipeline-arn $IMAGE_PIPELINE_ARN \
| jq .imageBuildVersionArn -r \
`
# 完了待ち
while [[ $STATUS != "AVAILABLE" ]]
do
STATUS=`\
aws imagebuilder get-image \
--image-build-version-arn $IMAGE_BUILD_VERSION_ARN \
| jq .image.state.status -r \
`
ECHO $STATUS
sleep 5
done
ECHO $STATUS
# ami取得
aws imagebuilder get-image \
--image-build-version-arn $IMAGE_BUILD_VERSION_ARN \
| jq .image.outputResources.amis[0].image -r

AWS CLIによるASGへのAMI反映の自動化

このスクリプトで取得したamiをtest-asgのlaunch configに反映します。これで、AutoScalingGroupで新しいEC2を立てるときは更新後のamiが使われるようになります。

DATE=$(date "+%s")
aws autoscaling create-launch-configuration \
--launch-configuration-name test-$DATE \
--image-id <<取得したAMI ID>> \
--instance-type t2.small
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name test-asg \
--launch-configuration-name test-$DATE

新AMIから作成したEC2の展開 / 旧EC2の退役

ASGにAMIが反映されたら、新AMIから作成したEC2の展開 / 旧EC2の退役を行います。以下の手順で作業しています。

  • EC2台数を二倍にする (新AMIでEC2が起動する)
  • EC2台数を元に戻す (終了ポリシーにより旧AMIのEC2が退役する)

AWS CLIでこの操作を自動化するスクリプトを仮組みしてみました。※インスタンスのヘルスチェックやエラーハンドリングなど考慮できていない点が多いので、本番で使うにはまだ不安があります。

# EC2の台数を取得.
# TODO (min max desiredがinstancesと揃ってること前提 x healthy & inserviceなこと前提...)
CURRENT_INSTANCE_NUM=`\
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names=$1 \
--profile=$2 \
--region=$3 \
--query "AutoScalingGroups[0].length(Instances)" \
2>/dev/null\
`
# EC2台数を2倍にする
DOUBLED_INSTANCE_NUM=`expr 2 \* $CURRENT_INSTANCE_NUM`
echo "現在のEC2台数:" $CURRENT_INSTANCE_NUM
echo "二倍のEC2台数:" $DOUBLED_INSTANCE_NUM
echo "二倍のEC2台数 ${DOUBLED_INSTANCE_NUM} 台へアップデートします"
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name=$1 \
--profile=$2 \
--region=$3 \
--min-size=$DOUBLED_INSTANCE_NUM \
--max-size=$DOUBLED_INSTANCE_NUM \
--desired-capacity=$DOUBLED_INSTANCE_NUM \
2>/dev/null
# 2倍になるまで待機.
while [[ $UPDATING_INSTANCE_NUM != $DOUBLED_INSTANCE_NUM ]]
do
UPDATING_INSTANCE_NUM=`\
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names=$1 \
--profile=$2 \
--region=$3 \
--query "AutoScalingGroups[0].length(Instances)" \
2>/dev/null\
`
echo "EC2の立ち上がりを待機しています..."
echo "現在の台数" $UPDATING_INSTANCE_NUM "期待台数" $DOUBLED_INSTANCE_NUM
sleep 5
done
# OK!
echo "現在のEC2台数:" $UPDATING_INSTANCE_NUM
echo "二倍のEC2台数:" $DOUBLED_INSTANCE_NUM
echo "二倍になりました。"
# 下げる
echo "最初のEC2台数 ${CURRENT_INSTANCE_NUM} 台へアップデートします"
aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name=$1 \
--profile=$2 \
--region=$3 \
--min-size=$CURRENT_INSTANCE_NUM \
--max-size=$CURRENT_INSTANCE_NUM \
--desired-capacity=$CURRENT_INSTANCE_NUM \
2>/dev/null
# もとに戻るまで待機.
while [[ $UPDATING_INSTANCE_NUM != $CURRENT_INSTANCE_NUM ]]
do
UPDATING_INSTANCE_NUM=`\
aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-names=$1 \
--profile=$2 \
--region=$3 \
--query "AutoScalingGroups[0].length(Instances)" \
2>/dev/null\
`
echo "EC2のシャットダウンを待機しています..."
echo "現在の台数" $UPDATING_INSTANCE_NUM "期待台数" $CURRENT_INSTANCE_NUM
sleep 5
done
# EC2台数がもとに戻った.
echo "現在のEC2台数:" $UPDATING_INSTANCE_NUM
echo "入れ替え完了しました."

実行すると以下のようになります。

❯ ./autoscale.sh test-asg my-account ap-northeast-1
現在のEC2台数: 1
二倍のEC2台数: 2
二倍のEC2台数 2 台へアップデートします
EC2の立ち上がりを待機しています...
現在の台数 1 期待台数 2
EC2の立ち上がりを待機しています...
現在の台数 1 期待台数 2
EC2の立ち上がりを待機しています...
現在の台数 2 期待台数 2
現在のEC2台数: 2
二倍のEC2台数: 2
二倍になりました。
最初のEC2台数 1 台へアップデートします
EC2のシャットダウンを待機しています...
現在の台数 2 期待台数 1
EC2のシャットダウンを待機しています...
現在の台数 2 期待台数 1
~~~~~~~~~~~~~~~中略~~~~~~~~~~~~~~~
現在の台数 2 期待台数 1
EC2のシャットダウンを待機しています...
現在の台数 1 期待台数 1
現在のEC2台数: 1
入れ替え完了しました.

パイプライン化

これらのスクリプトを組み合わせてローカルから流してもいいのですが、CodeBuildなりSSM Automationなどからも実行できるのではないかと思います。

まとめ

この記事では

  • EC2 Image Builder と組み合わせて Ansible playbook を実行する方法
  • AWS CLIを使って新AMIの反映、ASG/EC2への展開までを自動化する方法

を検証/検討しました。

既存のAnsible playbookを資産として活かしながら、Packerで実現していたAMI作成、またServerspecのようなテストのパイプラインをAWSの仕組みに乗ることができそうです。またAMIをEC2へ反映する部分まで含めた自動化も、AWS CLIで実現できそうです。

引き続き検証しつつ、アップデートを待ちつつ、移行するタイミングを見計らっていきたいと思います。

--

--