CloudformationでIaCしよう

# どうも、CFn 大好き！　　アプリエとインフラの気持ちのわかるエンジニア　おーつきです。

アプリケーションの目線から考えると、プログラムのソースコードを管理するといいこと：
・（コードを）Gitでインフラが管理できる。
・（環境）開発差異をDiffして世代管理できる。
・作成リソースを独立した単位で構成できる。

ただ、IaCやれるといいねと認識しても、なかなかできないのが

・脱パラメータシート
・IAMやCW監視の管理を増加、見直す
・レイヤーごとにリソース管理する

そんな、インフラエンジニアの悩みで考えたときCloudformationを導入するのに共有したTipsを共有しようと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/689bd9d8-579f-e828-5ee1-d1fc56ce3e3c.png)


## Template をレイヤとして考えた設計にする

Cloudforamtionではテンプレートという１ファイルごとにStackを定義できます。このStackを極力分類で分けて管理すると便利です。
特に下のレイヤー（図では数字の１からかさねて５レイヤーと思ってください）から、上のレイヤーが参照できるような形で定義すると　CFnのインポート・エクスポート機能を使って依存性や実装の管理ができます。

おすすめは：
1: VPC スタック　・・・　
　主に　VPC、InternetGatewayやドメインのZoneなどを定義してあげます。
　ここはあまり欲張らずに　EndpointやPeeringも抜いて考えてしまうと良いです。
　かわりにOutputセクションで、VPCIDやARNなんかをExportしておきます。
2: セグメントスタック
　#1 に定義するSubnet、Route,Routetable やコネクションを定義します。
　大体の要件が決まりそうであれば、セキュリティグループ自体もここに定義します。
3: リソーススタック 
　KMS, IAM やSNS,S3  など後続から参照しそうなものを定義します。
　　※監視に使うSNSやEventBridgeは、システムと独立している方がいいなと思うので私の場合６以降で定義していたりします。
4: データストア、ストレージスタック　
　　RDSやElasticachなんかを定義します。　メンバーと話が割れたりしますが、アプリケーションが参照するやSGを記載するのに先に定義しておくと書きやすくなります。 
 DB と EFS などのファイルっぽいものを分けるのも良いと思います。
 またRDSのいるスタックは構築にインスタンスタイプや導入に１時間くらいかかってしまうことがあるので、なるだけこの中には　RDSに関連のある　SubnetGroupや　OptionGroup程度に留めておいて　#５以降から参照依存度を低くしておくのがおすすめです。
5: サーバスタック
 ALB、ECS、Lambda などを定義します。　もしECSやECR、EKSなどのLBと独立しCICD的なデプロイを考える場合は
　 5.1 ~ 5.5 くらいにわけて、　前者は不動のもの（ECRやEKS）、後者にワーカーノードや　ECS(Fargate)などを定義するとあとあと破棄して繋げ直しやすくなります。

大体こんな感じなのですがそのほかにも
    6: WAF , 7: endpoint, 8: バッチ, 9:監視 , 10: AI　.... と分けておいたりするのも意外に便利であまり依存しないように後方参照にさせるのがミソです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/641b8484-14ac-d4a9-b4ac-26f3347bef34.png)

---
それではここから、書くテンプレートの中を細分化して設計するあたりを定義していきます。

## Stack 名称にこだわる
 - Stack名称は以下にするようにするとスタック管理が楽になります。 （これは決め打ちでParameteで引数取ろうとせず　CreateStack時にテンプレート開いてコピペさせるのおすすめです）

    ```
        StackName:  CFSystem-d1-server
        ※CFSystemというプロジェクトのための、開発環境で1面でスタック作成する場合の例
    ```
    
 - テンプレートのヘッダに以下のように記載しておくとCF作成時にコピペでスタック名を入力できるので便利です
 - ※ (Stack作成時にコピペ、さらにParameterの指定も書いておくと忘れにくいです)
 
```yaml: CloudformationTemplate.ymlのヘッダ部分
###############################################################################
## AWSTemplateFormatVersionセクション
## 説明: AWS Cloudforamtion 仕様に基づき「'2010-09-09'」固定
AWSTemplateFormatVersion: '2010-09-09'
###############################################################################
## CloudformationTemplate  (YAML形式)
## サーバースタック用テンプレート
## StackName: (開発環境でスタック作成するスタック名: CFSystem-d-server)
## p: CFSystem-p-appserver
#        # EnvType: p
## s: CFSystem-s-appserver
#        # EnvType: s
## d: CFSystem-d-appserver
#        # EnvType: d
##
```



## パラメータは３、４個程度に絞る

何でもかんでもパラメータ化したくなるのがアプリケーションのプロパティの考え方ですが。よく考えてください。
構築時入力するのは　*あなた*　です！　CreateStackごとに、パラメータを全部テキストフィールドに入れていくめんどくささは半端ないもの。

 - SystemName (今回割愛) ... CFSystem (例）
 - EnvType   .... ここで p:production , s:staging, d:develop
 - Mensu (今回割愛)
     - EnvType は　環境の種別ごとにパラメータシート扱いのMapping(後述)で環境ごとの変化を分けることができます。
     - Mensu は、現場であるあるな　同じ環境を横展開させたいなんてときに使います（ここは欲張らずにMappings対応はしないのが秘訣です）

## Mapping を環境の種別ごとに管理する
- Mappingをパラメータシートとして考えて、EnvType(環境の種別)をつかって構築に利用する。
-　パラメータを起動時に選択してもらう EnvType で指定して、選択できるようにする。
-　またYAMLで書くとパラメータシートとしてコメントや仕様を追記できる。
-　完全に脱パラメータシートにならないこともありますが、全てのプロパティを定義して、全てに説明を記載しておくと　書くときは面倒でも、先方と合意して　コードでリリースやパラメータの管理がしやすくなります。　
-　ちなみに、CFnリソースの持つ　require/non require 関係なく仕事では全てのパラメータを設定してデフォルト値を書くようにしている文化を作っています。（慣れたら大したことはない）
```yaml: Mappings セクションの一部抜粋：リソースごとのプロパティを環境種別向けに定義する方法
Mappings:
    RDB010:
        InstanceType:
            p: m4.xlarge　　　## [SPEC]　なんでこのインスタンスにしたのかの理由
            s: m3.large   ## [SPEC]　ステージング環境でなんでこのインスタンスにしたのかの説明
            d: t3.micro   ## [SPEC] どうして開発環境はこのインスタンスタイプでいいかの説明
        StorageSize:      ## [ストレージサイズや単位を書いておくこと　：　Gib]
            p: 100        ## [SPEC] 10000ユーザー * 10%アクティブ状態 * 1レコード 1MByte * 100行　などフェルミ推定的な数値の根拠も　コードないよりもMappingに記載することおすすめです。チューニングや判断がしやすくなります
            s: 80
            d: 20
            
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/79270156-c734-0795-a159-5a84f5fcbb0d.png)


## Condition を使ってインスタンスの作成要否や環境種別の判定でパラメータを操作する

Mappings の中に Create : true / false と言う値を用意しておいて
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/04242add-3d2d-7622-be33-7eef5d0ca6e7.png)

Condition のなかで　IsCreateRDB010 なんてのを作る判定を定義します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/8ed67d8f-b3da-6ebc-fbac-8a4e9547b9c9.png)


実際のリソース定義の中でCOnditionを使うことで、Mappings 定義をtrue -> false に切り替えてStack更新するだけでDBを消したり再作成したりできるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/846cc3ec-5ba2-1698-ddcc-e5ee076f7657.png)


## リソース名は　！Join使って　命名規則に従った名称を書いておく（タグなんかも便利）
- 色々書き方はあると思いますが以下のようにしてSNipetにしておくと便利です。リソース名も最近のAWSコンソールではタグや名称がつけられるようになったので比較的はやく、「どの環境」「どのしすてむ」「どのリソース」かが目視しやすくなります。

```yaml: 文字連結機能を使って、システム名称やIDを生成するTips
!Join [ '', [ !Ref SystemName, '-',!FindInMap [StackConfig,EnvLabel,!Ref EnvType], !Ref Mensu, "-rds-", !FindInMap [ RDB010, NameSuffix, !Ref EnvType ] ] ]
```
　↓
```yaml: 連結した文字列の 例
CFSystemName-d1-rds-RDS010
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/e90aaf74-91a4-e6be-6188-2e4e180300e0.png)


## <エクスポートする>ほかスタックから参照しやすいようなエクスポートを心がける。

作られたリソースは大概　ARNを返しますが、リソースのIDだったり名称を返すものもあるので　　Value の値には!Ref( リターンされるARN)のほか　!GetAtt リソース論理名.addres  などで　IPアドレス情報を返すなんて定義をして　テンプレートエクスポートしておくと　後方テンプレートから、直接値を参照するのに便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/62cbd377-56e4-c941-6077-5e96f60cd18c.png)

## <インポートする>　前方スタックからリソースを参照できることを理解して利用する。
エクスポートしたら　インポートできるようにしてあげると便利です。

```yaml: 例:リソースのインポートのTips　をみやすくしたもの
"Fn::ImportValue": 
  !Join [ 
     '', 
     [ 
        !FindInMap [StackConfig,VPC,ImportPrefixOfVpc], ## → "EXP-CFSystem-" 
        !FindInMap [StackConfig,EnvLabel,!Ref EnvType], 
        !Ref Mensu, 
        "-segment-", 
        "LoadBalancerSubnet010"                           ## 素直に論理名を書いておくと参照しやすい
    ] 
   ]
```
普段は１行書いてちょこっと直しますが、いきなり圧倒されると思うのでインデント入れてみやすくしておきます。



```yaml: 例:リソースのインポートのTips
###[NOTE] Subnetsを指定して　セグメントスタックでエクスポートしたロードバラン作用のサブネットA-Z をインポートする例
            Subnets:
                ##internet-facing --> [PublicSubnet1,PublicSubnet2]
                - "Fn::ImportValue": !Join [ '', [ !FindInMap [StackConfig,VPC,ImportPrefixOfVpc], !FindInMap [StackConfig,EnvLabel,!Ref EnvType], !Ref Mensu, "-segment-", "LoadBalancerSubnet010" ] ]
                - "Fn::ImportValue": !Join [ '', [ !FindInMap [StackConfig,VPC,ImportPrefixOfVpc], !FindInMap [StackConfig,EnvLabel,!Ref EnvType], !Ref Mensu, "-segment-", "LoadBalancerSubnet020" ] ]

```

## 後方参照できるスタックから、リソースをインポートする

依存するスタックとしないスタック、影響のないスタックの分割設計はシステムの展開を縦横にするためにも考慮が必要です。（こういう記事もかくといいですかね）

## CFnの適用でのリソースが変わる・変わらないを意識する

 CFnは　冪等性までしないので　ドリフト処理（手作業追加）について、機能の変更（プロパティ自体の変更）がない追加については　無視（CFnからはみえないじょうたいになります）
- 上書きされてしまうケース：
    　- RDSのインスタンスタイプの変更　・・・　単一の設定のものはCFnの値が変わります

- 上書きされないケース：
    -　SGの条件IPを追加した　・・・　差分はありますが、既存のものを上がいていないため無視される（機能としては有効だがCFn管理からはずれてしまう）
        　　- → 差分はCFnで取り込まない限り管轄されなくなってしまい横展開の際に際が発生する。
    　- Lambdaのソース自体：AWSリソースでないもの　は　CICDではないので　Lambdaのプロパティが変化するわけではないので　ソースだけ変えても古いソースのままになる。
     　　　- →バージョンやエイリアスリソースを作って、むき先を変えるような　AWSのリソースの変化を定義することでこちらは対応できるようになる。

## おまけ: NestStack は、あまり使わない。
- 好き付きあると思いますし便利と思う方もいらっしゃるかもしれません。ただ構成管理IaCするときもそうですが、初期導入時に　S3にスタックを入れて、入れ子にしたものを一気に作るための「手間と準備」がかかってしまいます。

<h1> そこで</h1>

最近出てきたのが、CloudformationのGit連携機能。
テンプレートをGItと連携することで　更新を行ってくれると言うものです。　便利な反面、更新するタイミングをGit戦略立てる必要があると思うのが強行のごろなので、この話はまた別の記事にしようと思います。

AWS CloudFormation Git 同期を使った作業: 
 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/git-sync.html

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/3c7284f7-b2cd-06a9-3c80-4ddadf91f38f.png)


## まとめ：
いくつかのTipsを共有したつもりですが　Cloudformationは、やってみると楽しい。奥が深い。シンプルに描ける。
冗長だからCDKだよ！ってのもあるかもしれません。
ただ、ちょっとした要求をこなしたい場合にCDKでは　レイヤ１、２あたりを考慮した記載が必要になったりとお作法が出てきます。あまりにお作法に踊らされてるなと思ったら、素直に　CF使えばいいじゃんとなれるよう覚えておくと良いなと思います。
実際このTipsで、数万コードとIaC構成書いてきた自負があるので是非お試しを！

## おまけ、せっかくだから、　Vogelsさんの　提唱する　フリューガルアーキテクチャー（倹約な設計をする）についても読んでみてよう。

この中には、設計の思想　（AWSはセキュリティを最高重要事項にしている。　とか　繰り返し挑戦し続けることそのためには　責任の共有モデルを理解し、成功なきおもいこみをやめ成功体験を得ることでつぎにすすもう）なんて半分自分なりの意訳で妄想を掻き立てる　７つの柱的な解説まで書いてあるサイトがあります。

https://www.thefrugalarchitect.com/

Chromeなどの日本語役機能を使うと便利ですが。１〜７の章を　６つの柱のように考え方を学びながら訳してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8216/3109db35-c019-a59a-850b-bdad5661d3fd.png)

以上！