【AWS】CloudFormationの実践的活用～個人的ベストプラクティス

# はじめに
最近、CloudFormationを実運用で使い始めました。開発環境／QA環境／本番環境ともにCloudFormationで運用できる目途がついたので、ポイントをまとめてみます。

なお、CloudFormationは名称が長いのでCFnと略します。


# 基本方針
## CFn templateの作成・管理
- 管理手法
    - templateはyamlで書く。
    - templateはGitリポジトリで管理する。
- template構成
    - templateはある程度独立した単位に分割する。
    - template間で共有するパラメータは、Stack作成時にParametersとして渡す／Cross-stack referenceで参照する。
- 複数環境の併存
    - 同一のAWSアカウント・同一リージョンに複数環境（dev1/dev2など）を構築できるようにする。
    - したがって、Stack名やCross-stack reference（後述）のExport名は、必ず環境名のプレフィックスを付与する。
    - 費用のかかるAWSリソースは環境名が分かるTagを必ず付与する。こうすることで、Cost Explorerで環境ごとの費用を確認できる。
    - ALBやRDSなどのEndPoint名は、デフォルトで生成されるものは利用しない。これに対するCNAMEレコードを必ず併せて作成し、利用する。
- 永続化データの保護
    - S3 BucketやRDSなど、データを保持するものは必ずDeletion PolicyをSnapshotにする。（必要に応じてRetainにする）
- 可視化（Visualize）
    - template自身内のResourceの依存関係を可視化した際に、複雑になりすぎないようにする。
    - template間の依存関係（Outputs/ImportValue）をあわせて管理する。graphviz等で図として出力できるようにしておく。

## アプリケーションからの利用
アプリケーションからAWSリソースを利用する際には、DNSを極力利用します。
Internetへ公開するドメイン名はもとより、RDSやRedisに接続するためのEndPointはあらかじめ検討して定義しておきます。
各AWSリソース作成時に、併せてRoute 53のCNAMEレコードを作成するようにします。

たとえばRDSの場合は下記のようなCNAMEレコードを作成し、アプリケーションから接続する際には必ずCNAMEレコードのドメイン名を用いることとします。

- RDSオリジナルのEnd Point名：
    - dev1-main-rds-cluster.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com
- Route53のCNAMEレコード
    - main-rds.dev1.your-app-example.com

ALBの場合も同様にCNAMEレコードを作成します。

# CFn templatesの全体構成・方針を検討する
## template分割／統合の検討
CFn templateを作っていくうえで重要ポイントは以下に尽きるかと思います。

- 如何にtemplateを分割／統合して見通しを良くするか
- Stack作成時のParameter指定間違いを如何に減らすか
- 如何に柔軟で変更に耐えうる構成とするか
- お金がかかるAWSリソースを如何に簡単に削除／再作成できるようにするか

templateの数が少なければ少ないほど、Parameters指定時の間違いは減りますが、見通しが悪くなります。
一方、templateを分割していくと、見通しは良くなりますが、Parameters指定時の間違いは増えます。
両者のバランスを考えながら、最適なポイントを探っていくことが肝要です。

また開発環境等では、Stackの作成／削除のタイミングを考える必要があります。
一般的に、開発環境は営業時間帯以外は利用されないため、利用していない時間帯はリソースを削除して開発環境の費用を抑えたい、といった要望があると思います。
頻繁に作成／削除するリソースとそうでないリソースとで別のtemplateにすることで、こういった運用が容易になります。

公式ドキュメントのベストプラクティスも参考になります。

AWS CloudFormation Best Practices
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/best-practices.html


## Stackへのパラメータ指定の方法
また、Stackにパラメータを指定する際には、AWS Management Console／AWS CLIからParametersとして指定する方法と、
Cross-stack referenceとして他の作成済みのStackのOutputsを利用する方法の2種類を利用します。

他に、SSMのParameter Storeを使う方法が合理的かと思いますが、まだ試しておりません。
いずれこちらも試してみたいと思います。
AWS Lambda関数など、他のプログラムからStackのUpdateをする際もParameter Storeを参照できるので、同一情報の多重化を防ぐことができそうです。

[Systems Manager パラメータをCloudFormationで使う](https://qiita.com/y-ohgi/items/7870ff8c2067290905e8)


## Cross-stack reference利用時の注意点
Cross-stack referenceの使い方に簡単に触れます。
基本的な使い方は以下のWalkthroughを読めばわかるかと思いますが、ポイントは以下の通りです。

- 被参照側
    - Outputsセクションで必要な値をExportする。
- 参照側
    - 他のtemplateからFn::ImportValue関数で参照する。

[Walkthrough: Refer to Resource Outputs in Another AWS CloudFormation Stack - AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/walkthrough-crossstackref.html)


Exportされている値は、これを参照しているStackが存在している間は変更／削除ができません。
従って、Cross-stack referenceで参照させる値は、その環境が存在する間は常に固定で変更が無いようなもののみを対象とします。
具体的には、VPC関連のリソースIDなど（VPC ID、Subnet ID、Security Group IDなど）がこれにあたるでしょう。

逆に、ECSのTask DefinitionはExportするべきではありません。
Task DefinitionのARNにはバージョン名が含まれますが、これはアップデートの度にバージョンがインクリメントされます。
Task Definitionの値をExportして他のStackから参照していると、これをアップデートできないことになります。

[AWS::ECS::TaskDefinition - AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#aws-resource-ecs-taskdefinition-returnvalues)

## 依存関係を可視化する（template間）
templateファイル間の関係をグラフ化することで、全体を把握しやすくなります。
要素間のネットワーク図を作成するためにはGraphvizというソフトが便利ですが、これをWebからお手軽に利用できるサイトがあります。

http://www.webgraphviz.com/

図の元ネタとなる定義は、下記のようなDOT言語と呼ばれる記法で記述します。
これをGitリポジトリで管理することで、templateの全体構成も把握しやすくなります。

```
#
# This file can be visualised in the Graphviz tool below.
# http://www.webgraphviz.com/
#


digraph g{
  "start" -> "template01" -> "template11"
  "start" -> "template02" -> "template11"
  "template02" ->  "template12"
}
```

ちなみに、上記の定義をグラフ化すると下記のような図になります。
![2018-09-06_02h31_52.png](https://qiita-image-store.s3.amazonaws.com/0/137777/c7b08682-7192-b327-8fbb-04de5df4aa4e.png)


# 現状でのベスト構成
上記までを踏まえたうえで、現状、最善と考えられるテンプレート構成をまとめてみました（幾つか不足はありますが）。
矢印は依存関係を表しています。

前提条件は下記の通りです。

- templateとStackは1対1
- アプリケーションは全てDockerコンテナ上で稼働し、ECS Cluster上に展開される
- AWS Lambda関数はCFnの対象外
- API GatewayのSwagger定義は、CFn templateには含めず別途管理している
- CodeCommit/CodeBuild/CodePipelineを併せて利用しているが、図には含んでいない

![2018-09-19_19h41_20.png](https://qiita-image-store.s3.amazonaws.com/0/137777/43e643e4-f789-735d-9668-d03b466c60c9.png)



まず、AWSリソースの特性に合わせてある程度番号を付与します。
現状は下記のようなルールを採用しています。

- 0x: Most basic templates needed by all other templates.
- 1x,2x: AWS resources dedicated to the application you built which don't require server-like resources. In other words, it does not needs the hourly fee.
- 3x,4x: AWS resources dedicated to the application which require server-like resources. If it is possible, you have to terminate them when you are not using to reduce cost.

各テンプレートは、概ね以下のAWSリソースを作ります。
EC2/RDS/ALBは利用していないときには削除できるように作ります。

| template | AWS Resources to be created | Remarks |
|------------------|-------------------|--------------------|
| 01-basis | VPC及びRoute53 | オプションでNAT Gatewayの利用可否を選択可能 |
| 02-acm | ACM | ACMをCloudFormationで作ること自体にはあまり意味は無いのですが、Exportして他のtemplateから参照させたかったため、ここで定義しています。 |
| 11-s3 | S3 Bucket | API GatewayのSwagger定義ファイルを確認 |
| 12-apigateway | API Gateway | S3 Bucketに格納されたSwagger定義ファイルを元に作成 |
| 13-cognito | Cognito Federated Identities若しくはUserpoolと、これに付与するIAM Roleなど | Cognitoで認証したユーザにてAPI Gateway経由でのAPIを実行させるためには、適切なPermissionsが必要です。この指定においてAPI GatewayインスタンスのIDが必要になります。ちなみにこれは、API Gateway上の定義で「AWS_IAM」と設定する場合にのみ必要です。 |
| 31-ec2-bastion | EC2インスタンス | 踏み台サーバのみ |
| 32-rds | RDSインスタンス | アプリケーションからの接続用に、Route53のCNAMEレコードを併せて作成 |
| 33-alb | ALB及びTargetGroup | TargetGroup名は固定名称としてExportし、ECS ClusterのServiceから参照しています。ALBインスタンスに付与される素のドメイン名に対し、Internet向けに公開するドメイン名のCNAMEレコードを作成します。 |
| 41-ecs-clusters | ECS Cluster及びこれに必要なAWSリソース（AutoScalingGroup、IAM Roleなど）||
| 42-ecs-taskdefs-and-services | ECSのTask Definition及びECS Clusterで実行するService| 本当はTask DefinitionとECS ClusterのServiceの定義は分離したかったのですが、Task DefinitionのARNにはバージョン番号が含まれており、Cross-stack referenceでの運用が実質できないため、1つのtemplateにせざるをえませんでした。 |

上記それぞれについて、差し支えない範囲でtemplateを作って公開する予定です。
取り急ぎ、01-basis及び32-rdsを公開中。

[【AWS】実例で学ぶCloudFormation～VPC/Route53編～](https://qiita.com/tmiki/items/886656a7b0b03f0d05e6)
[【AWS】実例で学ぶCloudFormation～RDS Aurora編～](https://qiita.com/tmiki/items/19cc4fc71949eaafbc3a)

# CFn templatesを運用する
上記までで、概ねtemplateの全体構成は決まりました。
次に、これらtemplateと、Stackをどのように運用していくか考えます。
ポイントだけ整理します。

- template作成・更新時は、AWS CLIのvalidate-template、cfn-lintを活用する
- Stack作成時に指定するParameterもyamlで管理し、Gitリポジトリで管理する
- 各templateにはバージョン番号を付与し、Stack作成時にParameterとして指定する
- Stack変更時は、Change Setを利用してどのような変更が行われるか確認する


具体的な情報はこちらの記事が詳しく、参考になります。
[(2017年12月時点) 私的 CloudFormation ベストプラクティス](https://qiita.com/yasuhiroki/items/8463eed1c78123313a6f)


# Furthermore
## 可視化ツールの活用
軽く探してみましたが、以下のようなものがあるようです。
なお、以下のものはいずれも1つのtemplate内での可視化となります。
template間の依存関係を可視化するものは見当たりませんでした。

- [CloudFormation Designer (in AWS Management Console)](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/working-with-templates-cfn-designer.html)
- [VisualOps](https://www.visualops.io/)　※2018/9/6現在、繋がらず
- [cloud-formation-viz]
(https://github.com/benbc/cloud-formation-viz)

## その他高度なツール
現在のCloudFormationサービスの仕様上、templateに指定するParameterなどは、自前で管理する必要があり、これを統合的・一元的に管理するためには多少の手間が必要です。
こういった作業を楽にするためのツールが無いか探してみました。

### Sceptre
CloudFormation templateの開発・管理を便利にしてくれるツールのようですが、詳細は未確認です。
Examleを見るに、1つのCFn tempalteをもとに複数環境を構築・管理できるもののようです。

https://github.com/cloudreach/sceptre

### Lono
詳細は未確認ですが、紹介動画を見る限りでは、可視化ツールがあったりと高機能なようです。
ERB（eRuby）ベースのテンプレートで定義ファイルを書くようです。
軽く動かした限りでは、Ruby 2.5の環境でないと動きませんでした。
Amazon Linuxの標準のyumリポジトリ（amzn-main/latest）ではRuby 2.4が最新となるかと思いますので、インストールには準備が必要です。

http://lono.cloud/
https://medium.com/boltops/lono-inspect-depends-visualizing-the-cloudformation-template-dependency-graph-3296290c84fb

ディレクトリ構造をみれば何となくイメージが湧くのではないでしょうか。
http://lono.cloud/docs/tutorials/ec2/project-structure/

### stacker
※詳細未確認

https://github.com/cloudtools/stacker

### cfn-flow
※詳細未確認

https://github.com/kickstarter/cfn-flow


# おわりに
CloudFormationを本番環境で活用していくとなると、いろいろと考えることが出てきます。
うっかり下手な方法で管理しようとすると、後々の運用が破綻する、ということにもなりかねません。

構築前に十分に検討し、場合によってはツールの助けを借りて、templateを管理・運用していく必要があります。