AWS CloudFormationをちょっとだけ理解した

やっとなんとなく意味がわかってきたCloudFormation。AWSのインスタンスやいろんな設定を簡単にデプロイできるツール、みたいな説明はいっぱい見かけるけど、覚えないといけない仕組みが増えただけでaws cliでできることとなにが違うのかがいままでよくわからなかったのです。

理解した内容は、

1. CloudFormationは冪等性を担保しやすいこと
2. 複数のリソースを一括で管理できること

詳細

1. インフラ構築をaws cliでがんばってスクリプト化しても「Infrastructure as Code」は実現できるかもしれないが、aws cliではcreateなんちゃら系をするコマンドは2回目以降にエラーになっちゃったりして、きちんと動くようにするにはいろいろ大変。CloudFormationであれば記述をシンプルにできるし、何度実行しても最新の記述に更新できる冪等性を実現できます。

2. EC2とRDSの組み合わせなど複数のリソースからなる一連の仕組みを一発で作れるだけでなく、削除も簡単です。EC2やRDSなどのリソースをCloudFormationのスタックというものにぶらさげて作るようなイメージで、スタックを削除するとぶらさがっているリソースもまとめて削除されます。

# 用語

- リソース
    - EC2インスタンス、ALB、IAM RoleなどAWS上のなにか。
- テンプレート
    - JSONかYAMLで書くリソース設定集みたいなもの。
    - テンプレートをもとにスタックが作られる。1つのテンプレートから同じ構成のスタックをパラメータを変えてたくさん作ることができる。
- スタック
    - テンプレートにパラメータを渡して実行するとできあがるもの。
    - スタックの中にEC2インスタンスなどのリソースが作られる。
    - スタックを削除するとぶらさがっているリソースも削除される。
    - テンプレートを再実行すると上書きされる。
- パラメータ
    - テンプレートでスタックを作成するときに必要な変数。
    - どんなパラメータがあるかはテンプレートの記述次第だが、インスタンスタイプ、インスタンス名、DB名などなど。
- チェンジセット
    - スタックを新規作成や更新して実行したときに実際にAWSのリソースがどんな変更を受けるかを表したもの

# 似たようなAWSサービスの比較

- CloudFormation
	- AWSのリソースを冪等性を持たせて一括管理するもの。
- OpsWorks
	- Chef。ミドルウェアのインストールや設定を冪等性を持たせて管理するツール。
	- Chef自体はAWS向けには限らないので、AWSのリソースを管理できるCloudFormationよりも少し上のレイヤーになるのかな。
- Elastic Beanstalk
	- ウェブアプリなどのよくあるインフラ構成を簡単に作れて、そのインフラ上にアプリを簡単にデプロイするための仕組み。
	- デプロイでは、LBとの切り離し方法、ダウンタイム、切り戻し方法などでの違いからデプロイ方式がいくつかある。

（CloudFormation以外はまだ触ったことないので、よくわかりません）

# 始め方

とりあえず以下の記事をもとに超簡単なテンプレを作って動かしてみる。

[【CloudFormation入門1】5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築 ｜ Developers.IO](https://dev.classmethod.jp/cloud/aws/cloudformation-beginner01/)

ブラウザでコンソールで触るのではなくawscliでスタックを作るには以下のコマンド。

チェンジセットを作るのみ（dry-run）。チェンジセットを確認する方法はこのコマンドの出力で案内される。

```
$ aws cloudformation deploy --template-file <TEMPLATE.yml> --stack-name <STACKNAME> --no-execute-changeset
```

実行するには`--no-execute-changeset`を外す。

```
$ aws cloudformation deploy --template-file <TEMPLATE.yml> --stack-name <STACKNAME>
```

次、LambdaをCloudFormationで試すには以下の記事が参考になる。Lambdaは関数のコードもデプロイしないといけないので、ひと手間増える。

[CloudFormationでAWS Lambdaを作成・更新する際のベストプラクティス - Qiita](https://qiita.com/kihoair/items/fe204fc8582946646d07)

AWSのいろいろなリソースをCloudFormationで試すには、レファレンスのページを見る。

[AWS リソースおよびプロパティタイプのリファレンス - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html)

# サンプル

Lambdaと必要なIAM Roleを作るCloudFormationを試してみます。

以下のようなディレクトリ構成でYAMLファイルとLambdaの関数のソースとなるファイルを作ります。

```
$ tree
.
├── sample1.yml
└── src
    └── main.py

1 directory, 2 files
```

YAMLファイル。

```sample1.yml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  SampleLambda:
    Type: AWS::Lambda::Function
    Properties:
      Code: src
      Handler: main.lambda_handler
      Role: !GetAtt SampleIAMRole.Arn
      Runtime: python3.7
  SampleIAMRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action: sts:AssumeRole
```

各リソースの`Properties`にどう書けばいいかは、レファレンスを見ればわかります。レファレンスではよくわからなければ、マネジメントコンソールで実際に適当にリソースを作ってみて、awscliとかで作ったリソースの値を見ればいいのかな。

レファレンス
[AWS::Lambda::Function - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)
[AWS::IAM::Role - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html)

`SampleLambda`の中の`Code`というプロパティはLambdaのソースコードを保存しているローカルのディレクトリ名です。

`GetAtt`というCloudFormationの中で使える関数も使っています。

[Fn::GetAtt - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html)

`GetAtt`の引数として`SampleIAMRole`というのはその2行下にあるリソース論理名です。`Arn`という属性の存在は以下のページでわかります。

[Arn - AWS::IAM::Role - AWS CloudFormation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#Arn-fn::getatt)

Lambdaの関数のソースファイルはこちら。

```src/main.py
def lambda_handler(event, context):
    print("Hello, World!")
```

以下のコマンドを実行すると、`sample1.packaged.yml`が生成されます。`Code`で指定したソースをいったんS3にアップロードして、YAMLの設定をS3を参照するように変更するコマンドです。変更後のYAMLはローカルの`sample1.packaged.yml`に書き出されます。

```
$ aws cloudformation package --template-file sample1.yml --s3-bucket <BUCKET-NAME> --s3-prefix <S3-PREFIX> --output-template-file sample1.packaged.yml
```

`--s3-bucket`と`--s3-prefix`はLambdaのソースを一時的にアップロードする先を指定します。`--s3-prefix`は後ろに自動で`/`が付与されるようですので、記載不要です（`/`を付けちゃうと二重になっちゃいます）。

`sample1.packaged.yml`は`sample1.yml`と中身がよく似ていますが、ちょっとだけ変換されています。

```
$ diff -u sample1.yml sample1.packaged.yml
--- sample1.yml 2019-11-18 23:50:34.048495755 +0900
+++ sample1.packaged.yml        2019-11-18 23:50:45.806498124 +0900
@@ -3,9 +3,14 @@
   SampleLambda:
     Type: AWS::Lambda::Function
     Properties:
-      Code: src
+      Code:
+        S3Bucket: XXXX
+        S3Key: YYYY/zzzz
       Handler: main.lambda_handler
-      Role: !GetAtt SampleIAMRole.Arn
+      Role:
+        Fn::GetAtt:
+        - SampleIAMRole
+        - Arn
       Runtime: python3.7
   SampleIAMRole:
     Type: AWS::IAM::Role
```

`Code`のところの差分を見ると、LambdaのソースがいったんS3にアップされていることがわかります。

`GetAtt`のところも差分がありますが、短縮した書き方から完全な書き方に変換されているようです。

続いて`sample1.packaged.yml`をCloudFormationでデプロイするには、次のコマンドを実行します。まずはdry-runするために`--no-execute-changeset`を付けます。

```
$ aws cloudformation deploy --template-file sample1.packaged.yml --stack-name sample1 --no-execute-changeset

An error occurred (InsufficientCapabilitiesException) when calling the CreateChangeSet operation: Requires capabilities : [CAPABILITY_IAM]
```

エラーになりました。

よくわかってないのですが、`--capabilities CAPABILITY_IAM`を付けるといいようです。

```
$ aws cloudformation deploy --template-file sample1.packaged.yml --stack-name sample1 --capabilities CAPABILITY_IAM --no-execute-changeset
```

`--no-execute-changeset`を外し、dry-runでなく実際に実行します。

```
$ aws cloudformation deploy --template-file sample1.packaged.yml --stack-name sample1 --capabilities CAPABILITY_IAM
```

ブラウザでマネジメントコンソールにアクセスしてLambdaのところに行くと、実際に実行でき、Hello, World!を出せました。

よし[完全に理解した](https://togetter.com/li/1268851)。