CFNを使用して、SSM Automationランブックを作成してAMIを別アカウントと共有する

CloudFormationを使用して、SSM Automationランブックを作成して、AMIを別アカウントと共有する

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ランブックを作成して、上記と同様の働きを実現します。

構築する環境

Diagram of creating SSM Automation runbook to share AMI with another account using CloudFormation

プライベートサブネット内にEC2インスタンスを作成します。
インスタンスは最新のAmazon Linux 2とします。

SSM Automationランブックを作成します。
このランブックの内容は以下の通りです。

  1. インスタンスのAMIを作成する。
  2. 作成した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)

カスタムランブックを作成します。
ランブックの作成については、以下のページをご確認ください。

https://awstut.com/2023/02/23/introduction-to-creating-ssm-automation-runbooks-using-cloudformation

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アクションを実行することで実現できます。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-action-executeAutomation.html

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を共有します。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/automation-action-executeAwsApi.html

AMIの共有はEC2サービスのmodify-image-attribute メソッドで実行できます。

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.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ランブックを実行するため方法については、以下のページをご確認ください。

https://awstut.com/2023/02/19/create-ami-using-ssm-automation-one-time-scheduled

今回は以下のタグが設定されているインスタンスを、関連付けの対象とします。

  • タグのキー:MyDocument
  • タグの値:Group1

上記の条件でインスタンスにタグ設定を行い、関連付けの対象にします。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

https://awstut.com/2021/12/02/cloudformation-nested-stacks

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • EC2インスタンス:i-0f5ba431de00d32a6
  • – カスタムSSM Automationランブック:soa-03-005-CreateAndShareAmiRunbook
  • – SSM関連付け:da0a89d3-bb77-480b-afda-c15d5f8fffec

動作確認

準備が整いましたので、AWS Management Consoleから各リソースを確認します。

カスタムSSM Automationランブック

作成したランブックを確認します。

Detail of SSM 1.
Detail of SSM 2.
Detail of SSM 3.

ランブックが正常に作成されていることがわかります。

このランブックには3つのパラメータが存在し、EC2インスタンスのAMIを作成後、別アカウントと共有する内容です。

SSM関連付け

関連付けを確認します。

Detail of SSM 4.

先述のランブックに対する関連付けが作成されていることがわかります。

Detail of SSM 5.

ランブックを実行するためのパラメータです。
AccountIdで指定したアカウントと、ランブックで作成するAMIを共有します。

Detail of SSM 6.

ターゲットを見ると、タグ名MyDocumentの値が「Group1」であるインスタンスが対象であることがわかります。

ランブックの実行履歴を確認します。

Detail of SSM 7.

正常に実行が完了しています。

実行された各ステップの詳細ログを確認します。

1つ目のステップを確認します。

Detail of SSM 8.

SSM用サービスロールとインスタンスIDをパラメータとしてAWS-CreateImageランブックが実行されました。
Outputsを見ると、AMIが作成されたことがわかります。
今回作成されたAMIのIDは「ami-06b79a64a1a2b1447」です。

2つ目のステップを確認します。

Detail of SSM 9.

Pythonスクリプトが実行されました。
Input parametersを見ると、先述のAMI IDがStringList型で格納されています。
そしてOutputsを見ると、これをString型で出力しています。

3つ目のステップを確認します。

Detail of SSM 10.

modify-image-attributeメソッドが実行され、別アカウントにAMIが共有されました。

最後に作成したAMIの詳細を確認します。

Detail of EC2 1.

Shared accountsの項目を見ると、確かに別アカウントのIDが表示されています。
つまりカスタムSSM Automationランブックを作成し、これをEC2インスタンスに関連づけることによって、このインスタンスのAMIが作成した上で、AMIが別アカウントと共有されたということです。

まとめ

カスタムSSM Automationランブックを使用して、AMIを別アカウントと共有する方法を確認しました。