AWS CloudFormation再入門

# 概要
CloudFormationよりTerraform派なので、理解しているようできちんと理解していなかったCloudFormationについて、最近[ServerlessFramework](https://www.serverless.com/)の利用機会が増えてきて(ServerlessFrameworkはCloudFormationがベースとなっているので)、改めて勉強したので、忘れないようにアウトプットしていく。

参考にした資料：

- [【AWS Black Belt Online Seminar】AWS CloudFormation](https://youtu.be/Viyqh9fNBjw)
- [【AWS Hands-on for Beginners】AWS 環境のコード管理 AWS CloudFormationで Web システムを構築する](https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-cfn-2020-reg-event-LP.html?trk=aws_introduction_page)

# CloudFormationとは
AWS公式の構成管理ツール(AWSサービス)でAWSにおけるIaCの手段の一つ。
クラウドインフラのIaCツールは他にもあるが、CloudFormation利用のメリットとして、以下がある。

- AWSサービスのため、ポリシーによる細かい制御やOrganizationsによる複数アカウントの一元管理が可能
- AWSサービスのため、利用している組織が多い(チームや企業が変わっても変わらず利用できる可能性が高い)
- SAMやServerlessFrameworkのベースになっているため、これらに応用できる。

AWSリソースの定義をJSONやYAML(JSONはYAMLのサブセットなので当たり前だが)で記述しておき、構成の変更や追加、さらに異なるリージョンやアカウントでの再構築を容易にできる。

![スクリーンショット 2021-02-01 0.59.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/56603142-7d81-187b-39f5-567d7e0c2087.png)

# CloudFormation利用の流れ
定義ファイルを記述し、S3に配置。
その定義ファイルを元に実際にリソースの作成や変更を実行という流れ。
もちろんこれらの操作(アップロード以降)はCLIでもマネジメントコンソールでも実施できる。

![スクリーンショット 2021-02-01 1.00.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/cd51204d-9b47-94cb-48e5-f2f31e496343.png)

# テンプレートとスタック
CloudFormationの理解に欠かせない2つの要素が「テンプレート」と「スタック」
これらについては少し深く書いていく。
![スクリーンショット 2021-02-01 1.17.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/bb3913b3-0e54-a163-67c2-635da6d41cdc.png)


## テンプレート
CloudFormationの管理対象とするAWSリソースの定義。
YAML/JSONで記述したテキストドキュメント。
![スクリーンショット 2021-02-01 1.08.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/513293f3-f18d-b960-3b8d-aedea887d4e7.png)

### テンプレートに記載する主な要素
- AWSTemplateFormatVersion
- Description
- Parameters
- Conditions
- Resources
- Outputs
![スクリーンショット 2021-02-01 1.09.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/7b5e0066-ff51-1e35-b834-2f25bb83d8c1.png)

その他にもあるが基本はこのあたり。
その他の要素については下記を参照のこと。
公式リファレンス：https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-reference.html

### Resources
管理対象とするAWSリソースの情報を記述する。
リソースタイプごとに必須とオプションのプロパティがあるので、指定可能なリソースタイプやリソースごとのプロパティについては公式リファレンスを見ながら、必要なものを記述していく。
Terraformの`resource`と似ているが、こちらは`Resources`属性の中にまとめて記述する。
![スクリーンショット 2021-02-01 1.42.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/ddb8998b-e011-dafa-8527-09da8d637538.png)

### AWSTemplateFormatVersion & Description
`AWSTemplateFormatVersion`: テンプレートの構文バージョン。IAMポリシーの`Version`のようなもの、現状一度もアップデートされていないので`2010-09-09`しか存在しない。指定無しでもデフォルトで最新バージョンとなるが、今後バージョンが上がって互換性がなくなる可能性も考え、ときのために書いておいたほうがいい。(とBlackBeltセミナーで言っていた)
`Description`: このテンプレートの対象が何なのか説明を書くことができる。動作に影響はなく、必須ではないが、保守観点で書いておいたほうがいい。
![スクリーンショット 2021-02-01 1.44.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/2384b008-9cbf-d6d3-fe1f-97336d09e0d0.png)

### Parameters
スタック構築時にユーザに指定させる値を定義。デフォルト値やデータ型(`String`や`List`など)の指定が必須。
正規表現や長さなどを指定し、入力値のチェックをさせることもできる。
Terraformの`variable`に似ているが、こちらは`Parameters`属性の中にまとめて記述する。
![スクリーンショット 2021-02-01 2.10.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/63321873-6187-11f5-a6ed-33345c9afc13.png)

### Outputs
スタック構築後に表示させる情報、自動で付与されたインスタンスIDなど表示させて、後で利用したりできる。
Terraformの`output`に似ているが、こちらは`Outputs`属性の中にまとめて記述する。
![スクリーンショット 2021-02-01 2.09.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/c6e35f94-481c-f8a7-e3bf-1620c9f3e774.png)

### Conditions
環境ごとなどで異なる定義でリソースを構築したい時に利用する。
下記例の場合、パラメータの値により構築するインスタンスタイプを切り替えている。
`Env`パラメータに`prod`が指定された場合`c1.xlarge`、`dev`の場合`m1.large`、その他の場合`m1.small`となる。
↓の記述例では組み込み関数も出てくるので、後述の組み込み関数と合わせて読むといい。

```yaml:記述例
Parameters:
  EnvType:
    Description: Environment type.
    Default: test
    Type: String
    AllowedValues: [prod, dev, test]
  
Conditions:
  CreateProdResources: !Equals [!Ref EnvType, "prod"]
  CreateDevResources: !Equals [!Ref EnvType, "dev"]

Resources:
  EC2Instance:
    Type: "AWS::EC2::Instance"
    Properties:
      ImageId: "ami-06cd52961ce9f0d85"
      InstanceType: !If [CreateProdResources, c1.xlarge, !If [CreateDevResources, m1.large, m1.small]]    
  MountPoint:
    Type: "AWS::EC2::VolumeAttachment"
    Condition: CreateProdResources
    Properties:
      InstanceId: !Ref EC2Instance
      VolumeId: !Ref NewVolume
      Device: /dev/sdh
  NewVolume:
    Type: "AWS::EC2::Volume"
    Condition: CreateProdResources
    Properties:
      Size: 100
      AvailabilityZone: !GetAtt EC2Instance.AvailabilityZone
```

### 組み込み関数
テンプレート内では事前定義されたいくつかの組み込み関数を利用することができる。
詳細は下記公式リファレンスを参照のこと。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

#### Fn
`Fn::~`の利用例
組み込み関数を利用することで、リソースの名前や`Outputs`で出力させる文字列を動的に生成できる。
基本は`属性名`が`関数名`で`値`として`引数`を指定する。引数が複数の場合`配列`で指定する。
短縮形の書き方もあり、`Fn::`の代わりに`!`を書き、`関数名`のあとにスペースを挟んで`引数`を指定する。
短縮形の場合も引数が複数の場合、`配列`で指定する。
基本は短縮形だけ覚えておけば困らなそう。

例えば`Fn::Sub`は単純に文字列の中に変数を展開する関数。
下記の例では`Parameters`で受け取った`Env`文字列とCloudFormationを実行するリージョンに応じたバケット名をつけている。

```yaml:記述例(Sub)
Parameters:
  Env:
    Type: String

Resources:
  DataBucket:
    DeletionPolicy: Retain
    Type: AWS::S3::Bucket
    Properties:
      BucketName:
        Fn::Sub:
          src-${AWS::Region}-${Env}
```

↑の`Properties`以下を短縮形にすると↓になる。

```yaml:記述例(Sub短縮形)
    Properties:
      BucketName: !Sub src-${AWS::Region}-${Env}
```

もう一つの例として`GetAtt`は`Resources`で定義したAWSリソースから様々な属性値を取得できる。
下記例の場合、`myELB`のソースセキュリティグループ名を取得しています。

```yaml:記述例(GetAtt)
Resources:
  myELB:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      AvailabilityZones:
        - eu-west-1a
      Listeners:
        - LoadBalancerPort: '80'
          InstancePort: '80'
          Protocol: HTTP
  myELBIngressGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ELB ingress group
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupName:
            GetAtt: 
              - myELB
              - SourceSecurityGroup.GroupName
```

↑の`SourceSecurityGroupName`以下を短縮すると↓になる。

```yaml:記述例(GetAtt短縮形)
          SourceSecurityGroupName: !GetAtt myELB.SourceSecurityGroup.GroupName
```

その他にも配列から添え字で値を取り出したり、文字列を結合したりできる。（配列から値を取り出すぐらい関数とか使わずかけるようにしてほしいが、しょうがない。）
`Fn::Sub`なんかもTerraformなら関数無しで変数展開してくれるのに、と思ってしまった。

#### Ref
パラメータまたはリソースの論理名(Resource定義の初めに書く名前)からID(インスタンスIDなど)を取得できる

```yaml:記述例
MyEIP:
  Type: "AWS::EC2::EIP"
  Properties:
    InstanceId:
      Ref:
        MyEC2Instance
```

↓こちらも短縮できる

```yaml:記述例(短縮形)
MyEIP:
  Type: "AWS::EC2::EIP"
  Properties:
    InstanceId: !Ref MyEC2Instance
```

`Ref`と`Fn`を組み合わせる事もできる
↓アカウントIDを取得してIAMロールのARNの文字列を作る例

```yaml:記述例(YAML)
Fn::Join:
- :
-
 - arn:aws:iam:
 - !Ref AWS::AccountId
 - role/xxxRole
```

↓短縮

```yaml:記述例(YAML短縮形)
!Join
- :
-
 - arn:aws:iam:
 - !Ref AWS::AccountId
 - role/xxxRole
```

↓ワンライン

```yaml:記述例(ワンライン)
!Join [:, [arn:aws:iam:, !Ref AWS::AccountId, role/xxxRole]]
```


### 疑似パラメータ
テンプレート内では事前定義されたいくつかの変数を利用することができる。
リージョンやアカウントIDなどにこの変数を指定することで複数リージョン間や複数アカウント間でのテンプレートの共有、共通環境の構築ができる。
詳細は下記公式リファレンスを参照のこと。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html

```yaml:記述例
Outputs:
  MyStacksRegion:
    Value: !Ref "AWS::Region"
  MyStackAccount:
    Value: !Ref "AWS::AccountId"
```

### Dynamic References
環境ごとに異なるが複数のサービスから共通で使うような変数や、パスワードなどの定義ファイルに記載したくない値はSSMやシークレットマネージャーに登録しておき、CloudFormationの実行時に取得させることができる。
![スクリーンショット 2021-02-01 1.33.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/68dd7332-bd29-e2ac-06b5-1360e0120ae8.png)

例えば、下記の例ではRDSのユーザ名やユーザパスワードなどのセキュアな情報はテンプレートに直接記載したくないので、シークレットマネージャーに登録しておき`Dynamic References`で動的に設定している。

```yaml:記述例
  MyRDSInstance:
    Type: 'AWS::RDS::DBInstance'
    Properties:
      DBName: MyRDSInstance
      AllocatedStorage: '20'
      DBInstanceClass: db.t2.micro
      Engine: mysql
      MasterUsername: '{{resolve:secretsmanager:MyRDSSecret:SecretString:username}}'
      MasterUserPassword: '{{resolve:secretsmanager:MyRDSSecret:SecretString:password}}'
```

## スタック
CloudFormationでの管理単位、同一スタック内での構築順序はスタック内のリソース同士の依存関係から自動的に決定される。
Terraformで言うとディレクトリや`workspace`の単位にあたる。
![スクリーンショット 2021-02-01 1.15.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/37c2c2c2-728e-3d13-e418-0f2756e481fe.png)

### リソースのインポート
いままでCloudFormationなど利用せずに手動や他のツールで構築したAWSリソースがあり、これからCloudFormationの管理対象にしたい場合はインポートすることもできる。
![スクリーンショット 2021-02-01 1.32.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/e1a74670-49b7-5ffb-168a-6bd7e06b8739.png)


### スタック分割について
AWSリソース管理単位の分割については、はじめにある程度うまく分割しておかないと、後々困る。
![スクリーンショット 2021-02-01 1.26.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/df51089e-7162-29a4-0ee5-6c23ef67e2ef.png)

とはいえ、分割したリソース間でも参照したい場合があるので、これを実現する「クロススタック参照」という機能もある。

![スクリーンショット 2021-02-01 1.28.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/a2fc6979-15de-ba04-724e-41ea5b37a140.png)

スタック分割については最初に紹介した資料([【AWS Black Belt Online Seminar】AWS CloudFormation](https://youtu.be/Viyqh9fNBjw))でさらに詳しく説明されているので是非視聴してもらいたい。

### スタックセット
CloudFormationではテンプレートを使用して、複数のリージョンのAWSアカウントにスタックを作成できる。
この機能をスタックセットという。
同一Organizations内のアカウントでない場合、対象の各アカウントからスタック作成を許可するIAMロールを引き受ける必要がある。
同一のOrganizations内で複数アカウントに対してリソースを作成する場合、IAMロールは必要ない。
参考：https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/stacksets-concepts.html

## スタックとリソースの保護
作成後のスタックやリソースを変更や削除から保護する方法は複数ある。
![スクリーンショット 2021-02-01 1.24.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/321ad434-d0bb-330d-1c90-0332f07be738.png)

### スタックポリシー
スタック作成時や作成後にスタック単位で変更/削除に対するポリシーを付与できる。
(作成後はCLIでしかポリシーの付与ができない。)
![スクリーンショット 2021-02-01 1.24.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/e0fbef43-d595-206b-0f0d-d14b71d8dda1.png)

文法はIAMポリシーなどのポリシードキュメントと同様
![スクリーンショット 2021-02-01 1.25.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/cd405680-c4dd-7a41-1ccb-23d895c32ea3.png)

### DeletionPolicy
スタックの削除時に一部リソースのみ保持させたい場合や、バックアップを取得させたい場合に利用する。
![スクリーンショット 2021-02-01 1.25.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/2c71e856-0b4f-2c5f-4ebd-9a4ce3c40cb4.png)

## StackSets
マルチリージョン・マルチアカウントのデプロイを行える。
![スクリーンショット 2021-02-01 1.20.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/140013/af0ed0dd-e149-a396-66fa-9f1ffd8c027d.png)