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

[cloudpack あら便利カレンダー 2019](https://benri2019.cloudpack.jp/) の記事となります。[誕生秘話](https://qiita.com/shinichi-takahashi/items/cea235795f904ccea207#%E3%81%82%E3%82%89%E4%BE%BF%E5%88%A9%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E8%AA%95%E7%94%9F%E7%A7%98%E8%A9%B1) はこちら。


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` のみとしています。

```yaml: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


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

```yaml: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


```yaml: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.conf` と`hooks.conf` ファイルを用意する必要があるため、メタデータで定義しています。
`cfn-hup` ヘルパーについては下記が参考になります。

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


```yaml: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ログインするのに利用するキーペアを作成します。
既存のキーペアを利用する場合は作成不要です。

```console
# 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を取得します。

```console
# 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です。

```console
## GlobalIP
> curl ifconfig.io

xxx.xxx.xxx.xxx
```


### スタック作成

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

```console
> 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"
    }
  ]'
```


### スタック作成結果を確認

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

```console
 > 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インスタンスを作成するといくつかのログファイルが作成されますが、下記のファイル確認しておくとだいたいは把握できます。


```console
> 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敗

```console
$ 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"}}}}}}
```


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

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

```yaml: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` コマンドを実行してスタック更新します。

```console
> 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"
    }
  ]'
```

### スタック更新結果を確認

```console
> 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


```console
> 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