初めてAWS Cloudformationをさわる人へ_正道編

## 目次
1.はじめに
2.テンプレート作成前準備
3.テンプレート例
4.テンプレートの構成説明
5.関数
6.AWS公式マニュアルの見方
7.ちなみに
8.おわりに

## はじめに
前回はAIによるCloudFormationテンプレートの作成方法を邪道編としてアップしました。
しかし、AIにテンプレートを作成してもらう場合でも、作成物の真偽を人間側で判断する必要があります。
今回は真偽判断や作成物を手直しをできるよう構成や組み込み関数などの基礎をまとめました。

https://qiita.com/super_ucchi/items/a6b6fa5c7ed0c9224164

## テンプレート作成前準備
#### コードエディターをインストール
今回はVisual Studio Codeを利用します。
Visual Studio Codeのインストール方法は下記を参照。

https://qiita.com/YurimyMiyu/items/5ce7821f1e3e5905c3ee
 
#### コードチェックアプリインストール
今回はCloudFormation Linterを利用します。
CloudFormation Linterのインストール方法は下記を参照。

https://qiita.com/ita-k/items/31d640d708925c0ae838

#### コード管理ツールをインストール
テンプレートを管理するためコード管理のツールが有用です。
今回は使いませんが、普段はgitを使用しています。


## テンプレート例
VPC、PublicSubnet、InternetGateway、VPCFlowlogsというシンプルな構成を作成するテンプレートを例とします。
下記はアーキテクチャイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3803480/e8e7de51-5965-4f22-9bd4-610ef06ff821.png)

自力でテンプレート作成しました。作成するリソースはAIに依頼した内容と同様です。
この自力作成テンプレートを使い、セッションや関数について説明します。
テンプレートは下記のとおりです。

```yaml:VPC,PublicSubnet,InternetGateway
AWSTemplateFormatVersion: '2010-09-09' 
Description: Create VPC,InternetGateway,PublicSubnet


# ------------------------------------------------------------#
# Meta Data
# ------------------------------------------------------------#
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: VPC Settings
        Parameters:
          - VPCName
          - VPCCIDR
          - PublicSubnetCIDR
          - S3BucketName


# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  VPCName:
    Type: String
    Default: test-qiita

  VPCCIDR:
    Type: String
    Default: 10.10.0.0/16

  PublicSubnetCIDR:
    Type: String
    Default: 10.10.51.0/20
  
  S3BucketName:
    Type: String
    Description: Please fill in the bucket name for collecting VPC Flow Logs.


Resources:
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  # VPC Create
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Ref VPCName

  VPCFlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      ResourceId: !Ref VPC
      ResourceType: VPC
      TrafficType: ALL
      LogDestinationType: s3
      LogDestination: !Sub arn:aws:s3:::${S3BucketName}

  # ------------------------------------------------------------#
  #  InternetGateway
  # ------------------------------------------------------------#
  # InternetGateway Create
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub InternetGateway-${VPCName}

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # ------------------------------------------------------------#
  #  Subnet
  # ------------------------------------------------------------#
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetCIDR
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub subnet-${VPCName}-public

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub route-${VPCName}-public

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: VPC
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGateway

  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable


# ------------------------------------------------------------#
# OutPut Parameters
# ------------------------------------------------------------#
Outputs:
  VPC:
    Description: VPC ID
    Value: !Ref VPC

  PublicSubnetId:
    Description: Public Subnet ID
    Value: !Ref PublicSubnet

  InternetGatewayId:
    Description: Internet Gateway ID
    Value: !Ref InternetGateway
```


## テンプレートの構成説明
テンプレートのセクションは6つ程度あります。Resourceというセクションのみ必須です。
テンプレートを元によく使う4つのセクションを説明します。
より詳しく知りたい方は下記のAWS公式ドキュメントを参照ください。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-anatomy.html

#### Metadata（任意）
テンプレートに関する追加情報を提供するためのセクションです。
また、"AWS::CloudFormation::Interface"を使うことでParametar（任意）のグループ化とラベル付けを行えます。
今回はVPC Settingsでひとまとめにしたかったのでセクションを作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3803480/690147e9-2cef-44ac-8f08-5f666006c036.png)
```yaml:Metadataセクション
# ------------------------------------------------------------#
# Meta Data
# ------------------------------------------------------------#
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: VPC Settings
        Parameters:
          - VPCName
          - VPCCIDR
          - PublicSubnetCIDR
          - S3BucketName
```

ちなみに下記のようなテンプレートだとキャプチャのような画面になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3803480/c8015c7c-6710-430e-9524-639a6712b71b.png)

```yaml:Metadataセクション
# ------------------------------------------------------------#
# Meta Data
# ------------------------------------------------------------#
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: VPC Settings
        Parameters:
          - VPCName
          - VPCCIDR
          - PublicSubnetCIDR
      - Label:
          default: S3 Flowlogs Settings
        Parameters:
          - S3BucketName
```



#### Parametar（任意）
Parametarセクションは、スタックの作成時にユーザーが入力できる値を定義します。
今回の場合だとVPC名とCIDRを任意に設定できるようにし、VPCフローログの保存先のバケット名を登録するようにしました。
ここで入力された値は、のちのResourcesセクションで!Refや!Subなどの組み込み関数を使って参照します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3803480/e136f38c-1234-4465-ad7c-ac7b4a3b1d75.png)

```yaml:Parametarセクション
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  VPCName:
    Type: String
    Default: test-qiita

  VPCCIDR:
    Type: String
    Default: 10.10.0.0/16

  PublicSubnetCIDR:
    Type: String
    Default: 10.10.51.0/20
  
  S3BucketName:
    Type: String
    Description: Please fill in the bucket name for collecting VPC Flow Logs.
```


#### Resource（必須）
Resourceのことセクションのみ必須です。
Resourceセクションでは、作成または管理したいAWSリソースをリストし、それぞれのリソースに対して設定やプロパティを指定します。
作成リソースの前に
```yaml:見やすさ重視
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
``` 
  などと書いているのは私の好みです。

作成されたリソースはCloudFormationのリソースタブから確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3803480/0dce21fc-d827-4b19-bea4-8e3ff80340e1.png)

```yaml:Resourceセクション
Resources:
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Ref VPCName

  VPCFlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      ResourceId: !Ref VPC
      ResourceType: VPC
      TrafficType: ALL
      LogDestinationType: s3
      LogDestination: !Sub arn:aws:s3:::${S3BucketName}

  # ------------------------------------------------------------#
  #  InternetGateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub InternetGateway-${VPCName}

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # ------------------------------------------------------------#
  #  Subnet
  # ------------------------------------------------------------#
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetCIDR
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub subnet-${VPCName}-public

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub route-${VPCName}-public

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: VPC
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGateway

  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable
```


#### Outputs（任意）
Outputsセクションでは、リソースの属性や他の重要な情報をユーザーや他のスタックに渡すことができます。
例えば、作成されたリソースのID、エンドポイント、またはURIなどを出力するのに使われます。

```yaml:Outputs（任意）セクション
# ------------------------------------------------------------#
# OutPut Parameters
# ------------------------------------------------------------#
Outputs:
  VPC:
    Description: VPC ID
    Value: !Ref VPC

  PublicSubnetId:
    Description: Public Subnet ID
    Value: !Ref PublicSubnet

  InternetGatewayId:
    Description: Internet Gateway ID
    Value: !Ref InternetGateway
```

Outputsセクションで指定された値はスタック作成後の出力タブで確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3803480/d7296ef1-a037-4c8d-b444-791c78be59c4.png)


## 組み込み関数
#### !Ref
同テンプレート内で定義されたリソースを参照します。
例えば下記テンプレート内の!Ref VPCCIDRにはParametarセクションで入力した値が参照されます。
```yaml:!Ref含むパート
Resources:
  # ------------------------------------------------------------#
  #  VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Ref VPCName
```

下記Outputsセクションの!Ref VPCだとResourceセクションで定義されたVPC IDが参照されます。
```yaml:!Ref含むパート
# ------------------------------------------------------------#
# OutPut Parameters
# ------------------------------------------------------------#
Outputs:
  VPC:
    Description: VPC ID
    Value: !Ref VPC
```

#### !Sub
同テンプレート内で定義されたリソースやパラメーターや疑似パラメーター(AWS::Region) などが参照されます。
!Refと違い、文字列の内の一部分のみ参照するときに使用します。
例えば下記パートだと　!Sub InternetGateway-${VPCName}　として使っています。
これはインターネットゲートウェイ名を個別で命名するのではなく、VPC名と紐づく名前をつけたかったためこのようにしました。
```yaml:!Sub含むパート
  # ------------------------------------------------------------#
  #  InternetGateway
  # ------------------------------------------------------------#
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub InternetGateway-${VPCName}
```

#### !GetAZs・!Select
!GetAZsは、指定したリージョンにおける利用可能なアベイラビリティゾーンのリストを取得するために使用します。
たとえば、us-east-1 リージョンであれば、!GetAZs '' は ['us-east-1a', 'us-east-1b', 'us-east-1c'] のようなリストを返します。

!Selectは、リストの中から特定のインデックスに基づいて1つの要素を取得するために使用します。

今回使われた　!Select [ 0, !GetAZs '' ]　は現在のリージョンで利用可能なアベイラビリティゾーンのリストを取得し、そのリストの最初の AZ（通常、最初のAZは us-east-1a など）を選択できます。
```yaml:!Select含むパート
  # ------------------------------------------------------------#
  #  Subnet
  # ------------------------------------------------------------#
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetCIDR
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub subnet-${VPCName}-public
```


## AWS公式マニュアル
ここまでテンプレートの構成について説明してきました。
テンプレート内の文については定型文が決まっており、CloudFormationの公式ユーザーガイドに説明があります。
AWS リソースタイプおよびプロパティタイプのリファレンス情報（Resourceセクションにおいての設定値）についても下記にあります。
なぜかVPC関連はAmazon EC2のページ内にあります。

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html


## ちなみに
[生成AIテンプレート](https://qiita.com/super_ucchi/items/a6b6fa5c7ed0c9224164#%E4%BD%9C%E6%88%90%E6%96%B9%E6%B3%95ai%E3%81%AB%E3%81%8A%E9%A1%98%E3%81%84)と見比べたところ、Meta DataとInput ParametersあとVPCフローログ設定以外はほとんど同じでした。
生成AIなかなかやりますね。


## おわりに
自分が初めてAWS Cloudformationテンプレート作成を始めた時につまずいたことをまとめました。
コードばかりで冗長気味となってしまいましたが、少しでも参考になると嬉しいです。