EC2 Image Builderで作ったAMI IDを動的に取得して、AutoScalingグループの起動設定を更新する

この記事は[ZOZOテクノロジーズ #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech2) 14日目の記事になります。
昨日は、[@_sa_chi_03](https://qiita.com/_sa_chi_03)さんの「[ZOZOTOWNのメールマガジンの仕組みについて](https://qiita.com/_sa_chi_03/items/cce9261c9c8f9a6bf7ae)」でした。

# まえがき
ZOZOテクノロジーズ SREチームのテックリード光野と申します。普段はAWSを使って、より安全でより手がかからず、お金もかからない。でも速い。そんないい感じのネットワーク構築に邁進しています。

さて、この記事ではAWS re:Invent 2019で発表された[EC2 Image Builder](https://aws.amazon.com/jp/blogs/news/automate-os-image-build-pipelines-with-ec2-image-builder/)を扱います。EC2 Image Builderは、指定したレシピを元にEC2のカスタムイメージを作成することができるサービスです。任意もしくは定期的なタイミングでAMIを更新することができます。

AutoScalingグループと合わせれば、毎日最新のセキュリティパッチのあたったAMIを作っておいて、インスタンス入れ替え、なんて事が簡単にできます。幸せです。

この投稿では、EC2 Image Builderで作成したAMI IDを機械的に参照できるよう工夫してみたいと思います。

# 本文
## 目標：CloudFormationテンプレートで動的参照
弊社ではインフラの管理に[CloudFormation](https://aws.amazon.com/jp/cloudformation/)を多用しています。
例えば、CloudFormationでAutoScalingの起動設定を作成する場合、次のように記述します。

```yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:
  AutoScalingLaunchConfiguration:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    Properties:
      ImageId: 'ami-0ab292e52c1d6ac8c' # Ubuntu 18.04 LTS
      InstanceType: 'm5.large'

  # 最低限の記述です。実際にはセキュリティグループなどが必要でしょう。。
```

ここでは、`ImageId`を可変にし、CloudFormation実行のタイミングで、常にImage Builderが作成する最新のAMI IDを参照するようにします。

## 組み合わせ
CloudFormationでは[SSM](https://aws.amazon.com/jp/systems-manager/)のParameter Storeに登録した値を動的に参照することができます。

```yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:
  AutoScalingLaunchConfiguration:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    Properties:
      ImageId: "{{resolve:ssm:oreore-image-with-eib:1}}"
      InstanceType: 'm5.large'
```
or 

```yaml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  AMI:
    Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    Default: 'oreore-image-with-eib'
Resources:
  AutoScalingLaunchConfiguration:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    Properties:
      ImageId: !Ref AMI
      InstanceType: 'm5.large'
```

パラメータからの指定であれば、バージョン番号の入力を省略できるため、常に最新〜という指定が叶えられそうです。他方、EC2 Image BuilderはSNSに対応しているため、SNS経由でなんとかParameter Storeまで値を届けられそうな気がします。

## 作ったもの

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62601/39ebe17e-c322-d8a5-8455-79542fe642f9.png)

ここではUserが手動でCloudFormationを更新します。

Lambdaに設定したスクリプトは以下のとおりです。Rubyが好きなので.rbです。

```rb
# coding: utf-8
require 'json'
require 'aws-sdk-ssm'

def lambda_handler(event:, context:)
    message = JSON.parse(event['Records'][0]['Sns']['Message'])
    puts message # debug用
    client = Aws::SSM::Client.new
    resp = client.put_parameter({
                                  name: 'oreore-image-with-eib',
                                  value: message['outputResources']['amis'][0]['image'],
                                  type: 'String',
                                  overwrite: true
                                })

    { statusCode: 200, body: JSON.generate('OK') }
end
```

CloudFormartionのテンプレートは上で掲載したものを使います。

```yaml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  AMI:
    Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
    Default: 'oreore-image-with-eib'
Resources:
  AutoScalingLaunchConfiguration:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    Properties:
      ImageId: !Ref AMI
      InstanceType: 'm5.large'
```

## 結果
EC2 Image Builder
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62601/a0e3b6bc-7708-5ada-4a50-703947cdd7b0.png)

SSM Parameter Store
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62601/83e10689-e5c8-e248-b668-884c6ff327e8.png)

CloudFormation
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62601/75e056d9-1fb7-c683-a2b3-1beec44f9564.png)

AutoScaling
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62601/fd46d80f-a9f0-f867-aad1-7f767bc08873.png)

しっかり反映されました！

# あとがき
EC2 Image BuilderからParameter Storeを通じて、動的に最新のAMIを得ることができました。
AWSのサービスは一つ新しいものが増えると、組み合わせの楽しみがどんどん広がるので目が離せません。

CloudFormationテンプレートになってさえいればCodePipelineなどで自動化もできますし、これまで抱えていた「カスタムAMIがあればもっと効率的に色々できるけど、運用するのが面倒くさいんだよな・・・」という悩みが解決できそうでとてもワクワクしています。

明日は[@murs313](https://qiita.com/murs313)さんの記事になります。お楽しみに！