Amazon CloudFormation

##1.Amazon CloudFormationとは？##
AWSでインスタンスやVPC環境を作成するとき、コンソール上で作ろうとすると、似たような環境を何度も作ったり、大量に作ったりする時は面倒。
そんな手間を一気に解消してくれるのがCloudFormaion。

平たく言うと「自動的にAWS上で作りたいものを作ってくれる」サービスです。
自分が作りたい環境を定義したファイルを作って、その定義書を読み込んで自動で環境を作ってくれます。



##2.CloudFormation/Elastic Beanstalk/OpsWorksとの違い##

|サービス名|説明|○|×|
|----------|----|---|---|
|CloudFormation |AWSリソース管理、構築を自動化するサービスであり、JSONやYAMLで記述|自由度が高い|OSより上のレイヤはできないこともある　|
|Elastic Beanstalk |定番インフラ構成の自動構築およびAppデプロイの自動化サービス|リソースをアプリ開発に集中できる|自由度は低い|
|OpsWorks |Chefレシピを利用した構成管理サービスであり、ChefはAWSがマネージドで管理<br>Zhttps://zenn.dev/yuta28/articles/opswork-wordpress|Chefを既に利用している際に最適|OSより下のレイヤはできないこともある|



##3.ベストプラクティス##

[AWS CloudFormation ベストプラクティス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/best-practices.html)


##4.基本用語##

- テンプレート：AWSリソースをJSONやYAMLで記述したドキュメント
- スタック：テンプレートから自動構築されたAWSリソースの集合


##5.機能##

- スタックセット：複数のAWSアカウントと複数のリージョンに対してスタックを作成できる。

[[新機能] CloudFormation StackSetsを試してみた](https://dev.classmethod.jp/cloud/aws/introducing-cloudformation-stacksets/)

基本的な手順は、
１.AアカウントでAWSCloudFormationStackSetAdministrationRole
２.BアカウントでAWSCloudFormationStackSetExecutionRole
３.スタックセットを設定する。

- スタック間のリソース参照機能：参照テンプレートをエクスポートして値を抽出し、その後参照するテンプレートのインポートによりリソース参照を行うことができる。

##7.テンプレート##

- セクション
 - Parameters : 実行時に全く同じものをデプロイするのではなく、パラメータを選ぶことができる。(後でやってみます。)
 - Mappings :  実行環境によって、変わる値をMap形式で定義する。（これも後でやってみる）
 - Resources : AWSリソース群を定義する。（基本）

- 組み込み関数
 - Ref : 他の要素を参照する。
 - FindInMap : Mappingsで定義された変数を決めに行く。

- 擬似パラメータ
 - AWS:Region : テンプレートが実行されている環境に応じて変わるような値を取りに行く

[サンプルテンプレート] (https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-sample-templates.html)も準備されている。


##8.基本のCloudFormationを作成してみる##

```yml:
{
  "AWSTemplateFormatVersion" : "2010-09-09",
   "Resources" : {
      "MyVPC" : {
         "Type" : "AWS::EC2::VPC",
         "Properties" : {
          "CidrBlock" : "10.0.0.0/16",
          "EnableDnsSupport" : "true",
          "EnableDnsHostnames" : "true",
            "InstanceTenancy" : "default",
            "Tags" : [ {"Key" : "Name", "Value" : "demo-VPC"} ]
          }
      },
    "MyIGW" : {
         "Type" : "AWS::EC2::InternetGateway",
         "Properties" : {
            "Tags" : [ {"Key" : "Name", "Value" : "demo-IGW"}]
          }
    },
    "AttachGateway" : {
        "Type" : "AWS::EC2::VPCGatewayAttachment",
        "Properties" : {
          "VpcId" : { "Ref" : "MyVPC" },
          "InternetGatewayId" : { "Ref" : "MyIGW" }
          }
    },
    "MySubnet" : {
         "Type" : "AWS::EC2::Subnet",
         "Properties" : {
            "VpcId" : { "Ref" : "MyVPC" },
            "CidrBlock" : "10.0.0.0/20",
            "AvailabilityZone" : "ap-northeast-1a",
            "Tags" : [ { "Key" : "Name", "Value" : "demo-Subnet" } ]
            }
    },
    "MyRouteTable" : {
         "Type" : "AWS::EC2::RouteTable",
         "Properties" : {
            "VpcId" : { "Ref" : "MyVPC" },
            "Tags" : [ { "Key" : "Name", "Value" : "demo-RT" } ]
          }
    },
    "SubnetAttache" : {
       "Type" : "AWS::EC2::SubnetRouteTableAssociation",
       "Properties" : {
          "RouteTableId" : { "Ref" : "MyRouteTable" },
          "SubnetId" : { "Ref" : "MySubnet" }
       }
    },
    "MyRoute" : {
         "Type" : "AWS::EC2::Route",
         "Properties" : {
            "RouteTableId" : { "Ref" : "MyRouteTable" },
            "DestinationCidrBlock" : "0.0.0.0/0",
            "GatewayId" : { "Ref" : "MyIGW" }
          }
    }
  }
}
```

"Type" をそれぞれ書く必要があり、設定は "Properties" で書く。
[AWS リソースおよびプロパティタイプのリファレンス](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) を見てかけばいい。
シンプルそう。
慣れだろう。

"MyVPC"を例に見ていくと、
"Type" : "AWS::EC2::VPC"
は「10.0.0.0/16のCIDRでVPCを作り、作ったVPCのNameタグはdemo-VPCにする」
となる。

ちなみに "MyVPC" とかは論理IDなので任意のもので可。

"Ref" は参照するということ。

"AttachGateway"を例に見ていくと、
"VpcId" : { "Ref" : "MyVPC" },
は、「インターネットゲートウェイのIDはMyVPCを参照する」
となる。


実際に作成されたことがわかる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/280929/4c47aa75-6043-41cf-0ef3-52ed4db22f77.png)


##7.Parametersを試してみる##

```yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Test cfn Template

Parameters:
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
    Description: Select EC2 instance type.

Resources:
  cfnVpc:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: '192.168.0.0/16'
      Tags:
        - Key: 'Name'
          Value: 'cfn-vpc'
  cfnSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      CidrBlock: '192.168.1.0/24'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: 'Name'
          Value: 'cfn-subnet'
      VpcId: !Ref cfnVpc
  cfnInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: 'Name'
        Value: 'cfn-igw'
  cfnAttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref cfnVpc
      InternetGatewayId: !Ref cfnInternetGateway
  cfnRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: 'Name'
          Value: 'cfn-rt'
      VpcId: !Ref cfnVpc
  cfnRoute:
    Type: AWS::EC2::Route
    DependsOn: cfnInternetGateway
    Properties:
      RouteTableId: !Ref cfnRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref cfnInternetGateway
  cfnSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref cfnSubnet
      RouteTableId: !Ref cfnRouteTable
  cfnEC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: "ami-3bd3c45c"
      InstanceType: !Ref InstanceType
      SubnetId: !Ref cfnSubnet
      BlockDeviceMappings:
        - DeviceName: '/dev/xvda'
          Ebs:
            VolumeType: 'gp2'
            VolumeSize: 8
      Tags:
        - Key: 'Name'
          Value: 'cfn-ec2-instance'
      SecurityGroupIds:
        - !Ref cfnSecurityGroup
  cfnSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "cfnSecurityGroup"
      VpcId: !Ref cfnVpc
      Tags:
        - Key: 'Name'
          Value: 'cfn-ssh-sg'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: 0.0.0.0/0
```

Parameters でインスタンスタイプを選択できるようにし、
InstanceType: !Ref InstanceType と定義すると、
スタック実行時に↓のような画面が選択できるようになる。
![スクリーンショット 2019-03-30 19.06.53.png](https://qiita-image-store.s3.amazonaws.com/0/280929/5aebde84-2e72-85ab-d086-3dd2bdfda6ba.png)


##8.Mappingsの動作を確認してみる##

```yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Test cfn Template

Parameters:
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium
    Description: Select EC2 instance type.
  KeyPair:
    Description: Select KeyPair Name.
    Type: AWS::EC2::KeyPair::KeyName

Mappings:
  RegionMap:
    us-east-1:
      hvm: "ami-a4c7edb2"
    ap-northeast-1:
      hvm: "ami-3bd3c45c"

Resources:
  cfnVpc:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: '192.168.0.0/16'
      Tags:
        - Key: 'Name'
          Value: 'cfn-vpc'
  cfnSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      CidrBlock: '192.168.1.0/24'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: 'Name'
          Value: 'cfn-subnet'
      VpcId: !Ref cfnVpc
  cfnInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: 'Name'
        Value: 'cfn-igw'
  cfnAttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref cfnVpc
      InternetGatewayId: !Ref cfnInternetGateway
  cfnRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: 'Name'
          Value: 'cfn-rt'
      VpcId: !Ref cfnVpc
  cfnRoute:
    Type: AWS::EC2::Route
    DependsOn: cfnInternetGateway
    Properties:
      RouteTableId: !Ref cfnRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref cfnInternetGateway
  cfnSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref cfnSubnet
      RouteTableId: !Ref cfnRouteTable
  cfnEC2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: !FindInMap [ RegionMap, !Ref "AWS::Region", hvm ]
      InstanceType: !Ref InstanceType
      SubnetId: !Ref cfnSubnet
      BlockDeviceMappings:
        - DeviceName: '/dev/xvda'
          Ebs:
            VolumeType: 'gp2'
            VolumeSize: 8
      Tags:
        - Key: 'Name'
          Value: 'cfn-ec2-instance'
      SecurityGroupIds:
        - !Ref cfnSecurityGroup
      KeyName: !Ref KeyPair
  cfnSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: "cfnSecurityGroup"
      VpcId: !Ref cfnVpc
      Tags:
        - Key: 'Name'
          Value: 'cfn-ssh-sg'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
```

ImageIdをami-***のようにそのま書くと、amiはリージョンごとにしか使えないようになっている。
そこで、Mappingsで利用する複数のリージョンを記載しておき、
FindInMap [ RegionMap, !Ref "AWS::Region", hvm ] で定義しておけば、Mappingsに記載したリージョンでこのテンプレートを利用できることとなる。


##9.NestedStacks　と　Cross Stack Reference##

- NestedStacks
 - 各テンプレートの共通部分を作成し、スタックの親子関係を実現。

- Cross Stack Reference
 - あるスタックから値をエクスポートし、別のスタックで参照し、多層またはサービス指向アーキテクチャを実現する。
 - [Outputsセクション](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html)を使って、他のスタックから参照したい値をエクスポート >> 別のスタックで、ImportValue関数を使って値を参照する。
 - Cross Stack は、AWSアカウントの場合、エクスポート名はリージョン内で一意である必要があります。

　例えば、東京リージョンの1人の従業員がCloudFormationスタック「Health1」を作成し、「NewStack」を出力しました。 
　別の従業員がシンガポールリージョンで別のアプリケーション「Health2」のCloudFormationスタックを作成し、「NewStack」という名前の出力をエクスポートしました。 
　最初の従業員は、シンガポールリージョンにCloud Formationスタック「Health1」をデプロイしようとしてもエラーになります。


##10.Dynamic Reference##

- CloudFormationにそのまま値を入れず、AWS Systems Managerのパラメータストア と AWS Secrets Manager に格納されたデータを動的に参照したい場合に利用する。


##11.Change Set(変更セット)とDrift Detection(ドリフト検出）##

- Change Set(変更セット)
 - 現状のスタックとの変更点を確認してから、更新することができる。
 - 確認できるのは、下記の画面の通り何のリソースが変更されるかである。

![スクリーンショット 2021-04-11 15.27.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/aec75bac-6f1a-c929-b64a-a41287eab24f.png)

- Drift Detection(ドリフト検出)
 - テンプレートと現状のリソースの差分を検出する。
 - 例えば、CloudFormation外で、何かのリソースの設定を変更してしまったなど、手動で変更してしまったものをドリフトという機能で差分を確認できます。
 - 検出できるリソースは全てではないので、[ドキュメント](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import-supported-resources.html)を確認する必要がある。

![スクリーンショット 2021-04-11 14.41.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/d26e7d0c-7aa7-5f56-411c-f0c8fca1a019.png)

##12.Rollback Triggers##
スタックの作成、更新中にアプリケーションの状態をAWS CloudFormationでモニタリングし、指定したアラームの閾値を超過した場合に、そのオペレーションをロールバック。


##13.StackSets##
1つのテンプレートを複数のAWSアカウントおよび複数のリージョンに展開することが可能。


##14.保護##

謝って削除することを防ぐために複数の機能があります。

- ユーザーのIAMポリシー
 - そもそもユーザーに触らせない。

- スタックの削除保護
 - スタック全体が誤って削除されるのを防ぐ。 

![スクリーンショット 2021-04-11 15.49.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/f700a89b-b6ca-6807-ca43-7e6f9a0f91d0.png)

- スタックポリシー
 - スタックの更新中にスタックのリソースが意図せずに変更、削除されるのを防ぐ。
 - スタックポリシーファイルの例は[ドキュメント](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html)を参考に。

- リソースの[DeletionPolicy 属性](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html)
 - スタックが削除された際にリソースをどうするかをテンプレートのリソースに記載する。

|オプション|説明|
|---|---|
| Delete |削除|
| Retain |保持|
| Snapshot |スナップショットを取得する|


##15.リソースのインポート##

- 手動で作成したAWSリソースをCloudFormationにインポートして管理可能。
- リソースをスタックから切り離し、別の管理下に移動することも可能。


##16.リンク##
[AWS CloudFormation を開発・テストする際に役立つツール纏め](https://qiita.com/leomaro7/items/b95880f137c63881f164)