CFNを使用して、SSM Automationランブックを作成してAMIを別アカウントと共有する
AWS SOAの出題範囲の1つである、デプロイ、プロビジョニング、およびオートメーションに関する内容です。
作成したAMIは別アカウントと共有することができます。
AWS公式ページで2つの方法が紹介されています。
- AWSマネージメントコンソール
- AWS CLI
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/sharingamis-explicit.html
今回はカスタムSSM Automationランブックを作成して、上記と同様の働きを実現します。
構築する環境
プライベートサブネット内にEC2インスタンスを作成します。
インスタンスは最新のAmazon Linux 2とします。
SSM Automationランブックを作成します。
このランブックの内容は以下の通りです。
- インスタンスのAMIを作成する。
- 作成したAMIを別アカウントと共有する。
このランブックとインスタンスの関連付けも作成します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-soa/tree/main/03/005
テンプレートファイルのポイント解説
Automationランブック
Resources:
CreateAndShareAmiRunbook:
Type: AWS::SSM::Document
Properties:
Content:
assumeRole: "{{AutomationAssumeRole}}"
description: Create and Share AMI with another account.
schemaVersion: "0.3"
parameters:
AccountId:
type: String
description: (Required) The Account ID you want to share AMI.
default: ""
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
default: ""
InstanceId:
type: String
description: (Required) The instance ID you want to run commands on.
default: ""
mainSteps:
- name: createImage
action: aws:executeAutomation
maxAttempts: 1
timeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
onFailure: Abort
inputs:
DocumentName: AWS-CreateImage
RuntimeParameters:
AutomationAssumeRole:
- "{{AutomationAssumeRole}}"
InstanceId:
- "{{InstanceId}}"
outputs:
- Name: AmiStringList
Selector: "$"
Type: String
- name: convertStringListToString
action: "aws:executeScript"
inputs:
Runtime: python3.8
Handler: handler
InputPayload:
StringList:
- "{{createImage.Output}}"
Script: |-
def handler(events, context):
list = events['StringList']
return {'First': list[0]}
outputs:
- Name: Ami
Selector: $.Payload.First
Type: String
- name: shareAmiWithAnotherAccount
action: aws:executeAwsApi
maxAttempts: 1
onFailure: Abort
inputs:
Service: ec2
Api: modify-image-attribute
ImageId: "{{convertStringListToString.Ami}}"
LaunchPermission:
Add:
- UserId: "{{AccountId}}"
DocumentFormat: YAML
DocumentType: Automation
Name: !Sub "${Prefix}-CreateAndShareAmiRunbook"
TargetType: /AWS::EC2::Instance
Code language: YAML (yaml)
カスタムランブックを作成します。
ランブックの作成については、以下のページをご確認ください。
Contentプロパティでランブックの中身を定義します。
parametersデータエレメントで、このランブックを実行するためのパラメータを指定します。
今回は以下の3つのパラメータを受け取ります。
- AccountId:AMIを共有するアカウントのID
- AutomationAssumeRole:Autometionがこのランブックを実行するために使用するサービスロールのARN
- InstanceId:AMIを作成するインスタンスのID
mainStepsデータエレメントにランブックで実行する処理を記載します。
このブックは3つのステップで構成されています。
1ステップ目
1つ目のステップはインスタンスからAMIを作成します。
AMIの作成はSSM AutomationランブックAWS-CreateImageを使用します。
Automation内で別のAutomationランブックを実行することになりますが、これはaws:executeAutomationアクションを実行することで実現できます。
inputsにAWS-CreateImageを実行するために必要なパラメータを指定します。
今回はSSM用サービスロールと、AMIを作成するインスタンスのIDを指定します。
outputsでAWS-CreateImageを実行後に返されるデータを指定します。
このランブックを実行後は、作成されたAMIのIDが返されるため、これをそのまま受け取ります。
2ステップ目
2つ目のステップは1ステップ目で受け取ったデータの型を変換します。
aws:executeAutomationアクションを実行後に返されるデータの型はStringListです。
出力
セカンダリオートメーションによって生成される出力。この出力は、次の形式を使って参照できます: セカンダリ自動化ステップ名.Output
タイプ: StringList
aws:executeAutomation – 別のオートメーションを実行する
つまりAWS-CreateImageを実行後に返されるデータは「[‘ami-abcdefg’]」という形となり、AMI IDを文字列として受け取れないということになります。
後述する3ステップでは、このAMI IDを使って処理を行いますが、求めるデータ型はStringです。
そこで以下のページを参考にして、StringList型をString型にキャストします。
https://qiita.com/r18j21/items/6aae052e67455cce2e4e
キャストする方法はaws:executeScriptアクションでPythonスクリプトを実行するものです。
このスクリプトの内容は、1ステップ目で出力されたデータを参照して、1つ目のデータを返すというものです。
これでStringList型だったデータがString型に変換されます。
3ステップ目
3つ目のステップはAMIを別アカウントと共有します。
aws:executeAwsApiアクションという形で、AWS APIを実行してAMIを共有します。
AMIの共有はEC2サービスのmodify-image-attribute メソッドで実行できます。
このAPIを実行するために、2つのパラメータを設定します。
1つ目はImageIdです。これは2ステップでString型に変換したAMI IDを指定します。
2つ目はLaunchPermissionです。これにAMIを共有するアカウントを指定します。
Automation用サービスロール
Resources:
CreateAndShareAmiRunbookRole:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- ssm.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
Policies:
- PolicyName: CopyAndShareAmiWithAnotherAccountPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ec2:CreateImage
- ec2:DescribeInstances
- ec2:ModifyImageAttribute
- iam:PassRole
Resource:
- "*"
Code language: YAML (yaml)
通常、サービスロールを指定しない場合は、デフォルトのサービスリンクロール(SLR)であるAWSServiceRoleforAmazonSSMが使用されます。
今回は以下のAWS公式ページに従って、サービスロールを作成します。
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-permissions.html
さらにAMIの作成および共有に必要な権限を、インラインポリシーとして付与します。
SSM関連付け
Resources:
CreateAndShareAmiRunbookAssociation:
Type: AWS::SSM::Association
Properties:
AssociationName: !Sub "${Prefix}-CreateAndShareAmiRunbookAssociation"
AutomationTargetParameterName: InstanceId
Name: !Ref CreateAndShareAmiRunbook
Parameters:
AccountId:
- !Ref AccountId
AutomationAssumeRole:
- !GetAtt CreateAndShareAmiRunbookRole.Arn
InstanceId:
- "{{RESOURCE_ID}}"
Targets:
- Key: !Sub "tag:${TagKey}"
Values:
- !Ref TagValue
WaitForSuccessTimeoutSeconds: !Ref WaitForSuccessTimeoutSeconds
Code language: YAML (yaml)
上述のランブックとインスタンスの関連付けを作成します。
SSM関連付けを作成して、EC2インスタンスに対してSSM Automationランブックを実行するため方法については、以下のページをご確認ください。
今回は以下のタグが設定されているインスタンスを、関連付けの対象とします。
- タグのキー:MyDocument
- タグの値:Group1
上記の条件でインスタンスにタグ設定を行い、関連付けの対象にします。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- EC2インスタンス:i-0f5ba431de00d32a6
- – カスタムSSM Automationランブック:soa-03-005-CreateAndShareAmiRunbook
- – SSM関連付け:da0a89d3-bb77-480b-afda-c15d5f8fffec
動作確認
準備が整いましたので、AWS Management Consoleから各リソースを確認します。
カスタムSSM Automationランブック
作成したランブックを確認します。
ランブックが正常に作成されていることがわかります。
このランブックには3つのパラメータが存在し、EC2インスタンスのAMIを作成後、別アカウントと共有する内容です。
SSM関連付け
関連付けを確認します。
先述のランブックに対する関連付けが作成されていることがわかります。
ランブックを実行するためのパラメータです。
AccountIdで指定したアカウントと、ランブックで作成するAMIを共有します。
ターゲットを見ると、タグ名MyDocumentの値が「Group1」であるインスタンスが対象であることがわかります。
ランブックの実行履歴を確認します。
正常に実行が完了しています。
実行された各ステップの詳細ログを確認します。
1つ目のステップを確認します。
SSM用サービスロールとインスタンスIDをパラメータとしてAWS-CreateImageランブックが実行されました。
Outputsを見ると、AMIが作成されたことがわかります。
今回作成されたAMIのIDは「ami-06b79a64a1a2b1447」です。
2つ目のステップを確認します。
Pythonスクリプトが実行されました。
Input parametersを見ると、先述のAMI IDがStringList型で格納されています。
そしてOutputsを見ると、これをString型で出力しています。
3つ目のステップを確認します。
modify-image-attributeメソッドが実行され、別アカウントにAMIが共有されました。
最後に作成したAMIの詳細を確認します。
Shared accountsの項目を見ると、確かに別アカウントのIDが表示されています。
つまりカスタムSSM Automationランブックを作成し、これをEC2インスタンスに関連づけることによって、このインスタンスのAMIが作成した上で、AMIが別アカウントと共有されたということです。
まとめ
カスタムSSM Automationランブックを使用して、AMIを別アカウントと共有する方法を確認しました。