私的CloudFormationベストプラクティス

# CloudFormationとは
CloudFormation(以下CFn)は、テキストファイルからAWSのリソースを作成することができるInfrastructure as Code(以下IaC)のサービスです。

# この記事で話すこと・話さないこと
## 話すこと
* CFnを用いたAWSリソース作成のベストプラクティス

## 話さないこと
* CFnに関するサービスの細かい仕様や用語の説明
* 他のIaCを実現するサービスとの比較

# ベストプラクティス
Multi-AZ構成のVPC上にALBとWebサーバを構築するサンプル
https://github.com/hicka04/cfn-best-practice

# ポイント
## ①YAML形式で記述
* CFnで使える記述方式
    * YAML
    * JSON
* YAMLで記述するメリット
    * コメントが書ける
    * JSONで記述する際の読みづらさ( `[]` `{}` によって文字が増える)の問題がない

## ②ライフサイクルと所有権を意識してStackを分割
参考 : [AWS公式のベストプラクティス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html#organizingstacks)

### ライフサイクル
* **変更するタイミング**が同一のリソースが1つのStackにまとまっていること
    * 複数人で開発する際に同一のテンプレートを編集する可能性を減らせる  
      →**コンフリクトやデグレを防げる**
* **削除するタイミング**が同一のリソースが1つのStackにまとまっていること
    * そのStackを削除するだけで不要になったリソースをすべて削除できる  
      →**オペミスを防げる**

### 所有権
* 簡単に言うと、「誰がそのリソースの変更を担当するのか?」
    * アプリケーションとDBで担当者/部署が別れている場合はその単位でStackの分割をすべき

## ③1テンプレートで複数環境のStackを作成できるようにする
* テンプレートには環境による差異は記述せず、パラメータで注入できるようにする
* パラメータを注入してStackを作成して初めて環境ごとの意味のあるリソースとなるようにする

<font color="Gray">筆者は、環境ごとにテンプレートを作成してしまい環境共通の改修をするのが面倒な状態にしてしまったことがあります…。</font>

### メリット
* 環境ごとにテンプレートファイルが分かれていない
    * 共通の変更を加えたい場合にミスが生まれない
    * 工数削減

### デメリット
* 環境ごとの差分がパラメータだけで表現できないときに追加で対応が必要(リソースの数が違う場合など)  

#### 解決方法
* 環境ごとの差分を表現する用のテンプレートを追加
* [Condition](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html)をもとに、作成するリソースを分岐

今回のサンプルコードでは、1つ目の方法を用いている。
理由としては、2つ目の方法では以下のようなデメリットがあるためである。

* 条件の設定をミスしてしまうと本番影響が出てしまう
* テンプレート内に、条件によって作成されるリソースと作成されないリソースが混在して読みづらくなる

## ④クロススタック参照を用いてStack同士の依存関係を作る
参考 : [AWS公式のベストプラクティス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html#cross-stack)

* 値のハードコードを避ける
    * テンプレートが再利用できる
* 強い依存関係を作れる
    * 他のStackに依存されているリソースがある場合、そのリソースを変更したり削除したりできなくなる
    * 誤った変更や削除をしようとすると、変更や削除に失敗し、ロールバック等の安全な行動をとってくれる

### 依存される側
* Export名は `${AWS::StackName}-${ResourceName}`
    * Stack名をExport名に含めることで、1テンプレートで複数環境のStackを作る場合でも一意のExport名になる

```network/network.yml
Outputs:
  Vpc:
    Value: !Ref Vpc
    Export:
      Name: !Sub ${AWS::StackName}-Vpc  # 👈
```

### 依存する側
* 依存したいリソースを作成している**Stack名をパラメータで受け取る**
    * `Metadata` で依存しているStackの一覧を整理するとよりわかりやすい
* `${AWS::StackName}-${ResourceName}` で `ImportValue` する

```web/web.yml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  NetworkStack:  # 👈
    Type: String

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Dependent Stacks
        Parameters:
          - NetworkStack  # 👈依存しているStackの一覧をここで整理

Resources:
  SgAlb:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId:
        Fn::ImportValue:
          !Sub ${NetworkStack}-Vpc  # 👈
```

## ⑤[Stackのネスト](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html)はしない
### Stackのネストとは
* CFnのテンプレートを共通化する手法
* CFnのテンプレート内で[Stackを作るという記述](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html)をすることで、Stackに親子関係を作る

### なぜネストさせないか
* [AWS公式のベストプラクティス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html#nested)ではネストでテンプレートを使い回すことを推奨している
* だがネストさせる場合、**子のStackの中で作られているリソースの詳細な変更セットが見れない**デメリットがある
    * 子のStackで**リソースが削除される危険な変更**をしても、変更セットとしては**「子のStackのどこかが変わってるみたいだな〜」しかわからない**
    * S3やDynamoDBといったデータストアのリソースが削除されるとデータも消えてしまい、CFnだけでは元の状態に戻すことができなくなる
* ネストでも詳細な変更セットが見れるようになれば、ネストを採用するのもアリだと筆者は思っている
    * ネストをすると[パラメータをテンプレート内に記述できる](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html#cfn-cloudformation-stack-parameters)ため、パラメータをバージョン管理できるようになるメリットがある

## ⑥命名
### Stack名
`stack-${ServiceName}-${SystemName}-${Purpose}-${Env}`  
具体例 : `stack-blog-web-prod`

* すべて小文字
* ハイフン(`-`)つなぎ
* `${Purpose}` はないこともある

### リソース名
クラスメソッドさんが公開している[AWSリソースの命名規則](https://dev.classmethod.jp/cloud/aws/aws-name-rule/)がとても参考になる

`${SystemName}` や `${Env}` はパラメータで受け取る
`${SystemName}` のような固定値は `Default` と `AllowedPattern` を用いると完全に固定可能

```yml
Parameters:
  Env:
    Type: String
    AllowedValues:
      - prod
      - stg
      - dev
  SystemName:
    Type: String
    Default: web
    AllowedPattern: ^web$
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Metadata
        Parameters:
          - Env
          - SystemName
```

### 論理ID名
`${ResourceTypeName}${Purpose}`  
具体例 : [`SgAlb`](https://github.com/hicka04/cfn-best-practice/blob/master/web/web.yml#L37)
ALB用のSecurityGroup

* アッパーキャメルケース
* 作成したいリソースの種類を最初に持ってくると分かりやすくなる
    * 最初の数文字見ただけで何を作るのか伝えられる
    * テンプレート内ではリソースの種類ごとにまとまって記述するとさらに分かりやすくなる

## ⑦タグ付け
Stack作成時にタグ付けすると、そのStack内で作成したすべてのリソースに同じタグをつけることができる
以下のタグをつけると詳細な絞り込みできる(と、[AWSリソースの命名規則](https://dev.classmethod.jp/cloud/aws/aws-name-rule/)でおまけとして紹介されてました)

* SystemName
* Env

<img width="1300" alt="stack-tag.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/80832/41c5607b-50a3-1b1f-eca5-f44fbaefc981.png">

# 最後に
本記事でご紹介したCFnのベストプラクティスは、チームメンバーや作成するシステムに応じて変化する可能性があります。状況に合わせて変更していただければと思います。
みなさんがCFnでIaCをする際の1つの指針になれば幸いです。