AWS CloudFormationでEC2にミドルまでセットアップする

# AWS CloudFormationでEC2にミドルまでセットアップする

CloudFormationでインフラのコード化だと言いつつ、EC2立てた後のミドルのセットアップはAnsibleあたりが必要なのだろうか、とかアプリのデプロイとはスクリプト分けたいよねとか、モヤモヤしてたらCloudFormationで出来るようなので試してみました。

## 参考

だいたい公式のチュートリアル通りでOK。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/deploying.applications.html

## UserData プロパティ

ここにEC2のプロビジョニングで実行できるシェルスクリプトが書けます。
極論これだけで全部賄うことも可能ですが、UpdateStackへの対応や、メンテやデバッグがしんどくなるので、cfn-init ヘルパースクリプトを使って実行内容をメタデータに記載する方法をとります。

```yaml
Resources:
  WebServerInstance:
    Type: AWS::EC2::Instance
    #(...)
    Properties:
      ImageId: ami-08847abae18baa040 # Amazon Linux 2 AMI (HVM), SSD Volume Type
      #(...)
      UserData: !Base64
        Fn::Sub: |
          #!/bin/bash
          yum -y update
          amazon-linux-extras install php7.2

          yum -y install aws-cfn-bootstrap
          # Install the files and packages from the metadata
          # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-init.html
          /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
          # Signal the status from cfn-init
          # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-signal.html
          /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region} 
    CreationPolicy:
      ResourceSignal:
        Timeout: PT5M
```

変数展開のためにFn:Subを使用。
ついでに`yum update`と`amazon-linux-extras`で管理されているリポジトリの有効化もやっておく。

あとはヘルパースクリプトのインストール、`cfn-init`によるデプロイ、`cfn-signal`で結果の通知。
何故かチュートリアルには`#!/bin/bash -xe`のオプションが付いていたため、エラー発生時に`cfn-signal`が実行されずにタイムアウト待ちになったため外しておきます。詳細をログに出したければ -xは残してもいいかも。

##  Metadata プロパティ

`cfn-init`を実行すると、ここに記載する`AWS::CloudFormation::Init`リソースの内容がデプロイされます。

```yaml
Resources:
  WebServerInstance:
    Type: AWS::EC2::Instance
    Metadata:
      # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-init.html
      AWS::CloudFormation::Init:
        config:
          packages:
            rpm:
              mysql57-community-release: http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
            yum:
              httpd: []
              mysql-community-client: []
              php: []
          files:
            # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-hup.html
            /etc/cfn/cfn-hup.conf:
              content: !Sub |
                [main]
                stack=${AWS::StackId}
                region=${AWS::Region}
              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.WebServerInstance.Metadata.AWS::CloudFormation::Init
                action=/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource WebServerInstance --region ${AWS::Region}
                runas=root
              mode: '000400'
              owner: root
              group: root
          commands:
            01_set_timezone:
              command: timedatectl set-timezone Asia/Tokyo
              ignoreErrors: false
            02_install_composer:
              command: /usr/bin/curl -s https://getcomposer.org/installer | /usr/bin/php && mv composer.phar /usr/local/bin/composer
              env:
                HOME: /root
              cwd: '~'
              test: test ! -e /usr/local/bin/composer
              ignoreErrors: false
          services:
            sysvinit:
              httpd:
                enabled: true
                ensureRunning: true
              cfn-hup:
                enabled: true
                ensureRunning: true
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
    Properties:
      #(...)
```

`configSets`を使うと複数のconfigを記載できるようです。依存関係のコントロール用？今回は必要ないので`config`を使用。

`packages`でyumのパッケージをインストール。`rpm`で野良パッケージや追加リポジトリのインストールも可能です。

`files`で設定ファイルの作成ができます。ここではUpdateStack用の`cfn-hup`サービスに関する設定を追加。

`commands`で任意のコマンドを実行できます。実行順は<b>キー名の昇順</b>になります。`test`を書くと判定がtrueの場合のみ実行できます。コマンド実行時は環境変数が殆ど設定されていないので注意。`env`に記載可能です。

`services`に起動したいサービスを記載します。

UserDataのログは /var/log/cloud-init-output.logに、`cfn-init`や`cfn-hup`のログは/var/log/cfn-*.logに出力されているので、うまくいかない場合は参照しましょう。

## UpdateStack時

`AWS::CloudFormation::Init:`の内容に変更があった場合、UpdateStack時に`cfn-hup`サービスが検知して、cfn-auto-reloader.confの内容(`cfn-init`)を実行してくれる...ハズなんですが、実行してくれる時としてくれない時があって良くわかりませんでした。

2018-11-02追記
/var/log/cfn-hup.logを眺めていたら、どうやら変更監視は15分間隔で実施されているようでした。
UpdateStack後に次回のチェックを待つとちゃんと実行されました。

実行されなかった場合は、EC2にログインしてactionの内容を手動で実行すれば反映できます。

```bash
sudo /opt/aws/bin/cfn-init -v --stack yourstackname --resource WebServerInstance --region ap-northeast-1 # Tokyo
```

## おわり

AWSの公式ドキュメントは必要なことは全部書いてあるはずなのに、実際やると結構苦労しますね。