【マルチアカウント編】EC2 Image Builder で作られたイメージ(AMI)を自動的に SSMパラメータストアに格納する
はじめに
EC2 Image Builder(以下 Image Builder)のパイプラインから EC2イメージ(AMI)を作成したときに、 そのAMI IDを Systems Manager(SSM)パラメータストアに自動的に格納するような構成を作っていきます。
前回は 1アカウント内で完結するケースを書きました。
今回は「マルチアカウント編」ということで、 イメージ配布(共有)先アカウントの SSMパラメータに格納する 構成を作ってみます。
図で表すと以下のとおりです。
- Image Builderパイプライン で AMI作成・配布時に SNSトピックを通知するように設定しておきます
- このSNSトピック通知をトリガーに Lambda関数を実行します
- このLambda関数で「各アカウントのSSMパラメータストア」に AMI IDの情報を登録( PutParameter )します
構成
大枠は 前回の1アカウント編のブログ と変わりません。 共通部分は簡単に説明します。
EC2 Image Builder
パイプラインの インフラストラクチャ設定 > SNS 部分を設定しておきます。
ディストリビューション設定 部分でイメージ共有の設定を行います。
このSNS設定/ディストリビューション設定により、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", (略) "distributionConfiguration": { "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-xxxx", "name": "sample-al2-ami-xxxx", "dateCreated": "Mar 5, 2021 1:14:50 AM", "dateUpdated": "Mar 9, 2021 2:56:29 AM", "distributions": [ { "region": "ap-northeast-1", "amiDistributionConfiguration": { "launchPermission": { "userIds": [ "111111111111", "222222222222" ] } } } ], "tags": { "internalId": "6520c90f-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-xxxx" }, "accountId": "123456789012" }, (略) "outputResources": { "amis": [ { "region": "ap-northeast-1", "image": "ami-01xxxxxxxxxxxxxxx", "name": "sample-recipe 2021-03-05T01-16-05.489Z", "accountId": "123456789012" } ] }, (略) }
ハイライト部分 distributionConfiguration
が配布設定です。後述の Lambda 関数で利用します。
IAMロール
上記のように IAMロールを 2種類 作成する必要があります。
- ロールA
- Lambdaの実行アカウントに作成する
AWSLambdaBasicExecutionRole
(AWS管理ポリシー) を付与- ロールBに
AssumeRole
を行う権限を付与
- ロールB
- 配布先アカウントにそれぞれ作成する
- ロールAが
AssumeRole
できるようする(信頼ポリシー) - SSMパラメータを作成/更新する権限を付与
今回のブログではロールBの名前を LambdaExecutionRoleForSSMParam
としています。
Lambda 関数
ランタイム: Python3.7
で作成しています。
以下のようなコードを作成しました。
import json import boto3 from boto3.session import Session import logging logger = logging.getLogger() logger.setLevel(logging.INFO) REGION = "ap-northeast-1" ROLE_NAME = "LambdaExecutionRoleForSSMParam" sts = boto3.client('sts') 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] dists = message['distributionConfiguration']['distributions'] logger.info("recipe_name={}".format(recipe_name)) logger.info("ami={}".format(ami)) logger.info("dists={}".format(dists)) # 配布先アカウントの SSMパラメータに登録 for dist in filter(lambda d: d.get('region') == REGION, dists): for user_id in dist['amiDistributionConfiguration']['launchPermission']['userIds']: logger.info( "executing ssm:PutParameters to user:{}".format(user_id)) put_param(recipe_name, ami, user_id) def put_param(recipe_name, ami, user_id): # 一時情報の取得 remote = sts.assume_role( RoleArn="arn:aws:iam::{}:role/{}".format(user_id, ROLE_NAME), RoleSessionName="imagebuilder_master" ) ssm = boto3.client( 'ssm', aws_access_key_id=remote['Credentials']['AccessKeyId'], aws_secret_access_key=remote['Credentials']['SecretAccessKey'], aws_session_token=remote['Credentials']['SessionToken'] ) # 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( "[put_param] ssm.put_parameter response: {}".format(response))
受け取った SNSメッセージをのステータス、AMI ID、レシピ名、配布情報を取得して、
配布先アカウントそれぞれに
SSMパラメータ ( "/ec2-imagebuilder/latest/{レシピ名}"
) に登録する処理を行います。
▼ ほか設定: SNS
Image Builder のインフラストラクチャ設定で指定したSNSトピックをトリガーとします。
▼ ほか設定: IAMロール
前述の ロールA を付与します。
AssumeRole
部分のポリシーは以下のようになります。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Resource": "arn:aws:iam::*:role/LambdaExecutionRoleForSSMParam", "Action": "sts:AssumeRole", } ] }
構築
(事前準備) 配布先アカウントのIAMロール
前述の ロールB (LambdaExecutionRoleForSSMParam
) を配布先アカウントそれぞれに作成します。
以下 CloudFormation(CFn)テンプレートを作成しました。
AWSTemplateFormatVersion: '2010-09-09' Parameters: MainAccountId: Description: "Main AWS account ID to run Lambda function" Type: String Resources: Role: Type: AWS::IAM::Role Properties: RoleName: LambdaExecutionRoleForSSMParam AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "sts:AssumeRole" Principal: "AWS": !Sub "arn:aws:iam::${MainAccountId}:root" Policies: - PolicyName: policy-for-ssm-action PolicyDocument: Version: "2012-10-17" Statement: - Sid: SSMPutParameter Effect: "Allow" Action: "ssm:PutParameter" Resource: "*"
個別に展開、もしくは CFn StackSets 展開しておきます。
SAMで構築
SNSトピックとLambda関数(+ロールA)の部分 を AWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築しました。 SAMのプロジェクト構成内容を記します。
▼ プロジェクト
sam init
で新規プロジェクトを作成します。
sam init --runtime python3.7 --name tracking-latest-images-in-imagebuilder-cross-account
▼ 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: - Sid: STSAssumeRole Effect: "Allow" Action: "sts:AssumeRole" Resource: "arn:aws:iam::*:role/LambdaExecutionRoleForSSMParam"
▼ scripts/app.py
前述のLambda関数のコードを scripts/app.py
に格納します。
▼ ビルド、デプロイ
sam build sam deploy --guided
特にパラメータ指定していないので、ガイド通りに YES 選択でデプロイできます。
確認
事前に ImageBuilderのパイプラインの インフラストラクチャ設定 > SNS 部分 に、 作成した SNSトピックを指定しておきます。
パイプライン実行
Image Builder のパイプラインを実行します。 [使用可能] になるまで待ちます。
配布先アカウントの確認
まず、EC2の AMI画面を見てみます。Image Builder からのAMI共有を確認できました。
次に配布先のアカウントの SSMパラメータを見てみます。
AMI ID が登録されていること確認できました。
おわりに
EC2 Image Builder で作った最新AMIを「配布先アカウントの SSMパラメータストア」に 自動登録する仕組みを作ってみました。
マルチアカウント環境では AMIを他アカウントに共有する機会が多いと思います。 配布されたAMI および作成されたSSMパラメータを使って、 EC2インスタンスを作成する際の AMI ID 参照などに活用できると思います。
参考
- AWS
- DevelopersIO