CfnでFargateのCI/CD環境を構築する

## はじめに
これは社内勉強会用に作成した資料です。
内容は口頭で説明することを前提として作りになっているので、内容が不足している部分があります。

---
 
## 目次
1. IaCについて
1. CloudFormationに取り組むモチベーション
1. CloudFormation ? Terraform ? AWS CDK ? 
1. CloudFormationについて
1. 今回作った環境
1. 現状の設定の課題
1. 注意事項
1. 実際に構築する
1. まとめ

---

## IaCについて

---

#### IaCとは
簡単に言えばインフラ構成を設定ファイルやプログラミング言語等で表現すること

通常のコードと同様にバージョン管理する

基本的に再利用可能である

通常のコードのデプロイと同様、変更を反映する手順が単一になり、誤りが起きにくくなる

--- 

## CloudFormationに取り組むモチベーション

---

- 単純に何回も同じ作業することを減らしたい
- 自分以外の人でも環境構築できるようにしたい
- よくある環境や設定の消し忘れとかなくしたい
- コード管理することでレビューしやすくして、インフラ担当以外にも触れる機会を増やしたい
- AWS自身が提供しているのでなくなることはなさそう

--- 

## CloudFormation ? Terraform ? AWS CDK ? 

---

単純にIaCにAWS環境で取り組む場合、いくつかのツールがある

1. CloudFormation
2. Terraform
3. AWS CDK

---

CloudFormation

- aws cliを利用する
- 設定をjson / yamlで記載し、その内容を元にインフラを構築する
- yamlで記載すればコメントも残せる
- 本家にマニュアルがあるので困ったときに情報を探しやすい
- 最近一部の設定は既存リソースをimportできるようになった

--- 

Terraform

- HashiCorpが提供しているツール (https://www.terraform.io/)
- AWS意外にもGCPやAzureなどにも対応できる
- jsonのような形のtfファイルで設定を記載する
- 既存リソースの状況を取得して設定ファイルにすることができる
- 事前にどのような変更が行われるかcliで確認できる
- workspaceの概念で環境(prod/stage/dev)を分けることができそう

---

AWS CDK

- AWSから提供されているTypescript/javascriptやPythonでインフラを構築できるツール

---

個人的に使ってみた感想ではTerraformのほうが使い勝手がよかったように感じるが、
今回はCloudFormationを使ってみる。

---

## CloudFormationについて

- https://aws.amazon.com/jp/cloudformation/

---

- stackという単位で設定を管理する
- １つの設定(yaml)に対して１つ以上のstackが対応する
- stackは複数作成でき、異なるstackの内容を参照することができる
- stack A の設定をstack Bが使用している場合、内容に変更があると、stack Aは更新できない(クロススタック(後述)の場合連動して変更してはくれない)

---

設定ファイル例(yaml)

```yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  Project:
    Type: String
    Default: "Test"

Mappings:
  RegionMap:
    ap-northeast-1:
      AZ: ['ap-northeast-1a','ap-northeast-1c']
    ap-northeast-2:
      AZ: ['ap-northeast-2a','ap-northeast-2c']

Resources:
  VPC:
    Type: AWS::EC2::VPC
    DeletionPolicy: Retain
    Properties:
      CidrBlock: 10.10.0.0/16
      Tags:
        - Key: Name
          Value: !Sub "${Project}vpc"
```

---

実行方法

```shell
aws cloudformation create-stack --stack-name test-stack \
--template-body file://./network.yaml
```

パラメータの指定

```shell
aws cloudformation **** --parameters ParameterKey=Name,ParameterValue=Hoge
```

---

設定ファイル内のパラメータの設定

```yaml
Parameters:
  Project:
    Type: String
    Default: "cfn-test-" # Defaultがない場合はコマンドラインで値を指定しないとエラーになる 
    AllowedValues: ["cfn-test-","hogehoge"]#許可する値

Mappings: #リージョンごとに異なるAMIの設定
  RegionMap: 
    us-east-1: 
      "HVM64": "ami-0ff8a91507f77f867"
    us-west-1: 
      "HVM64": "ami-0bdb828fd58c52235"

Metadata: #設定テンプレート
  Instances:
    Description: "Information about the instances"

```

---

他のstackで利用できるようにする

```yaml
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.10.0.0/16
Output:
  VPC:
    Value: !Ref VPC
    Export: { "Name" : !Sub "output-vpc"}
    #{'Fn::ImportValue': !Sub "output-vpc"}で他のstackから参照できる
```

- 出力した値はManagementConsoleのcloudformationの出力で確認できる

--- 

設定ファイル内に出てくる関数

- Fn::GetAtt
    - リソースの属性を指定するために使用.例) Arnなど

```yaml
RoleArn: !GetAtt XXXRole.Arn
```

- Fn::FindInMap
  - Mappingで定義した内容を参照するために使用

```yaml
Mappings:
  BuildNames:
    Web:
      Name: web
    App:
      Name: app

{ Value: { "Fn::FindInMap" : [ "BuildNames", "Web", "Name"]}}
```

---

- Fn::Sub
    - sprintf的な感じで使える。またparameterの指定などの際にも利用する

```yaml
Parameters:
  Project:
    Type: String
    Default: "cfn-test-"
RoleName: !Sub "${Project}build-role" # cfn-test-build-role
Value:
  !Sub
    - "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ImageName}"
    - { "ImageName": {'Fn::ImportValue': !Sub "${Project}ecr-web-image"}}

```

- Fn::ImportValue
  - 他の設定ファイルで指定した値を参照する

```yaml
Value: {'Fn::ImportValue': !Sub "${Project}test-output-value"}
```

ほかにもあるので詳しくは、、 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

---

- Fn::Select
  - リストから指定した添字の番号の値を参照する

```
AvailabilityZone: !Select 
  - 0
  - Fn::GetAZs: !Ref 'AWS::Region'
```

** Fn::GetAZsは指定したリージョンのAZのリストを取得する関数

---

設定の構成例

- 単一のyaml
- 単一のyaml,パラメータの指定
- 複数のyaml(クロススタック)
- 複数のyaml(スタックのネスト)

---

単一のyaml

![Untitled Diagram-base.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213799/49cea6c1-9043-6483-4eee-7c4ed0377828.png)

---

単一のyaml,パラメータの指定

![Untitled Diagram-parameter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213799/14541ed1-604c-ed2c-91f0-83875a977270.png)

---

複数のyaml(クロススタック)
![Untitled Diagram-クロススタック.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213799/ac98e58f-acd4-18de-de16-1043c8d50cb7.png)

---

複数のyaml(スタックのネスト)

![Untitled Diagram-スタックのネスト.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213799/5452487e-c31c-1f25-cc91-825769d4ae65.png)

---

- 今回試しに環境を作ってみて、スタックのネストが管理はしやすいと感じた
  - ファイルを分割できること
  - 他のスタックの内容を参照できる
  - 指定したParameterをそれぞれのスタックに引き渡せること

---

##  実際に環境を作ってみる

---

## 今回試しに作る環境

![test_cloudformation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213799/9cdd450e-e020-e74a-07fe-5c280d5d64b9.png)

---

##  デモ

---

## 今後の課題

- CloudFormationで出来なかったDeploymentGroupの作成をCloudFormationで

- ECS Serviceを更新すると、影響がデプロイの設定にもあるので同じstackにするなど連動するようにする

- CodeBuild内でECS ClusterやServiceの指定を環境変数で行っているが、無理やり感があるのでスマートなやり方を探して改善

- prod/staging/devなどの環境を分けることを考慮した内容にする(スタックのネストかな)

---

## わかったこと

---

## わかったこと

- 実際に自分でGUIでも構築してみること
  - 当然だけど、、MangementConsoleとかで実際に構築したことがないと設定内容が理解できない

- 新しいリソースは対応していないので、全てをCloudFormationで管理しようとしない

- updateする際にstack名を誤ると、もとの内容が消えるので注意
    - Conditionで条件分岐させたときも同様

- 間違って消えると困るリソースはDeletionPolicyを指定すること

- DeletionPolicyを設定していると、他のstackとの依存関係で削除できないこともある

---

## わかったこと(続き)

- 本番で構築するときはupdate-stackではなく、create-change-set => execute-change-setで差分を確認して反映すること(terraformでいうplanで変更を確認すること)

- 構築時のエラー情報を確認するためには、結局ManagementConsoleみた方が早い

- 最初は手動であとから柔軟に対応するならTerraformのほうが便利そう

- 実際に構築したことないと、特に権限の設定の確認は難しい

- バージョン管理できるので差分の確認(レビュー)がしやすくて良い
    - コードを見れば内容がわかるのでManagementConsoleをみなくても良い

---

## わかったこと(続き2)

- リソースによって条件があるので要注意
  - ECS Service名を指定している場合、CloudFormationではUpdateはできない??

- 他のstackで使用している出力の内容が変わる場合、updateはできない
  - 更新するたびに内容がかわるようなものに関しては１つのCloudFormation stackにする

- Parametersで名前を指定する場合でも、stackは別名にする必要がある。同名を指定すると上書きされる。

---
 
## まとめ

---

##  まとめ

- 変更予定の確認等、正直terraformのほうがやりやすい部分がある

- CloudFormationで全てやろうとしてもできない場合があるので、固執しない
    - ECS (blue/greenデプロイ)はCloudFormation対応していない？？[参照](
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-codedeploy-deploymentgroup-loadbalancerinfo.html)

- prod/staging/devを何でわけるのがベストかは要検討(region?vpc?アカウント?)

- 実際に自分でGUIで構築して内容を理解してからCloudFormationを使ったほうが理解しやすい
  - 初めて触る場合はVPCやSubnetの設定で試すとやりやすい

- 最初に準備するのは大変(特に権限周り)だけど、１回準備してこまごまと修正するだけならそこまで大変ではないはず。。

- いろいろ面倒はあるけど、レビューしやすい、共有しやすいなどメリットがあるので導入する価値はある