EC2 Image Builder で作られたイメージ(AMI)を自動的に SSMパラメータストアに格納する

2021.03.09

はじめに

EC2 Image Builder(以下 Image Builder)のパイプラインから EC2イメージ(AMI)を作成したときに、 そのAMI IDを Systems Manager(SSM)パラメータストアに自動的に格納するような構成を試してみます。

図で表すと以下のとおりです。

img

  1. Image Builderパイプライン で AMI作成時に SNSトピックを通知するように設定しておきます
  2. このSNSトピック通知をトリガーに Lambda関数を実行します
  3. このLambda関数で SSMパラメータストアに AMI IDの情報を登録( PutParameter )します

なお、今回試した構成は以下 AWSブログを参考にしています。

構成

EC2 Image Builder

パイプラインやレシピの詳細説明は割愛します。

パイプラインの インフラストラクチャ設定 > SNS 部分を設定しておきます。

img

SNS設定により、ImageBuilder で AMI作成が完了したときに、以下のようなメッセージを通知することができます。 (※メッセージサンプル全文はブログの最後に記載)

{
  "versionlessArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe",
  "semver": 1073741827,
  "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3",
  "name": "sample-recipe",
  (略)
  "state": {
    "status": "AVAILABLE"
  },
  (略)
  "outputResources": {
    "amis": [
      {
        "region": "ap-northeast-1",
        "image": "ami-01xxxxxxxxxxxxxxx",
        "name": "sample-recipe 2021-03-05T01-16-05.489Z",
        "accountId": "123456789012"
      }
    ]
  },
  (略)
}

ハイライト部分(レシピ名, ステータス, AMI ID)を後述の Lambda関数で利用します。

Lambda関数

今回は ランタイム: Python3.7 で作成しています。

以下のようなコードを作成しました。

import json
import boto3
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ssm = boto3.client('ssm')


def lambda_handler(event, context):
    message = json.loads(event["Records"][0]["Sns"]["Message"])
    process_sns_message(message)


def process_sns_message(message):
    logger.info("Printing message: {}".format(message))
    # state キーが無いとき、もしくは state.status が "AVAILABLE" でないときはパラメータストアに登録しない
    if message.get('state') == None or message['state'].get('status') != "AVAILABLE":
        return None
    # レシピ名とAMI IDを取得
    recipe_name = message['name']
    ami = message['outputResources']['amis'][0]
    logger.info("recipe_name={}".format(recipe_name))
    logger.info("ami={}".format(ami))
    # SSMパラメータストアに登録
    response = ssm.put_parameter(
        Name="/ec2-imagebuilder/latest/{}".format(recipe_name),
        Description="Latest AMI ID:{}".format(recipe_name),
        Value=ami['image'],
        Type='String',
        Overwrite=True,
        Tier='Standard'
    )
    logger.info("Printing ssm.put_parameter response: {}".format(response))

受け取った SNSメッセージをのステータス、AMI ID、レシピ名を取得して、 SSMパラメータ ( "/ec2-imagebuilder/latest/{レシピ名}" ) に登録するシンプルな処理です。

▼ ほか設定: SNS

Image Builder のインフラストラクチャ設定で指定したSNSトピックをトリガーとします。

img

▼ ほか設定: IAMロール

実行ロールに必要なポリシーは以下のとおり

  • AWSLambdaBasicExecutionRole (AWS管理ポリシー)
  • SSMパラメータに値を入れるアクション( ssm:PutParameter )を許可するポリシー

後者は以下のようなポリシーです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Resource": "*",
            "Action": "ssm:PutParameter"
        }
    ]
}

SAMで構築

上記構成 (SNSトピックとLambda関数の部分)を AWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築しました。 SAMのプロジェクト構成内容を記します。

プロジェクト

sam init で新規プロジェクトを作成します。

sam init --runtime python3.7 --name tracking-latest-images-in-imagebuilder

template.yaml

必要なリソースを記述した template.yaml は以下のとおり。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "tracking the latest AMI ID in EC2 Image Builder pipeline"
Resources:
  # SNS topic
  SnsTopic:
    Type: AWS::SNS::Topic
    Properties: 
      TopicName: topic-for-imagebuilder
  # Lambda function
  Function:
    Type: AWS::Serverless::Function
    Properties:
      Description: "Update SSM Parameter with the latest AMI ID"
      CodeUri: scripts/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        EventBridgeRule:
          Type: SNS
          Properties:
            Topic: !Ref SnsTopic
      Policies:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:PutParameter
              Resource: '*'

scripts/app.py

前述のLambda関数のコードを scripts/app.py に格納します。

ビルド, デプロイ

sam build
sam deploy --guided

特にパラメータ指定していないので、ガイド通りに YES 選択でデプロイできます。

確認

1回目

まず ImageBuilderのパイプラインの インフラストラクチャ設定 > SNS 部分に、前述で作成した SNSトピックを指定します。

そのパイプラインを実行してみます。

img

[使用可能] になるまで待ちましょう。

img

[使用可能] となった段階で SSMパラメータストアを確認します。

img

先程 ImageBuilderパイプラインで作成した AMIの IDが登録されていることを確認できました。

2回目以降

再度 同じパイプラインを実行してみます。

img

値が上書きされたことを確認できました。

おわりに

EC2 Image Builder で作った最新AMIをSSMパラメータストアに自動登録する仕組みを作ってみました。 CloudFormation で EC2インスタンスを作成する際の AMI ID 参照などに活用できると思います。

参考

ImageBuilder の通知メッセージサンプル

{
  "versionlessArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe",
  "semver": 1073741827,
  "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3",
  "name": "sample-recipe",
  "version": "0.0.1",
  "type": "AMI",
  "buildVersion": 3,
  "state": {
    "status": "AVAILABLE"
  },
  "platform": "Linux",
  "imageRecipe": {
    "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image-recipe/sample-recipe/0.0.1",
    "name": "sample-recipe",
    "version": "0.0.1",
    "components": [
      {
        "componentArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:component/sample-test/0.0.1/1"
      },
      {
        "componentArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:component/sample-build/0.0.1/1"
      }
    ],
    "platform": "Linux",
    "parentImage": "arn:aws:imagebuilder:ap-northeast-1:255485124026:image/amazon-linux-2-x86/2021.2.20/2",
    "blockDeviceMappings": [
      {
        "deviceName": "/dev/xvda",
        "ebs": {
          "encrypted": false,
          "deleteOnTermination": true,
          "volumeSize": 8,
          "volumeType": "gp2"
        }
      }
    ],
    "dateCreated": "Oct 27, 2020 2:34:30 AM",
    "tags": {
      "internalId": "52b73813-4fed-4a5a-a64d-22521554d366",
      "internalId": "52xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image-recipe/sample-recipe/0.0.1"
    },
    "accountId": "123456789012"
  },
  "sourcePipelineArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image-pipeline/sample-al2-ami",
  "infrastructureConfiguration": {
    "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:infrastructure-configuration/sample-infra-conf",
    "name": "sample-infra-conf",
    "instanceTypes": [
      "t3.small"
    ],
    "instanceProfileName": "EC2InstanceProfileForImageBuilder",
    "securityGroupIds": [
      "sg-xxxxxxxxxxxxxxxxx"
    ],
    "subnetId": "subnet-xxxxxxxx",
    "tags": {
      "internalId": "30xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:infrastructure-configuration/sample-infra-conf"
    },
    "logging": {
      "s3Logs": {}
    },
    "terminateInstanceOnFailure": true,
    "snsTopicArn": "arn:aws:sns:ap-northeast-1:123456789012:topic-for-imagebuilder",
    "dateCreated": "Mar 5, 2021 1:14:49 AM",
    "accountId": "123456789012"
  },
  "imageTestsConfigurationDocument": {
    "imageTestsEnabled": true,
    "timeoutMinutes": 720
  },
  "distributionConfiguration": {
    "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-6b98ab10-8065-42e0-9954-975621d6258c",
    "name": "sample-al2-ami-6b98ab10-8065-42e0-9954-975621d6258c",
    "dateCreated": "Mar 5, 2021 1:14:50 AM",
    "distributions": [
      {
        "region": "ap-northeast-1",
        "amiDistributionConfiguration": {}
      }
    ],
    "tags": {
      "internalId": "6520c90f-86ef-4a01-9143-6fee8079d1e8",
      "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-6b98ab10-8065-42e0-9954-975621d6258c"
    },
    "accountId": "123456789012"
  },
  "dateCreated": "Mar 5, 2021 1:15:31 AM",
  "outputResources": {
    "amis": [
      {
        "region": "ap-northeast-1",
        "image": "ami-013e36332e677ed78",
        "image": "ami-01xxxxxxxxxxxxxxx",
        "name": "sample-recipe 2021-03-05T01-16-05.489Z",
        "accountId": "123456789012"
      }
    ]
  },
  "buildExecutionId": "7cba5e41-3d86-4b3e-8f01-e0f8edd36371",
  "testExecutionId": "0d103efb-0755-4465-b6f0-3af0ce93d407",
  "distributionJobId": "40e757ba-575b-4314-8e38-21a1cba8535e",
  "integrationJobId": "e8eb11b2-6a63-4aaa-ab1b-685bae925833",
  "accountId": "123456789012",
  "enhancedImageMetadataEnabled": false,
  "tags": {
    "internalId": "6041d8a9-3491-4a03-a1b6-bf92c83227a3",
    "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3"
  }
}