AWS CloudFormation

##概要
AWSリソースをコード化してくれるサービスで、テキストファイル(=テンプレート)にリソースの構造を記述してCloud Formationのサービス画面で読み込ませると、AWSリソースを自動作成してくれる。
自動生成だけでなく、変更や削除もできる。

テンプレートのファイル形式はXML,JSONなどあるが、YAMLをオススメしています。
理由: コメントの記述が可能な点、人間にとって読みやすい点です。

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

##スタック
テンプレートを読み取って作成されたAWSサービスの集合体のことを**スタック**と呼びます。
テンプレートの構文途中にエラーがあっても中途半端に生成されることはなく、自動でロールバックされる。

**更新時**
テンプレートを新しく更新して改めてCloudFormationに読み込ませることで、バージョン2のスタックに更新される。

**削除時**
スタック単位で作成していたもの全てが削除される。

##Drift Detection
テンプレートでスタックを作成した後、GUI(手動)でS3を削除した際差分が生まれてしまうケースがあります。
この差分を検知してくれる機能をDrift Detectionと言い、どこに差分があるのか検出できます。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/detect-drift-stack.html

##CloudFormationテンプレート
テンプレートはAWS独自の記述式となっていて、9つのセクションで分かれている。

1,**AWSTemplateFormatVersion**
　このテンプレートのバージョン
2,**Description**
3,**Metadata**
4,**Parameters**
　パラメーターを設定する領域
5,**Mappings**
6,**Conditions**
7,**Transform**
8,**Resources**
　重要なのはResourcesセクションとなっている
9,**Outputs**

##Resourcesセクション
![CloudFormation2.004.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207512/376e18e1-77c8-805c-f382-55145f40fece.jpeg)

VPC,EC2,S3など作成したいAWSリソースに対してそれぞれ記述していく。
茶色の文字がCloudFormationで決められた形式で赤文字がユーザー側で記載していく。

Logical IDは論理ID(名前のようなもの)。
Typeは作成するAWSリソースを記載。
PropertiesはTypeについての詳細を記述。

この3セットを作成するリソース分記述する。
必要なプロパティなどは公式ドキュメントを読みながら記述していく。

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html

##作成デモ用テンプレートファイル

<details><summary>テンプレートファイル (ver 1)</summary><div>

```yml
AWSTemplateFormatVersion: 2010-09-09
Resources: 
  MyVPC2:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.8.0/21
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: MyVPC2fromCF
  subnetName:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      VpcIp: !Ref MyVPC2
      CidrBlock: 10.0.8.0/24
      Tags:
        - Key: Name
          Value: subnet1formCF
```
</div></details>

<<手順>>
AWS CloudFormation < スタックの作成 < 新しいリソースを使用(標準)
スタックの作成
テンプレートの準備完了状態にチェック、
テンプレートソースはテンプレートファイルのアップロードにチェック、
上のyamlファイルをアップロードし、次へを選択
(一旦S3にアップロードされて、且つ形式などに誤りがある際はエラーが出る)

スタックの名前をつけて次へを選択

##テンプレート更新
作成したテンプレートファイル(ver 1)で作成したVPCと紐づいた状態で
そのファイルに追加で記述したものを、CloudFormationにアップロードすることで更新される。
更新方法は作成時と似ていて
該当のスタックを選択し、更新するボタンを選択　< 既存テンプレートを置き換えるを選択
ここでテンプレートの指定にテンプレートのアップロードを選択してファイルをアップロードします。(← この辺りは作成時と同じ)

<details><summary>テンプレートファイル (ver 2)</summary><div>

```yml
AWSTemplateFormatVersion: 2010-09-09
Resources: 
  MyVPC2:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.8.0/21
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: MyVPC2fromCF
  subnetName:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      VpcId: !Ref MyVPC2
      CidrBlock: 10.0.8.0/24
      Tags:
        - Key: Name
          Value: subnet1fromCF
  secGroupName:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: GroupName-SG
      GroupDescription: GroupDescription-SG
      VpcId: !Ref MyVPC2
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags:
        - key: Name
          Value: SGfromCF
```


必要なプロパティは公式を検索して記述していく

</div></details>

##Ref関数

組み込み関数 !Ref (リファレンスの略で参照の意味)
同じテンプレートファイル内で作成されたパラメータまたはリソースの値を返す。

同ファイル内にMyEC2Instanceが使用されていることを条件に
プロパティのインスタンスIDにMyEC2Instanceという論理IDを同一にすることで紐付けうことができる。

##クロススタック参照
実際の運用では、リソースの種類によってテンプレートファイルを分割する運用を推奨しており、上のテンプレートで作成されたVPC,subnet,SGとは別にEC2を作成するためのテンプレートファイルを作成していく。

![CloudFormation5.005.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207512/f102fda7-95c0-922e-b433-91c2ca7e3fa6.jpeg)

その中で、お互いのリソースを参照し合うような状態(例: !Ref関数)が発生する事がある。
これを**クロススタック**という。


![CloudFormation5.007.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207512/ae23b3c8-f36f-f3a3-8535-f825f019e0d6.jpeg)

###Outputsセクション
クロススタックを行うにはOutputsという他のテンプレートから参照させる準備の領域が必要でこの設定を**参照元**に書いていく。
記述に必要なものは3つ

**・論理ID(Subnet1)**
　　名前のようなもの
**・value**
　　同じテンプレート内のデータをvalueとして取得
**・Export**
　　他のテンプレートから、Nameに指定した文字をImportValueで参照できる

```yml
Outputs:
  Subnet1:
    Value: !Ref subnetName
    Export:
      Name: Subnet1Name
  Sg1:
    Value: !GetAtt secGroupName.GroupId
    Export:
      Name: SG1Name
```

###ImportValue関数
参照したいファイルで記述
別のテンプレートファイルの中身を参照する(クロススタック)際はRef関数でなく、ImportValue関数を使用する。

###GetAtt関数
参照させたいファイルで記述
論理IDに.をつなぐことで中身を指定して取得できる。
どんな戻り値を取得できるか調べる際は公式ドキュメントを確認↓ 

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html

##AWS CLIを使用したスタックの基本操作
###aws cloudformation deploy
AWSにSSH接続した後
スタック作成時のコマンド

```console
$ aws cloudformation deploy --template-file stack_s3_param.yml --stack-name s3bucketcreate
```

オプション
--template-file → テンプレートを作成するためのファイル名
--stack-name → 作成されるスタックに名前をつける

###aws cloudformation delete-stack
スタック削除時のコマンド

```console
$ aws cloudformation delete-stack --stack-name s3bucketcreate
```

--stack-name → スタック名を指定

###パラメーター値を指定する
コマンドラインで引数を指定

```yml
AWSTemplateFormatVersion: "2010-09-09"

Description: CloudTechDemoS3

Parameters:
  S3BucketName:
    Type: String
    Description: Type of this BacketName.

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${S3BucketName}
```

```console
$ aws cloudformation deploy --template-file stack_s3.yml --stack-name s3bucketcreate --parameter-overrides S3BucketName=samplecloudtechbucket
```

オプション
--parameter-overrides → S3BucketNameという変数にsamplecloudtechbucketという値を指定。

###外部設定ファイルからパラメーターを読み込む
ファイルに以下のコードを書いた状態

```s3config.cfg
S3BucketName=samplecloudtechbucket2
```

```console
$ aws cloudformation deploy --template-file stack_s3.yml --stack-name s3bucketcreate --parameter-overrides $(cat s3config.cfg)

$(cat s3config.cfg)部分でS3のバケット名に ！Sub ${S3BucketName}と記述していたことで、s3config.cfgが使用される
```

###CloudFormationで作成したS3Bucketの論理IDを出力できる

```console
$ aws cloudformation describe-stack-resource --stack-name s3bucketcreate --logical-resource-id S3Bucket
```
###設定ファイルと分けるメリット
テストや本番といった環境の変化によっても一つのテンプレートを使いまわせる。

**RDS作成**

<details><summary>Stack.yml</summary><div>

```Stack.yml
AWSTemplateFormatVersion: "2010-09-09"

Description: CloudTechDemo

Parameters:
  DatabasePassword:
    Type: String
    Description: Database password
    NoEcho: "true"
  ApplicationSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: Target subnets
  VpcId:
    Type: AWS::EC2::VPC::Id
    Description: Target VPC
  DBinboundCidrIPs:
    Type: String
    Description: SecurityGroupInboundIP

Resources:
  ApplicationDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      Engine: MySQL
      EngineVersion: 5.7
      DBInstanceClass: db.t2.micro
      AllocatedStorage: 10
      StorageType: gp2
      MasterUsername: CloudTech
      MasterUserPassword:
        Ref: DatabasePassword
      DBName: CloudTech
      VPCSecurityGroups:
        - !Ref ApplicationDatabaseSecurityGroup
      DBSubnetGroupName: !Ref ApplicationDatabaseSubnetGroup
      MultiAZ: "false"
      AvailabilityZone: !Sub ${AWS::Region}a
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-db
  ApplicationDatabaseSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Application Database Subnet Group
      SubnetIds: !Ref ApplicationSubnets
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-db-subnet-group
  ApplicationDatabaseSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub ${AWS::StackName} Application Database Security Group
      VpcId: !Ref VpcId
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 3306
        ToPort: 3306
        CidrIp: !Ref DBinboundCidrIPs
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-db-sg
```
</div></details>

<details><summary>dev.cfg</summary><div>

```dev.cfg
DatabasePassword=xxxx
ApplicationSubnets=xxxx
VpcId=xxxx
DBinboundCidrIPs=xxxx
```
</div></details>

##最後に...
デモで作成したRDSはそのままだと料金が発生するのでスタックごと削除します。

```console
$ aws cloudformation delete-stack --stack-name RDSmySQLcreate
```
スタック名がRDSmySQLcreateのスタックを削除するコマンドです。

##参考
この記事はAWS初学者を導く体系的な動画学習サービス
「AWS CloudTech」の課題カリキュラムで作成しました。

https://aws-cloud-tech.com