最近 CloudFormation を触っていて、よくある初期構築のベストプラクティスについて意見がほしいので自分の考える CloudFormation の設計や使い方についての考えを書いた。
CloudFormation のメリット
CloudFormation を利用するためメリットはリソースの参照を簡単に記述できることと、べき等性の保証だと思っている。
以前、 EC2 インスタンスのプロビジョニングを Ansible で書いたことがある。 べき等性を確保したく、 EBS のマウント、アンマウント、 EIP の付け替えなどの変更操作を Ansible だけで操作したいということを考えたときに、 Ansible の YAML ではなく以下のような Python のコードを書いた。
こんなコードを書いたのは Ansible では YAML で EC2 API の実行の応答結果を扱えないためだ。 たとえば、インスタンスを作成した時に得られるインスタンス ID を引数に他の API を実行することができない。
CloudFormation はテンプレートと呼ばれるファイルに記述することでそれぞれのマネージドサービスのプロパティを記述することができる。 マネージドサービスのプロパティでは他のマネージドサービスのプロパティを参照できる。 先の Python のコードを CloudFormation のテンプレートに書き換えると以下のようになる。
依存関係ごとにスタックを分けて、記述するのが個人的なベストプラクティスだと思っている。 特に依存関係がないものはマネージドサービスごとにスタックを分けていて、自分は以下のようなフォルダ構成を取っている。
$ tree . ├── cloudtrail │ ├── parameters │ │ ├── production.json │ │ └── staging.json │ └── template.yaml ├── iam │ └── template.yaml ├── README.md ...
ステージング環境と本番環境で一部のパラメタを変えて API の引数を変えているのでオペレーションを環境間でそのまま適用できるのも CloudFormation の魅力だと思っている。 スタックの動作について環境間の差分としてパラメータ以外は変えることはできず If 文がサポートされていないのもそういった用途のマッチしていて、環境間で整合性をとるための方法としてユースケースを限定してくれているのがよいなと思った。
テンプレートフォーマットの選択
テンプレートフォーマットに YAML がサポートされるまではテンプレートは JSON で記述するか、サードパーティ製モジュールの SparkleFormation, kumogata 等を用いて独自の DSL で記述するといった選択肢があった。 これらはナンセンスだと思っていて、 JSON を使って記述するとフォーマットとしての可読性はそこそこ高いのだけどコメントが書けない致命的な問題、サードパーティ製モジュールの DSL を利用するとサードパーティモジュールへの依存や、公式ドキュメントのサンプルを参照しにくい問題につまずいた。 YAML がサポートされるようになってからはこれらの問題が解決されて、 YAML が選択肢として良いと思っている。
しかし、 YAML を採用するとなると新たに考慮すべき点がある。例えば、ポリシードキュメントの記法を考慮しないといけない。 IAM グループを作成するときのテンプレートを、すべて YAML で記述すると以下のようになる。
... Groupadmin: Type: "AWS::IAM::Group" Properties: GroupName: admin Policies: - PolicyName: AllAllow PolicyDocument: Statement: - Effect: Allow Action: "*" Resource: "*" ...
しかし、ポリシードキュメント部分は以下のようにして JSON (文字列扱い) で書くことができる。
... Groupadmin: Type: "AWS::IAM::Group" Properties: GroupName: admin Policies: - PolicyName: AllAllow PolicyDocument: | { "Statement": [ { "Effect": "Allow", "Action": "*", "Resource": "*" } ] } ...
どちらが見やすいかという議論ではなくて、ポリシードキュメントは AWS のシステム上では JSON で記述される。 そのため、ポリシードキュメントのドキュメントを見ても、 JSON のサンプルはあるものの YAML のサンプルはない。 また、変更したい時にマネジメントコンソール上で validate して確認した後にテンプレートに貼り付けることで Stack 更新時のエラーにハマることなく更新できる。 なので、自分はポリシードキュメント部分に関しては JSON で記述するようにしている。
どこまでを CloudFormation で扱うか
CloudFormation は一部の API を扱えない。
今回、 CloudFormation のテンプレートを書いていて困ったのは一部の API が扱えないこと。 例えば、 EC2 の KeyPair の登録や、作成した Elasticache のエンドポイントの取得ができない。
EC2 の KeyPair の登録は AWS アカウントごとあるいはリージョンごとに繰り返す作業なので、 CloudFormation にまとめて定型化したい。 Elasticache のエンドポイントの取得に関しても… 特に CloudFormation が API をサポートしてくれない理由はないと思うので、 API をサポートしてくれるのがベストだが、 CloudFormation が対応しない API に対しては無理に CloudFormation で対応するのではなく別のツールを使うのが良いと思う。 テンプレートを動的に生成するような対応を取ると、リソースの依存解決が CloudFormation 外に依存することや環境ごとにテンプレートが異なる可能性 (この環境ではリソースを作成するが別の環境では作成しない等の If 文のような場合分け) が発生する。 テンプレートの生成次第でなにもかもできるようになってしまうと、結局のところ冒頭で書いた Ansible の Python コードと何ら変わりはないので、CloudFormation は CloudFormation がサポートする範囲内でオペレーションを収めたい。 AWS に CloudFormation の API のサポートを要望を出していって、それまでのつなぎとして使うには動的テンプレート生成はありかなと思うけれども、避けられるようなら避けたい課題かなと思う。
まとめ
CloudFormation の個人的におもっているベストプラクティス知見について書いたッ! もっとこうしたほうがいいとか、知見がほしい。