AWS Cli + Ansible + Jenkins で Auto Scaling Launch Configuration を自動更新

# 概要
- AWS Cli, Ansible, Jenkins を使ってイメージの作成から AutoScaling の Launch Configuration を自動更新する仕組みを構築

![スクリーンショット_042516_110112_AM.jpg](https://qiita-image-store.s3.amazonaws.com/0/41052/2e3b593d-e281-0bb0-bb18-e391405be4cc.jpeg)

# 環境
- Vagrant 1.8.1
- CentOS 7
- Python 3.5
- pip 8.1.1
- [AWS Cli 1.10.21](https://aws.amazon.com/jp/cli/)
- [jq 1.5](https://stedolan.github.io/jq/)
- Jenkins 2.0
- Ansible 1.9

インストール手順については割愛

# 流れ
1. AMI 作成用 EC2 インスタンスの作成
2. 起動した EC2 インスタンスを Ansible でリモートプロビジョニング
3. プロビジョニングした EC2 インスタンス から AMI を作成
4. EC2 インスタンスを Terminate
5. 作成した AMI をセットした起動設定（Launch Configuration）を作成
6. 作成した起動設定を AutoScaling Group にアタッチ
7. 古い起動設定を削除

# 下準備

## Jenkinsジョブ
- Parameterized Trigger plugin を使って、パラメータをテキストファイルに書き込み、下流ジョブに渡す。（[参考記事](http://qiita.com/yasuhiroki/items/daff66ab18b315ae4298)）

## AWS Cli
- `aws configure` コマンドであらかじめ ./aws/config と ./aws/credentials ファイルを作成して、/var/lib/jenkins 配下にコピーしておく。

# ジョブコード
- 基本やっていることは下記の3つ。
  1. aws コマンドを[いろいろなオプション付き](http://docs.aws.amazon.com/cli/latest/index.html)で実行する。
  2. レスポンスの JSON を jq コマンドでパースし、必要なパラメータを抽出する。
  3. パラメータを次のジョブに渡す。
- コマンドのパスを通すため .bashrc をジョブ毎で読み込む。（他の方法で可能なら必要ない）

## 1. AMI 作成用 EC2 インスタンスの作成
- [ec2 run-instances](http://docs.aws.amazon.com/cli/latest/reference/ec2/run-instances.html) を利用する。
- いくつか重複するオプションがあり（`security-groups`, `secruity-group-ids`, `placement Groups` など）、同時に宣言するとエラーになる。
- [ec2 wait instance-status-ok](http://docs.aws.amazon.com/cli/latest/reference/ec2/wait/instance-status-ok.html) オプションでインスタンスの起動がステータスOKになるまで待機する。

```bash
source ~/.bashrc

SECURITY_GROUP=sg-xxxx
KEY_NAME=xxxx
INSTANCE_TYPE=t2.micro
SUBNET_ID=subnet-xxxx

INSTANCES=`aws ec2 run-instances \
--image-id $AMI_ID \
--security-group-ids $SECURITY_GROUP \
--key-name $KEY_NAME \
--instance-type $INSTANCE_TYPE \
--placement AvailabilityZone=ap-northeast-1a \
--block-device-mappings '[{"DeviceName": "/dev/sda1", "Ebs":{"DeleteOnTermination":true}}]' \
--subnet-id $SUBNET_ID \
--no-associate-public-ip-address`

INSTANCE_ID=`echo $INSTANCES | jq -r '.Instances[].InstanceId'`

aws ec2 wait instance-status-ok --instance-ids $INSTANCE_ID

echo "INSTANCE_ID=$INSTANCE_ID" > $WORKSPACE/params.txt
```

## 2. 起動した EC2 インスタンスを Ansible でリモートプロビジョニング
- Ansible Play Book の内容は割愛
- Anisble は Jenkins サーバーにインストールし、EC2 インスタンスに対してリモートプロビジョニングを実行
- EC2 インスタンスのプライベートIPを inventory file に 追記し、`ansible-playbook`を実行

```bash
source ~/.bashrc

INSTANCES=`aws ec2 describe-instances --instance-ids $INSTANCE_ID`

PRIVATE_IP=`echo $INSTANCES | jq -r .Reservations[].Instances[].PrivateIpAddress`

cd $WORKSPACE/inventory
echo -e [server] > ami_template
echo -e "${PRIVATE_IP} ansible_ssh_user=centos ansible_ssh_private_key_file=/var/lib/jenkins/.ssh/id_rsa.pem" >>  ami_template

cd $WORKSPACE
ansible-playbook -i inventory/ami_template main.yml --tags xxx,xxx,xxx

echo "INSTANCE_ID=$INSTANCE_ID" > $WORKSPACE/params.txt
```

## 3. プロビジョニングした EC2 インスタンス から AMI を作成
- [ec2 create-image](http://docs.aws.amazon.com/cli/latest/reference/ec2/create-image.html) と 起動したインスタンスを使って AMI を作成する。
- AMI 作成時にタグを付けられない、[ec2 create-tags](http://docs.aws.amazon.com/cli/latest/reference/ec2/create-tags.html) を使って AMI にタグを付ける。
- [ec2 wait image-available](http://docs.aws.amazon.com/cli/latest/reference/ec2/wait/image-available.html) オプションで AMI が利用可能になるまで待機する。

```bash
source ~/.bashrc

NOW=`date '+%Y%m%d%H%M%S'`
AMI_NAME=service-ec-image_${NOW}

IMAGE=`aws ec2 create-image \
--instance-id $INSTANCE_ID \
--name $AMI_NAME \
--no-reboot`

NEW_AMI_ID=`echo $IMAGE | jq -r .ImageId`

aws ec2 create-tags --resources $NEW_AMI_ID --tags Key=xxxx,Value=xxxx
aws ec2 wait image-available --image-ids $NEW_AMI_ID

echo "INSTANCE_ID=$INSTANCE_ID\n" > $WORKSPACE/params.txt
echo "NEW_AMI_ID=$NEW_AMI_ID" >> $WORKSPACE/params.txt
```

## 4. EC2 インスタンスを Terminate
- [ec2 terminate-instances](http://docs.aws.amazon.com/cli/latest/reference/ec2/terminate-instances.html) を使って不要になった EC2 インスタンスを削除する。
- [ec2 wait instance-terminated](http://docs.aws.amazon.com/cli/latest/reference/ec2/wait/instance-terminated.html) を使って EC2 インスタンスが削除されるまで待機する。

```bash
source ~/.bashrc

aws ec2 terminate-instances --instance-ids $INSTANCE_ID
aws ec2 wait instance-terminated --instance-ids $INSTANCE_ID
```

## 5. 作成した AMI をセットした起動設定（Launch Configuration）を作成
- [autoscaling create-launch-configuration](http://docs.aws.amazon.com/cli/latest/reference/autoscaling/create-launch-configuration.html) を使って、上記で作成した AMI を指定して起動設定を作成する。

```bash
source ~/.bashrc

NOW=`date '+%Y%m%d%H%M%S'`
LAUNCH_CONFIGURATION_NAME=service_${NOW}
SECURITY_GROUP=sg-xxxxx
INSTANCE_TYPE=t2.micro

aws autoscaling create-launch-configuration \
--launch-configuration-name $LAUNCH_CONFIGURATION_NAME \
--image-id $NEW_AMI_ID \
--security-groups $SECURITY_GROUP \
--instance-type $INSTANCE_TYPE

echo "LAUNCH_CONFIGURATION_NAME=$LAUNCH_CONFIGURATION_NAME" > $WORKSPACE/params.txt
```

## 6. 作成した起動設定を Auto Scaling Group にアタッチ
- [autoscaling describe-auto-scaling-groups](http://docs.aws.amazon.com/cli/latest/reference/autoscaling/describe-auto-scaling-groups.html) を使って、指定の Auto Scaling Group にセットされている起動設定情報を取得する。
- [autoscaling update-auto-scaling-group](http://docs.aws.amazon.com/cli/latest/reference/autoscaling/update-auto-scaling-group.html) を使って、上記で作成した起動設定を指定の Auto Scaling Group にアタッチする。

```bash
source ~/.bashrc

AUTO_SCALING_GROUP=`aws autoscaling describe-auto-scaling-groups \
--auto-scaling-group-name service_asg`

OLD_LAUNCH_CONFIGURATION_NAME=`echo $AUTO_SCALING_GROUP | jq -r .AutoScalingGroups[].LaunchConfigurationName`

aws autoscaling update-auto-scaling-group \
--auto-scaling-group-name service_asg \
--launch-configuration-name $LAUNCH_CONFIGURATION_NAME

echo "OLD_LAUNCH_CONFIGURATION_NAME=$OLD_LAUNCH_CONFIGURATION_NAME" > $WORKSPACE/params.txt
```

## 7. 古い起動設定を削除
- [autoscaling delete-launch-configuration](http://docs.aws.amazon.com/cli/latest/reference/autoscaling/delete-launch-configuration.html) を使って、古い起動設定を削除する。

```bash
source ~/.bashrc

aws autoscaling delete-launch-configuration \
--launch-configuration-name $OLD_LAUNCH_CONFIGURATION_NAME
```

# まとめ
- [Packer](https://www.packer.io/) の代用になる仕組みを AWS Cli を使って構築できた。
- Packer で Ansible を利用する場合、AMI に Anisble をインストール する必要があり（EC2 の場合）、動作環境に余計なツールをインストールしたくなかったので断念した。
- 発展系として作成した AMI が正しい状態かどうかを確認するフローを検討したい。