ImageBuilder をサポートした CloudFormation で AMI作成から EC2のオートスケール起動まで実装してみた

1つのCloudFormationテンプレートで、ImageBuilder で カーネル更新などを行ったAMI作成し、EC2のオートスケール起動までを実装してみました。
2020.06.01

AWSチームのすずきです。

ImageBuilder をサポートした CloudFormationと、AMI IDをサポートした パラメータストアを利用して、 AMI作成から、EC2のオートスケール起動まで 1つの CloudFormationのスタックでの実装を試す機会がありましたので、紹介させて頂きます。

CloudFormation

imagebuilder-install-kernelng-jq.yaml

IAM

  • AMIの雛形となる EC2環境で利用するIAMロール(インスタンスプロファイル)を設定します。
  • ImageBuilder が必須とする権限、管理ポリシー「AmazonSSMManagedInstanceCore」「EC2InstanceProfileForImageBuilder」を利用して付与します。
  • S3へのログ出力を利用するため、ログ出力先とするS3の設定をインラインポリシーで追加しています。
Ec2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/EC2InstanceProfileForImageBuilder
  Ec2RolePolicies:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: Ec2RolePolicies
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - s3:List*
          Resource:
          - '*'
        - Effect: Allow
          Action:
          - s3:*
          Resource:
            - !Sub 'arn:aws:s3:::${S3Bucketlog}'
            - !Sub 'arn:aws:s3:::${S3Bucketlog}/*'
      Roles:
      - !Ref 'Ec2IAMRole'
  Ec2IAMProfile:
    Type: AWS::IAM::InstanceProfile
    DependsOn: Ec2IAMRole
    Properties:
      Path: /
      Roles:
        - !Ref 'Ec2IAMRole'

S3

  • Image Builder のログ出力先として利用するS3バケットを作成します。
  • AMI作成時、System Manager「aws:runCommand」のログ出力先となります。
S3Bucketlog:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${AWS::StackName}-log-${AWS::Region}-${AWS::AccountId}'
      LifecycleConfiguration:
        Rules:
          - Id: AutoDelete
            Status: Enabled
            ExpirationInDays: 14

ImageBuilder

BuildComponent

  • ビルド用、テスト用、2つの設定を用意します。
  • 「jq」コマンドを Amazon Linux2のリポジトリ、 新しいLinuxカーネルを拡張リポジトリからインストールしています。

BuildComponent:
    Type: AWS::ImageBuilder::Component
    Properties:
      Data: |
        name: yum_install
        description: jq_install
        schemaVersion: 1.0
        phases:
          - name: build
            steps:
              - name: UpdateOS
                action: UpdateOS
              - name: yum_update
                action: ExecuteBash
                inputs:
                  commands:
                    - yum update -y
              - name: jq_install
                action: ExecuteBash
                inputs:
                  commands:
                    - yum install jq -y
              - name: kernel_ng_install
                action: ExecuteBash
                inputs:
                  commands:
                    - amazon-linux-extras install -y kernel-ng
          - name: validate
            steps:
              - name: jq_install
                action: ExecuteBash
                inputs:
                  commands:
                    - rpm -qi jq
      Name: !Sub '${AWS::StackName}-build'
      Platform: Linux
      Version: 1.0.0

TestComponent

  • テストは指定したコマンドの実行結果 $? の値が 0 以外である場合、異常とみなします。
  • 今回はインストールした jq コマンドが存在し、実行可能な状態である事を、バージョン情報の取得で判定する設定にしました。
TestComponent:
    Type: AWS::ImageBuilder::Component
    Properties:
      Data: |
        name: jq_test
        description: jq_test
        schemaVersion: 1.0
        phases:
          - name: test
            steps:
              - name: check_status
                action: ExecuteBash
                inputs:
                  commands:
                    - jq --version
      Name: !Sub '${AWS::StackName}-test'
      Platform: Linux
      Version: 1.0.0

ImageRecipe

  • ビルド用と、テスト用のコンポーネントを登録します。
  • 利用するAMIは、imagebuilder に登録された Amazon Linux 2の最新AMIを利用する指定 (x.x.x) としました。
ImageRecipe:
    Type: AWS::ImageBuilder::ImageRecipe
    Properties:
      Components:
        - ComponentArn: !Ref 'BuildComponent'
        - ComponentArn: !Ref 'TestComponent'
      Name: !Sub '${AWS::StackName}-Recipe'
      ParentImage: !Sub 'arn:aws:imagebuilder:${AWS::Region}:aws:image/amazon-linux-2-x86/x.x.x'
      Version: 1.0.0
  • 「ParentImage」は、SSM Agent がインストール済みの AMIを ID指定で利用する事も可能です。
  • AmazonLinux 2のARM用AMI (ami-08360a37d07f61f88: amzn2-ami-hvm-2.0.20200406.0-arm64-gp2)も利用可能でした。
Properties:
      ParentImage: ami-08360a37d07f61f88

InfrastructureConfiguration

  • AMIの雛形となるEC2インスタンスと、ログ出力先の設定を行います。
InfrastructureConfiguration:
    Type: AWS::ImageBuilder::InfrastructureConfiguration
    Properties:
      InstanceProfileName: !Ref 'Ec2IAMProfile'
      InstanceTypes:
        - t3.small
      Name: !Sub '${AWS::StackName}-InfrastructureConfiguration'
      SecurityGroupIds: []
      TerminateInstanceOnFailure: true
      Logging:
        S3Logs:
          S3BucketName: !Ref 'S3Bucketlog'
          S3KeyPrefix: !Sub '${AWS::StackName}'

Image

  • ImageRecipeを利用してAMIを作成する指定を行います。
Image:
    Type: AWS::ImageBuilder::Image
    Properties:
      ImageRecipeArn: !Ref 'ImageRecipe'
      InfrastructureConfigurationArn: !Ref 'InfrastructureConfiguration'
      ImageTestsConfiguration:
        ImageTestsEnabled: true
        TimeoutMinutes: 60

SSM::Parameter

  • !GetAtt 'Image.ImageId' で作成した AMIIDを取得し、パラメータストアに「aws:ec2:image」として登録します。
Parameter:
    Type: AWS::SSM::Parameter
    Properties:
      DataType: aws:ec2:image
      Name: !Sub '${AWS::StackName}-amiid'
      Type: String
      Value: !GetAtt 'Image.ImageId'
      Description: !Sub '${AWS::StackName}-amiid'

LaunchTemplate

  • パラメータストアに登録されたAMIIDを !Sub '{{resolve:ssm:${Parameter}:1}}' で呼び出し、EC2を起動するテンプレートを作成します。
  • インスタンスプロファイル、今回は雛形用の設定をそのまま流用する設定としました。
Ec2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateData:
        IamInstanceProfile:
          Arn: !GetAtt 'Ec2IAMProfile.Arn'
        ImageId: !Sub '{{resolve:ssm:${Parameter}:1}}' 
        InstanceType: t3.small

AutoScalingGroup

  • ImageBuilder で 作成したAMI を利用して、EC2インスタンスを1台 デフォルトVPCにスポットで起動する設定としました。
Ec2AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AvailabilityZones: !GetAZs ''
      DesiredCapacity: 1
      MinSize: 0
      MaxSize: 1
      HealthCheckType: EC2
      MixedInstancesPolicy:
        InstancesDistribution:
          OnDemandBaseCapacity: 0
          OnDemandPercentageAboveBaseCapacity: 0
        LaunchTemplate:
          LaunchTemplateSpecification:
            LaunchTemplateId: !Ref 'Ec2LaunchTemplate'
            Version: !GetAtt 'Ec2LaunchTemplate.LatestVersionNumber'
          Overrides:
            - InstanceType: t3a.micro
            - InstanceType: t3.micro

実行例

CloudFormationの実行開始から、今回の設定では約24分でAMIが作成できました。

イベント 実行時刻 経過時間
CloudFormation実行開始 15:23:32 -
AMI作成開始 15:26:08 0:02:36
AMI作成完了 15:47:42 0:24:10
EC2起動開始 15:48:03 0:24:31
EC2起動完了 15:48:37 0:25:05

更新

ビルド、テスト用のコンポーネントを更新した場合、Component、ImageRecipe の それぞれの「Version」を 1.0.0 → 1.0.1 と更新し、CloudFormation管理リソースの置換を行うことで Update StackによるAMIの更新が可能です。

Type: AWS::ImageBuilder::Component
    Properties:
      Version: 1.0.1
Type: AWS::ImageBuilder::ImageRecipe
    Properties:
      Version: 1.0.1

CloudFormationの更新後、ImageBuilder上から、古い AMIは削除されます。 作成済みのAMIと、スナップショットはそのまま残り続けますので、適宜EC2コンソールなど利用して撤去して下さい。

まとめ

ImageBuilderを用いて、セキュリティパッチの反映や、動作テストまで完了したAMIを用意する事で、 EC2の起動時間短縮や、確実な起動が期待できます。

実際の利用にあたっては、ImageBuilderと、EC2の起動設定(起動テンプレート、オートスケール)は 別のCloudFormationスタック管理が望ましい場合もあると思われますが、 開発環境などでシンプルな管理が望ましい場合には、今回紹介した方法をお試し下さい。