cloudpack あら便利カレンダー 2019の記事となります。誕生秘話はこちら。

AWS CloudFormation(CFn)でEC2インスタンスを作成するのに、AWS::CloudFormation::Initタイプを利用すると、インスタンス起動後にパッケージのインストールやファイル作成、コマンド実行を含めることができて便利そうだったのでお試ししてみました。

個人的に一番のメリットはスタック更新時にメタデータの変更も検知して再実行させることができる点です。

AWS::CloudFormation::Init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

利用するのにいくつかのドキュメントを渡り歩く必要があったので、必要最低限のテンプレートと利用方法、ポイントをまとめました。

前提

  • AWSアカウントがある
  • AWS CLIがインストール済みで利用可能
  • CFn、EC2関連の権限がある

テンプレート

EC2インスタンスを作成・管理するテンプレートとなります。

EC2インスタンスを起動するVPCやサブネットは既存のリソースを利用前提となります。
SSHアクセスする際のキーペアは事前に作成し、セキュリティグループはインスタンスとあわせて作成しています。
作成するリージョンは検証なので、us-east-1のみとしています。

cfn-template.yaml

Parameters:
  VpcId:
    Type: AWS::EC2::VPC::Id
  SubnetId:
    Type: AWS::EC2::Subnet::Id
  EC2KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName
  InstanceType:
    Type: String
    Default: t3.small
  MyInstanceSSHCidrIp:
    Type: String
    Default: '0.0.0.0/0'

Mappings:
  AWSRegionToAMI:
    us-east-1:
      HVM64: ami-0080e4c5bc078760e

Resources:
  MyInstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Join
        - " "
        - - !Ref AWS::StackName
          - "SecurityGroup"
      GroupDescription: "MyInstance SecurityGroup"
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        -
          CidrIp: "0.0.0.0/0"
          IpProtocol: "-1"
      SecurityGroupIngress:
        -
          CidrIp: !Ref MyInstanceSSHCidrIp
          Description: !Join
            - " "
            - - !Ref AWS::StackName
              - "SSH Port"
          IpProtocol: "tcp"
          FromPort: 22
          ToPort: 22
      Tags:
        -
          Key: "Name"
          Value: !Join
            - " "
            - - !Ref AWS::StackName
              - "SecurityGroup"

  MyInstance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          files:
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack = ${AWS::StackName}
                region = ${AWS::Region}
                interval = 1
              mode: "000400"
              owner: "root"
              group: "root"

            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers = post.update
                path = Resources.MyInstance.Metadata.AWS::CloudFormation::Init
                action = /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
                runas = root
              mode: "000400"
              owner: "root"
              group: "root"
          commands:
            test:
              command: "echo $STACK_NAME test"
              env:
                STACK_NAME: !Ref AWS::StackName
          services:
            sysvinit:
              cfn-hup:
                enabled: "true"
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf

    Properties:
      InstanceType: !Ref InstanceType
      KeyName: !Ref EC2KeyPairName
      ImageId: !FindInMap [ AWSRegionToAMI, !Ref "AWS::Region", HVM64 ]
      IamInstanceProfile: !Ref AWS::NoValue
      NetworkInterfaces:
        - AssociatePublicIpAddress: True
          DeviceIndex: 0
          GroupSet:
            - !Ref MyInstanceSecurityGroup
          SubnetId: !Ref SubnetId
      Tags:
        - Key: 'Name'
          Value: !Ref AWS::StackName
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            echo "start UserData"
            /opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
            echo "finish UserData"

    CreationPolicy:
      ResourceSignal:
        Timeout: PT5M

Outputs:
  ClientInstanceId:
    Value: !Ref MyInstance
  ClientPublicIp:
    Value: !GetAtt MyInstance.PublicIp

ポイント

AWS::CloudFormation::Initタイプを利用する

AWS::CloudFormation::Initタイプを利用すると、EC2インスタンスでcfn-initヘルパースクリプト用のメタデータとして取り込まれて実行されます。
cfn-initヘルパースクリプトはメタデータに応じて以下のような操作を行います。

  • CFnのメタデータの取得と解析
  • パッケージのインストール
  • ディスクへのファイルの書き込み
  • Linux/UNIXグループ・ユーザー作成
  • ソースのダウンロード・展開
  • コマンド実行
  • サービスの有効化/無効化と開始/停止

詳しくは下記が参考になります。

AWS::CloudFormation::Init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

cfn-init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-init.html

今回のテンプレートではメタデータでファイル書き込み、コマンド実行とサービス有効化する定義をしています。詳細は後ほど。

cfn-template.yaml_一部抜粋

MyInstance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          files:
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack = ${AWS::StackName}
                region = ${AWS::Region}
                interval = 1
              mode: "000400"
              owner: "root"
              group: "root"

            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              (略)
          commands:
            test:
              command: "echo $STACK_NAME test2"
              env:
                STACK_NAME: !Ref AWS::StackName
          services:
            sysvinit:
              cfn-hup:
                (略)

cfn-init ヘルパースクリプトをUserDataで実行する

AWS::CloudFormation::Initタイプを利用してメタデータを定義しただけだと、スタック作成時にcfn-initヘルパースクリプトは実行されないため、ユーザーデータを定義して実行します。
ユーザーデータについては下記が参考になります。

Linux インスタンスでの起動時のコマンドの実行 – Amazon Elastic Compute Cloud
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html

今回のテンプレートでは/opt/aws/bin/cfn-initでエラーとなった場合、リソース作成失敗となるように/opt/aws/bin/cfn-signalも実行するようにしています。/opt/aws/bin/cfn-signalを利用する場合には、CreationPolicyを定義しておく必要があります。
cfn-signalヘルパースクリプトについては下記が参考になります。

cfn-signal – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html

cfn-template.yaml_一部抜粋

MyInstance:
    (略)
    Properties:
      (略)
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            echo "start UserData"
            /opt/aws/bin/cfn-init --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
            echo "finish UserData"
    CreationPolicy:
      ResourceSignal:
        Timeout: PT5M

cfn-hup ヘルパーでメタデータ更新時に対応する

メタデータとユーザーデータの定義だけだと、メタデータを変更してスタック更新しても変更が検知されず、EC2インスタンスに反映されないため、cfn-hup ヘルパーを利用する必要があります。cfn-hupヘルパーはEC2インスタンスでデーモンとして実行が可能です。
cfn-hupデーモンを実行するためにはcfn-hup.confhooks.confファイルを用意する必要があるため、メタデータで定義しています。
cfn-hupヘルパーについては下記が参考になります。

cfn-hup – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-hup.html

cfn-template.yaml_一部抜粋

 MyInstance:
    Type: AWS::EC2::Instance
    Metadata:
      AWS::CloudFormation::Init:
        config:
          files:
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack = ${AWS::StackName}
                region = ${AWS::Region}
                interval = 1
              mode: "000400"
              owner: "root"
              group: "root"
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Sub |
                [cfn-auto-reloader-hook]
                triggers = post.update
                path = Resources.MyInstance.Metadata.AWS::CloudFormation::Init
                action = /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource MyInstance --region ${AWS::Region}
                runas = root
              mode: "000400"
              owner: "root"
              group: "root"
          commands:
            (略)
          services:
            sysvinit:
              cfn-hup:
                enabled: "true"
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf

利用方法

パラメータ値を取得

キーペア

EC2インスタンスへSSHログインするのに利用するキーペアを作成します。
既存のキーペアを利用する場合は作成不要です。

 # Create KeyPair
> aws ec2 create-key-pair \
  --key-name cfn-init-test-ec2-key \
  --query "KeyMaterial" \
  --output text > cfn-init-test-ec2-key.pem

> chmod 400 cfn-init-test-ec2-key.pem

VPC、サブネット

既存のVPC、サブネット配下にEC2インスタンスを作成する前提ですので各IDを取得します。

# VpcId
> aws ec2 describe-vpcs \
  --query "Vpcs"

[
    {
        "CidrBlock": "172.31.0.0/16",
        "DhcpOptionsId": "dopt-b06bd8c8",
        "State": "available",
        "VpcId": "vpc-xxxxxxxx",
        "OwnerId": "xxxxxxxxxxxx",
        "InstanceTenancy": "default",
        "CidrBlockAssociationSet": [
            {
                "AssociationId": "vpc-cidr-assoc-2b23e646",
                "CidrBlock": "172.31.0.0/16",
                "CidrBlockState": {
                    "State": "associated"
                }
            }
        ],
        "IsDefault": true
    },
]


# SubnetId
> aws ec2 describe-subnets \
  --filters '{"Name": "vpc-id", "Values": ["vpc-xxxxxxxx"]}' \
  --query "Subnets"

[
    {
        "AvailabilityZone": "us-east-1a",
        "AvailabilityZoneId": "use1-az2",
        "AvailableIpAddressCount": 4089,
        "CidrBlock": "172.31.80.0/20",
        "DefaultForAz": true,
        "MapPublicIpOnLaunch": true,
        "State": "available",
        "SubnetId": "subnet-xxxxxxxx",
        "VpcId": "vpc-xxxxxxxx",
        "OwnerId": "xxxxxxxxxxxx",
        "AssignIpv6AddressOnCreation": false,
        "Ipv6CidrBlockAssociationSet": [],
        "SubnetArn": "arn:aws:ec2:us-east-1:xxxxxxxxxxxx:subnet/subnet-xxxxxxxx"
    },
]

自身のグローバルIP

SSHアクセスするためのセキュリティグループに指定するIPアドレスを取得します。
ここではifconfig.ioを利用していますが他の手段でもOKです。

## GlobalIP
> curl ifconfig.io

xxx.xxx.xxx.xxx

スタック作成

aws cloudformation create-stackコマンドを利用してスタック作成します。
AWSマネジメントコンソールから作成してもOKです。

> aws cloudformation create-stack \
  --stack-name cfn-init-test \
  --template-body file://cfn-init-template.yaml \
  --capabilities CAPABILITY_IAM \
  --region us-east-1 \
  --parameters '[
    {
      "ParameterKey": "VpcId",
      "ParameterValue": "vpc-xxxxxxxx"
    },
    {
      "ParameterKey": "SubnetId",
      "ParameterValue": "subnet-xxxxxxxx"
    },
    {
      "ParameterKey": "EC2KeyPairName",
      "ParameterValue": "cfn-init-test-ec2-key"
    },
    {
      "ParameterKey": "MyInstanceSSHCidrIp",
      "ParameterValue": "xxx.xxx.xxx.xxx/32"
    }
  ]'

スタック作成結果を確認

スタック作成ができたか確認します。

 > aws cloudformation describe-stacks \
  --stack-name cfn-init-test

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-init-test/a1dc6aa0-a9e6-11e9-b171-0a515b01a4a4",
            "StackName": "cfn-init-test",
            "Parameters": [
                {
                    "ParameterKey": "MyInstanceSSHCidrIp",
                    "ParameterValue": "xxx.xxx.xxx.xxx/32"
                },
                {
                    "ParameterKey": "VpcId",
                    "ParameterValue": "vpc-xxxxxxxx"
                },
                {
                    "ParameterKey": "EC2KeyPairName",
                    "ParameterValue": "cfn-init-test-ec2-key"
                },
                {
                    "ParameterKey": "SubnetId",
                    "ParameterValue": "subnet-xxxxxxxx"
                },
                {
                    "ParameterKey": "InstanceType",
                    "ParameterValue": "t3.small"
                }
            ],
            "CreationTime": "2019-07-19T05:32:43.477Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "ClientPublicIp",
                    "OutputValue": "xxx.xxx.xxx.xxx"
                },
                {
                    "OutputKey": "ClientInstanceId",
                    "OutputValue": "i-xxxxxxxxxxxxxxxxx"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

EC2インスタンスで確認

テンプレートのOutputsでEC2インスタンスのIPアドレス(‘ClientPublicIp’ )が出力されるようにしているので、SSHログインして各ログを確認します。
CFnでEC2インスタンスを作成するといくつかのログファイルが作成されますが、下記のファイル確認しておくとだいたいは把握できます。

 > ssh -i cfn-init-test-ec2-key.pem ec2-user@xxx.xxx.xxx.xxx

$ cat /var/log/cfn-init.log

2019-07-19 06:53:00,454 [INFO] -----------------------Starting build-----------------------
2019-07-19 06:53:00,455 [INFO] Running configSets: default
2019-07-19 06:53:00,455 [INFO] Running configSet default
2019-07-19 06:53:00,456 [INFO] Running config config
2019-07-19 06:53:00,460 [INFO] Command test succeeded
2019-07-19 06:53:00,467 [INFO] enabled service cfn-hup
2019-07-19 06:53:00,768 [INFO] Restarted cfn-hup successfully
2019-07-19 06:53:00,769 [INFO] ConfigSets completed
2019-07-19 06:53:00,769 [INFO] -----------------------Build complete-----------------------
2019-07-19 06:53:01,055 [DEBUG] CloudFormation client initialized with endpoint https://cloudformation.us-east-1.amazonaws.com
2019-07-19 06:53:01,055 [DEBUG] Signaling resource MyInstance in stack cfn-init-test with unique ID i-xxxxxxxxxxxxxxxxx and status SUCCESS


$ cat /var/log/cfn-init-cmd.log

2019-07-19 06:53:00,455 P2208 [INFO] ************************************************************
2019-07-19 06:53:00,455 P2208 [INFO] ConfigSet default
2019-07-19 06:53:00,456 P2208 [INFO] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2019-07-19 06:53:00,456 P2208 [INFO] Config config
2019-07-19 06:53:00,457 P2208 [INFO] ============================================================
2019-07-19 06:53:00,457 P2208 [INFO] Command test
2019-07-19 06:53:00,460 P2208 [INFO] -----------------------Command Output-----------------------
2019-07-19 06:53:00,460 P2208 [INFO]    cfn-init-test test
2019-07-19 06:53:00,460 P2208 [INFO] ------------------------------------------------------------
2019-07-19 06:53:00,460 P2208 [INFO] Completed successfully.


$ cat /var/log/cloud-init-output.log

(略)
Updated:
  java-1.7.0-openjdk.x86_64 1:1.7.0.211-2.6.17.1.79.amzn1
  kernel-tools.x86_64 0:4.14.128-87.105.amzn1
  perl.x86_64 4:5.16.3-294.43.amzn1
  perl-Pod-Escapes.noarch 1:1.04-294.43.amzn1
  perl-libs.x86_64 4:5.16.3-294.43.amzn1
  perl-macros.x86_64 4:5.16.3-294.43.amzn1
  python27-jinja2.noarch 0:2.7.2-3.16.amzn1
  wget.x86_64 0:1.18-5.30.amzn1

Complete!
Cloud-init v. 0.7.6 running 'modules:final' at Fri, 19 Jul 2019 05:33:29 +0000. Up 19.60 seconds.
start UserData
finish UserData
Cloud-init v. 0.7.6 finished at Fri, 19 Jul 2019 05:33:31 +0000. Datasource DataSourceEc2.  Up 20.79 seconds

スタック更新に関わるログとファイルは以下となります。
cfn-hupデーモンが定期的にCFnスタックのメタデータを取得しmetadata_db.jsonに保存、メタデータとファイルに差分があるとcfn-auto-reloader.confで指定したアクション(コマンド)が実行される仕組みになっています。
なので、スタック作成後、cfn-hupデーモンが1度もメタデータ取得していないタイミングでスタック更新しても、metadata_db.jsonが存在していないと変更が検知されないみたいです。(1敗

$ cat /var/log/cfn-hup.log

2019-07-19 06:53:00,750 [DEBUG] CloudFormation client initialized with endpoint https://cloudformation.us-east-1.amazonaws.com
2019-07-19 06:53:00,750 [DEBUG] Creating /var/lib/cfn-hup/data
2019-07-19 06:53:00,756 [INFO] No umask value specified in config file. Using the default one: 022


$ sudo cat /var/lib/cfn-hup/data/metadata_db.json

{"cfn-auto-reloader-hook|Resources.MyInstance.Metadata.AWS::CloudFormation::Init": {"config": {"files": {"/etc/cfn/cfn-hup.conf": {"owner": "root", "content": "[main]\nstack = cfn-init-test\nregion = us-east-1\ninterval = 1\n", "group": "root", "mode": "000400"}, "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {"owner": "root", "content": "[cfn-auto-reloader-hook]\ntriggers = post.update\npath = Resources.MyInstance.Metadata.AWS::CloudFormation::Init\naction = /opt/aws/bin/cfn-init -v --stack cfn-init-test --resource MyInstance --region us-east-1\nrunas = root\n", "group": "root", "mode": "000400"}}, "services": {"sysvinit": {"cfn-hup": {"files": ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"], "enabled": "true"}}}, "commands": {"test": {"command": "echo $STACK_NAME test", "env": {"STACK_NAME": "cfn-init-test"}}}}}}

メタデータを編集してスタック更新する

メタデータに実行するコマンドを追加してスタック更新してみます。

cfn-template.yaml_一部抜粋

(略)
          commands:
            test:
              command: "echo $STACK_NAME test"
              env:
                STACK_NAME: !Ref AWS::StackName
            test2:
              command: "echo $STACK_NAME test2"
              env:
                STACK_NAME: !Ref AWS::StackName
(略)

aws cloudformation update-stackコマンドを実行してスタック更新します。

> aws cloudformation update-stack \
  --stack-name cfn-init-test \
  --template-body file://cfn-init-template.yaml \
  --capabilities CAPABILITY_IAM \
  --region us-east-1 \
  --parameters '[
    {
      "ParameterKey": "VpcId",
      "ParameterValue": "vpc-xxxxxxxx"
    },
    {
      "ParameterKey": "SubnetId",
      "ParameterValue": "subnet-xxxxxxxx"
    },
    {
      "ParameterKey": "EC2KeyPairName",
      "ParameterValue": "cfn-init-test-ec2-key"
    },
    {
      "ParameterKey": "MyInstanceSSHCidrIp",
      "ParameterValue": "xxx.xxx.xxx.xxx/32"
    }
  ]'

スタック更新結果を確認

> aws cloudformation describe-stacks \
  --stack-name cfn-init-test

{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-east-1:xxxxxxxxxxxx:stack/cfn-init-test/a1dc6aa0-a9e6-11e9-b171-0a515b01a4a4",
            "StackName": "cfn-init-test",
            "Parameters": [
                {
                    "ParameterKey": "MyInstanceSSHCidrIp",
                    "ParameterValue": "xxx.xxx.xxx.xxx/32"
                },
                {
                    "ParameterKey": "VpcId",
                    "ParameterValue": "vpc-xxxxxxxx"
                },
                {
                    "ParameterKey": "EC2KeyPairName",
                    "ParameterValue": "cfn-init-test-ec2-key"
                },
                {
                    "ParameterKey": "SubnetId",
                    "ParameterValue": "subnet-xxxxxxxx"
                },
                {
                    "ParameterKey": "InstanceType",
                    "ParameterValue": "t3.small"
                }
            ],
            "CreationTime": "2019-07-19T05:32:43.477Z",
            "LastUpdatedTime": "2019-07-19T05:41:40.446Z",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM"
            ],
            "Outputs": [
                {
                    "OutputKey": "ClientPublicIp",
                    "OutputValue": "xxx.xxx.xxx.xxx"
                },
                {
                    "OutputKey": "ClientInstanceId",
                    "OutputValue": "i-xxxxxxxxxxxxxxxxx"
                }
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

EC2インスタンスで確認

CFnのリソース更新されてもEC2インスタンス側への変更反映は、cfn-hupヘルパーが担当しますので、反映にはタイムラグが発生します。cfn-hupヘルパーがメタデータの変更確認する間隔は初期設定で15分となっており、/etc/cfn/cfn-hup.conf ファイルのintervalで指定ができます。(今回は1分で指定)

cfn-hup – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-hup.html

> ssh -i cfn-init-test-ec2-key.pem ec2-user@xxx.xxx.xxx.xxx


$ cat /var/log/cfn-init.log

2019-07-19 06:53:00,454 [INFO] -----------------------Starting build-----------------------
2019-07-19 06:53:00,455 [INFO] Running configSets: default
2019-07-19 06:53:00,455 [INFO] Running configSet default
2019-07-19 06:53:00,456 [INFO] Running config config
2019-07-19 06:53:00,460 [INFO] Command test succeeded
2019-07-19 06:53:00,467 [INFO] enabled service cfn-hup
2019-07-19 06:53:00,768 [INFO] Restarted cfn-hup successfully
2019-07-19 06:53:00,769 [INFO] ConfigSets completed
2019-07-19 06:53:00,769 [INFO] -----------------------Build complete-----------------------
2019-07-19 06:53:01,055 [DEBUG] CloudFormation client initialized with endpoint https://cloudformation.us-east-1.amazonaws.com
2019-07-19 06:53:01,055 [DEBUG] Signaling resource MyInstance in stack cfn-init-test with unique ID i-xxxxxxxxxxxxxxxxx and status SUCCESS
2019-07-19 06:56:01,457 [DEBUG] CloudFormation client initialized with endpoint https://cloudformation.us-east-1.amazonaws.com
2019-07-19 06:56:01,458 [DEBUG] Describing resource MyInstance in stack cfn-init-test
2019-07-19 06:56:01,522 [INFO] -----------------------Starting build-----------------------
2019-07-19 06:56:01,523 [DEBUG] Not setting a reboot trigger as scheduling support is not available
2019-07-19 06:56:01,524 [INFO] Running configSets: default
2019-07-19 06:56:01,525 [INFO] Running configSet default
2019-07-19 06:56:01,525 [INFO] Running config config
2019-07-19 06:56:01,526 [DEBUG] No packages specified
2019-07-19 06:56:01,526 [DEBUG] No groups specified
2019-07-19 06:56:01,526 [DEBUG] No users specified
2019-07-19 06:56:01,526 [DEBUG] No sources specified
2019-07-19 06:56:01,526 [DEBUG] /etc/cfn/cfn-hup.conf already exists
2019-07-19 06:56:01,526 [DEBUG] Moving /etc/cfn/cfn-hup.conf to /etc/cfn/cfn-hup.conf.bak
2019-07-19 06:56:01,526 [DEBUG] Writing content to /etc/cfn/cfn-hup.conf
2019-07-19 06:56:01,526 [DEBUG] Setting mode for /etc/cfn/cfn-hup.conf to 000400
2019-07-19 06:56:01,526 [DEBUG] Setting owner 0 and group 0 for /etc/cfn/cfn-hup.conf
2019-07-19 06:56:01,527 [DEBUG] /etc/cfn/hooks.d/cfn-auto-reloader.conf already exists
2019-07-19 06:56:01,527 [DEBUG] Moving /etc/cfn/hooks.d/cfn-auto-reloader.conf to /etc/cfn/hooks.d/cfn-auto-reloader.conf.bak
2019-07-19 06:56:01,527 [DEBUG] Writing content to /etc/cfn/hooks.d/cfn-auto-reloader.conf
2019-07-19 06:56:01,527 [DEBUG] Setting mode for /etc/cfn/hooks.d/cfn-auto-reloader.conf to 000400
2019-07-19 06:56:01,527 [DEBUG] Setting owner 0 and group 0 for /etc/cfn/hooks.d/cfn-auto-reloader.conf
2019-07-19 06:56:01,527 [DEBUG] Running command test
2019-07-19 06:56:01,527 [DEBUG] No test for command test
2019-07-19 06:56:01,531 [INFO] Command test succeeded
2019-07-19 06:56:01,531 [DEBUG] Command test output: cfn-init-test test

2019-07-19 06:56:01,531 [DEBUG] Running command test2
2019-07-19 06:56:01,531 [DEBUG] No test for command test2
2019-07-19 06:56:01,535 [INFO] Command test2 succeeded
2019-07-19 06:56:01,536 [DEBUG] Command test2 output: cfn-init-test test!

2019-07-19 06:56:01,536 [DEBUG] Using service modifier: /sbin/chkconfig
2019-07-19 06:56:01,536 [DEBUG] Setting service cfn-hup to enabled
2019-07-19 06:56:01,538 [INFO] enabled service cfn-hup
2019-07-19 06:56:01,539 [DEBUG] Not modifying running state of service cfn-hup
2019-07-19 06:56:01,539 [INFO] ConfigSets completed
2019-07-19 06:56:01,539 [DEBUG] Not clearing reboot trigger as scheduling support is not available
2019-07-19 06:56:01,539 [INFO] -----------------------Build complete-----------------------



$ cat /var/log/cfn-init-cmd.log

2019-07-19 06:53:00,455 P2208 [INFO] ************************************************************
2019-07-19 06:53:00,455 P2208 [INFO] ConfigSet default
2019-07-19 06:53:00,456 P2208 [INFO] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2019-07-19 06:53:00,456 P2208 [INFO] Config config
2019-07-19 06:53:00,457 P2208 [INFO] ============================================================
2019-07-19 06:53:00,457 P2208 [INFO] Command test
2019-07-19 06:53:00,460 P2208 [INFO] -----------------------Command Output-----------------------
2019-07-19 06:53:00,460 P2208 [INFO]    cfn-init-test test
2019-07-19 06:53:00,460 P2208 [INFO] ------------------------------------------------------------
2019-07-19 06:53:00,460 P2208 [INFO] Completed successfully.
2019-07-19 06:56:01,525 P2337 [INFO] ************************************************************
2019-07-19 06:56:01,525 P2337 [INFO] ConfigSet default
2019-07-19 06:56:01,525 P2337 [INFO] ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2019-07-19 06:56:01,526 P2337 [INFO] Config config
2019-07-19 06:56:01,527 P2337 [INFO] ============================================================
2019-07-19 06:56:01,527 P2337 [INFO] Command test
2019-07-19 06:56:01,530 P2337 [INFO] -----------------------Command Output-----------------------
2019-07-19 06:56:01,531 P2337 [INFO]    cfn-init-test test
2019-07-19 06:56:01,531 P2337 [INFO] ------------------------------------------------------------
2019-07-19 06:56:01,531 P2337 [INFO] Completed successfully.
2019-07-19 06:56:01,531 P2337 [INFO] ============================================================
2019-07-19 06:56:01,531 P2337 [INFO] Command test2
2019-07-19 06:56:01,535 P2337 [INFO] -----------------------Command Output-----------------------
2019-07-19 06:56:01,535 P2337 [INFO]    cfn-init-test test!
2019-07-19 06:56:01,535 P2337 [INFO] ------------------------------------------------------------
2019-07-19 06:56:01,535 P2337 [INFO] Completed successfully.



$ cat /var/log/cloud-init-output.log

(略)
Updated:
  java-1.7.0-openjdk.x86_64 1:1.7.0.211-2.6.17.1.79.amzn1
  kernel-tools.x86_64 0:4.14.128-87.105.amzn1
  perl.x86_64 4:5.16.3-294.43.amzn1
  perl-Pod-Escapes.noarch 1:1.04-294.43.amzn1
  perl-libs.x86_64 4:5.16.3-294.43.amzn1
  perl-macros.x86_64 4:5.16.3-294.43.amzn1
  python27-jinja2.noarch 0:2.7.2-3.16.amzn1
  wget.x86_64 0:1.18-5.30.amzn1

Complete!
Cloud-init v. 0.7.6 running 'modules:final' at Fri, 19 Jul 2019 06:52:59 +0000. Up 18.81 seconds.
start UserData
finish UserData
Cloud-init v. 0.7.6 finished at Fri, 19 Jul 2019 06:53:01 +0000. Datasource DataSourceEc2.  Up 20.07 seconds

メタデータに追加したコマンドが実行されるのを確認できました。
やったぜ。

スタック更新時にメタデータで定義しているすべての処理を実行したくない場合、Configsetを用いるとグルーピングや実行順の制御をして特定の処理ができます。詳細は下記をご参考ください。

AWS::CloudFormation::Init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

まとめ

CFnでEC2インスタンスを作成する際に、インスタンス起動後の処理もテンプレートで定義して管理できる仕組みになっていますので、非常に使い勝手が良さそうです。

参考

AWS::CloudFormation::Init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

AWS::CloudFormation::Init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html

cfn-init – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-init.html

Linux インスタンスでの起動時のコマンドの実行 – Amazon Elastic Compute Cloud
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/user-data.html

cfn-signal – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html

cfn-hup – AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-hup.html

元記事はこちら

AWS::CloudFormation::Init タイプを使ってEC2インスタンスの環境構築ができるようにしてみた