AWS CloudFormationの書き方　初歩

# 目的
AWS初心者の方でドキュメントの見方が分からない為、色々なブログに書いてある抜粋されたテンプレートを参考にしながら書いている方向けに
AWSの公式ドキュメントを参考にしながら、CloudFormationのテンプレートを書けるようになることが、今回の目的です。
今回はyamlで書きますが、jsonでもドキュメントの見方は変わらないです。

# テンプレートの構成要素
```ec2.yml
AWSTemplateFormatVersion: 2010-09-09
Description: ---
Metadata: 

Parameters: 

Mappings: 

Conditions: 

Resources: 

Outputs:
```
- AWSTemplateFormatVersion
テンプレートのフォーマットバージョンを指定します。
現在の最新は公式ページを見ることで確認できます。
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/format-version-structure.html
- Metadata
テンプレートの付加情報を記述します。
コンソールから見た時のパラメータの順番などもここで制御できます。
- Parameters
スタックを作成する時に値を指定したい場合などに、パラメータとして受け取ります。
受け取った値をResourcesで宣言するリソースの設定値に指定することも可能です。
- Mappings
キーと値をマッピングすることができます。
キーがxxxの場合は値はyyyである。のように紐づけた管理表のようなものを記述することができます。
- Description
テンプレートの説明を記述します。
- Conditions
IF分岐をする場合などに使用します。
入力パラメータで受け取った値がxxxならtrueを返し、yyyならfalseを返却する等の条件を記述します。
- Resources
AWSリソースを宣言する本文です。
作成したいAWSサービスのプロパティに沿って記述します。
- Outputs
スタック外にリソースの情報をアウトプットします。
Aスタックで宣言しているリソースのIDをBスタックで使用したい場合などに使用します。


この中で必須の要素が**AWSTemplateFormatVersion**と**Resources**になります。
その他はオプションとして、必要な時に使用します。
Parametersは良く使用すると思うので、今回は必須要素とParametersを使用して説明していきます。

# テンプレートの作成
今回は例として、EC2とそれにアタッチするSecurityGroupを作成するテンプレートを作成したいと思います。

## EC2
- ### 指定できるプロパティを調べる
まず作成したいAWSサービスで何が設定できるのかを調べます。
※CloudFormation 「サービス名」とかで検索できます。

検索するとこのような公式ドキュメントがヒットすると思います。
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html
![スクリーンショット 2023-06-19 19.38.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/e605a2cd-3a2a-fabe-9030-2b3592d8faa1.png)
Type:は何のリソースかを表しています。(今回は大枠がEC2で、その中のInstanceです。)
Properties:配下がEC2で指定できるプロパティです。
それぞれ説明があるので、必要に応じて使用します。
※必須プロパティもあるので、注意してください。

- ### 必要なプロパティを抜粋し、作成
```ec2.yml
AWSTemplateFormatVersion: 2010-09-09

Parameters: 
  ImageId:
    Type: AWS::EC2::Image::Id
  IamRole:
    Type: String
  VpcId:
    Type: String
  SubnetId:
    Type: String
  VolumeSize:
    Type: Number
Resources: 
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref ImageId
      InstanceType: "t2.micro"
      IamInstanceProfile: !Ref IamRole
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref SecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref VolumeSize
            VolumeType: gp3
            DeleteOnTermination: true
            Encrypted: false
      Tags:
        - Key: Name
          Value: my-instance

```
今回は、これらを設定して作成したいと思います。
**Parameters**
AMI、インスタンスにアタッチするIAMロール、配置VPCのID、配置サブネットのID、EBSのサイズをスタック作成時に指定できるように記述します。

**Resources**
下記4つはParametersで受け取った値を組み込み関数の1つであるRef関数※1を使用して取得しています。
1. ImageId
1. IamInstanceProfile
1. SubnetId
1. VolumeSize

- SecurityGroupIds
あとで作成するセキュリティグループのIDをRef関数※1で取得して設定しています。
ドキュメントを確認すると 「- String」となっています。こちらは文字列配列を指定することを意味します。
![スクリーンショット 2023-06-20 20.46.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/030db8c6-f43c-e401-c3b6-5f25f4341368.png)
今回は1つしか指定していませんが、複数のSecurityGroupIdを指定する場合は、下記のように書きます。
```yml
      SecurityGroupIds:
       - SecurityGroupId1
       - SecurityGroupId2
```
jsonだとこうなります。
```json
"SecurityGroupIds":["SecurityGroupId1","SecurityGroupId2"]
```
<br>

- BlockDeviceMappings
ドキュメントを確認すると「- BlockDeviceMapping」となっています。
こちらは「BlockDeviceMapping」の配列を指定することを意味します。
設定値については、BlockDeviceMappingをクリックすることで確認することができます。
![スクリーンショット 2023-06-24 14.11.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/63893f75-266a-db02-8be3-dd985ea66724.png)
![スクリーンショット 2023-06-24 14.16.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/8273b794-fc93-f503-cc21-dd9bd1a37053.png)
Ebs:の箇所は配列ではなく、EbsというKeyに対して、複数のkey:valueを指定します。
「-」はつけずに、インデント(先頭の空白)をつけて記述していきます。
![スクリーンショット 2023-06-24 14.15.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/41e790ba-8dd0-2984-d06e-18348f8fa790.png)

BlockDeviceMappingsは配列指定なので、こちらも複数指定することが可能です。
その場合は、このようにBlockDeviceMappingの要素を複数記述することになります。
```yml
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref VolumeSize
            VolumeType: gp3
            DeleteOnTermination: true
            Encrypted: false
        - DeviceName: /dev/xvdb
          Ebs:
            VolumeSize: 8
            VolumeType: gp3
            DeleteOnTermination: true
            Encrypted: false
```
<br>

- Tags
BlockDeviceMappingsと同じ要領でTagの要素を記述していきます。

<font color="Red">※1</font>
Ref関数は、基本的に宣言している論理名を引数として渡すと対応した値を取得することができます。
取得できる値はサービスによって変わりますが、それぞれのドキュメントの「Return values」を確認することで何が取得できるのかがわかります。
例えば、今回のSecurityGroupの場合だと、「resource ID」とあるので、SecurityGroupのIDを取得できることがわかります。
![スクリーンショット 2023-06-20 20.28.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3472762/1d3b899c-2b2a-f541-3693-fdad5710fa6f.png)
Parametersの論理名を指定した場合は、パラメータの値を取得します。


## SecurityGroup
EC2でSecurityGroupを使用しているので、同じ要領で、
EC2.ymlにSecurityGroupの記述を追加します。

```ec2.yml
AWSTemplateFormatVersion: 2010-09-09

Parameters: 
  ImageId:
    Type: AWS::EC2::Image::Id
  IamRole:
    Type: String
  VpcId:
    Type: String
  SubnetId:
    Type: String
  VolumeSize:
    Type: Number
Resources: 
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref ImageId
      InstanceType: "t2.micro"
      IamInstanceProfile: !Ref IamRole
      SubnetId: !Ref SubnetId
      SecurityGroupIds:
        - !Ref SecurityGroup
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref VolumeSize
            VolumeType: gp3
            DeleteOnTermination: true
            Encrypted: false
      Tags:
        - Key: Name
          Value: my-instance

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupName: "my-sg"
      SecurityGroupEgress: 
        - CidrIp: "0.0.0.0/0"
          FromPort: -1
          ToPort: -1
          IpProtocol: "-1"
      SecurityGroupIngress: 
        - CidrIp: "0.0.0.0/0"
          Description: "https"
          FromPort: 443
          ToPort: 443
          IpProtocol: "tcp"
      Tags: 
        - Key: "Name"
          Value: "my-sg"
      VpcId: !Ref VpcId

```
今回は
アウトバウンド：すべて
インバウンド：httpsのみ許可
で作成しています。



# まとめ
ドキュメントを参考にする際には下記を気をつける。
- 設定可能なプロパティを確認する。
- 入れ子になっている要素はリンクを踏む。
- 配列なのかを確認する。
- Refで何が取得できるのかを確認する。

# 最後に
CloudFormationはとても面白いサービスです。
今回はRef関数のみ、組み込み関数を使用しましたが、他にもSubや、GetAtt、Split、Joinなどさまざまな関数が用意されています。
また、カスタムリソースを使用して、CloudFormationではサポートされていない挙動を外部でLambda関数として定義しておいて、スタックの作成、更新、削除のタイミングでLambdaを起動する等も可能です。

気になる方は調べてみることをお勧めします。