VPCで理解するCloudFormation

## はじめに
新人であった昨年度、0からAWS基盤構築の経験をさせていただき、マネコン上からAWSのリソースを弄り回していると「あれ？これなんだっけ？いつのバージョンのやつだっけ？」みたいな現象が頻発してました。
やはりGUIは悪でCLIは正義ということを身を以って体感したのでCloudFormation（cfn）の学習を始めました。
今回はcfnの勉強がてらVPCの一般的な基盤構築を行ってみました。

## 構成図
![vpc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/412378/398135ab-3ffe-beab-0697-661335e727cd.png)
ご覧の通り、マネコンの「VPCを作成 / VPCなど」で作成できる諸々のリソースを作成します。
RouteTableもマネコン上からはあまり意識しませんが、作成対象のため今回の構成図では明示的に記述しています。

## テンプレート
以下先に全文。
```template.vpc.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create Fargate and ALB

Parameters:
  ProjectName:
    Type: String
    Description: Project Name for all
  VPCCiderBlock:
    Default: 10.0.0.0/16
    Type: String
    Description: VPC Cider Block for VPC
  PubSub1aCiderBlock:
    Default: 10.0.10.0/24
    Type: String
    Description: Public Subnet 1a Cider Block for PublicSubnet1a
  PriSub1aCiderBlock:
    Default: 10.0.20.0/24
    Type: String
    Description: Private Subnet 1a Cider Block for PrivateSubnet1a
  PubSub1cCiderBlock:
    Default: 10.0.30.0/24
    Type: String
    Description: Public Subnet 1c Cider Block for PublicSubnet1c
  PriSub1cCiderBlock:
    Default: 10.0.40.0/24
    Type: String
    Description: Private Subnet 1c Cider Block for PrivateSubnet1c

Resources:
  ############################################
  #### VPC                                ####
  ############################################
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCiderBlock
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-VPC

  ############################################
  #### Subnet                             ####
  ############################################
  PubSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PubSub1aCiderBlock
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-Public-Subnet-1a

  PriSubnet1a:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      CidrBlock: !Ref PriSub1aCiderBlock
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-Private-Subnet-1a

  PubSubnet1c:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PubSub1cCiderBlock
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-Public-Subnet-1c

  PriSubnet1c:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1c
      CidrBlock: !Ref PriSub1cCiderBlock
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-Private-Subnet-1c

  ############################################
  #### Route Table                        ####
  ############################################
  PubRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-rtb-public
      VpcId: !Ref VPC

  PriRouteTable1a:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-rtb-private-1a
      VpcId: !Ref VPC

  PriRouteTable1c:
    Type: AWS::EC2::RouteTable
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-rtb-private-1c
      VpcId: !Ref VPC

  ############################################
  #### Gateway                            ####
  ############################################
  S3Endpoint:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      RouteTableIds:
        - !Ref PriRouteTable1a
        - !Ref PriRouteTable1c
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcEndpointType: Gateway
      VpcId: !Ref VPC

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${ProjectName}-igw

  IGWAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref IGW
      VpcId: !Ref VPC
      
      
  # ############################################
  # #### NAT Gateway                        ####
  # ############################################
  # >>>>>>>> NAT Gatewayが必要な場合 >>>>>>>>>>>>
  # NatGatewayEIP:
  #   Type: AWS::EC2::EIP
  #   Properties:
  #     Domain: vpc
  #   DependsOn: VPC
    
  # NatGateway:
  #   Type: AWS::EC2::NatGateway
  #   Properties:
  #     AllocationId: 
  #       Fn::GetAtt:
  #         - NatGatewayEIP
  #         - AllocationId
  #     SubnetId: !Ref PubSubnet1a
  #     Tags:
  #       - Key: Name
  #         Value: !Sub ${ProjectName}-natgateway
  # <<<<<<<<< NAT Gatewayが必要な場合 <<<<<<<<<<<<
  
  

  ############################################
  #### Route                              ####
  ############################################
  PubRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW
      RouteTableId: !Ref PubRouteTable
  
  # >>>>>>>>> NAT Gatewayが必要な場合 >>>>>>>>>
  # PriRoute1a:
  #   Type: AWS::EC2::Route
  #   Properties:
  #     RouteTableId: !Ref PriRouteTable1a
  #     DestinationCidrBlock: 0.0.0.0/0
  #     NatGatewayId: !Ref NatGateway

  # PriRoute1c:
  #   Type: AWS::EC2::Route
  #   Properties:
  #     RouteTableId: !Ref PriRouteTable1c
  #     DestinationCidrBlock: 0.0.0.0/0
  #     NatGatewayId: !Ref NatGateway
  # <<<<<<<<<< NAT Gatewayが必要な場合 <<<<<<<<

  ############################################
  #### Route Table Association            ####
  ############################################
  PubSub1aRouteTableAssociate:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PubRouteTable
      SubnetId: !Ref PubSubnet1a

  PubSub1cRouteTableAssociate:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PubRouteTable
      SubnetId: !Ref PubSubnet1c

  PriSub1aRouteTableAssociate:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PriRouteTable1a
      SubnetId: !Ref PriSubnet1a

  PriSub1cRouteTableAssociate:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PriRouteTable1c
      SubnetId: !Ref PriSubnet1c

############################################
#### OutPuts                            ####
############################################
Outputs:
  VpcId:
    Value: !Ref VPC
    Description: VPC ID
    Export:
      Name: !Sub ${ProjectName}-VpcId
  PubSub1aId:
    Value: !Ref PubSubnet1a
    Description: Public Subnet ID in ap-northeast-1a
    Export:
      Name: !Sub ${ProjectName}-PubSub1aId
  PubSub1cId:
    Value: !Ref PubSubnet1c
    Description: Public Subnet ID in ap-northeast-1c
    Export:
      Name: !Sub ${ProjectName}-PubSub1cId
  PriSub1aId:
    Value: !Ref PriSubnet1a
    Description: Private Subnet ID in ap-northeast-1a
    Export:
      Name: !Sub ${ProjectName}-PriSub1aId
  PriSub1cId:
    Value: !Ref PriSubnet1c
    Description: Private Subnet ID in ap-northeast-1c
    Export:
      Name: !Sub ${ProjectName}-PriSub1cId

```
ここで個人的に学びになった点をピックアップしてみます。

#### RouteTable
基本的にはマネコンで作成するときと同じ認識でリソースを作っていきますが、マネコンと大きく違う点がRouteTable関係です。
RouteTableは、[AWS::EC2::RouteTable](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html)でRouteTable自体を作成し、[AWS::EC2::Route](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html)でそれぞれのルートを作成、[AWS::EC2::SubnetRouteTableAssociation](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnetroutetableassociation.html)でRouteTableとSubnetを結び付けることで機能します。

なお、NAT Gatewayを使わない限り Private の Route は必要ありません。
理由としては、S3 Endpoint（ゲートウェイ型）はRouteTableに直接アタッチするためです。（マネコンでは Route として Private Subnet -> S3 Endpoint として作成される）

#### IGW
InternetGateWay（IGW）などのGateway関係は基本的に、その「Gateway本体」と「Gatewayのアタッチ処理」を別のキーとて記述する必要があります。
今回の例で言うと、 IGWで本体を作成して、IGWAttachmentでそれを作成済みのVPCにアタッチしています。

#### OutPuts
このキーはオプショナルですが、かなり使用頻度が高くなりそうなのでピックアップします。
OutPutsに記述することで、このテンプレートで定義したリソースを他のテンプレートでも呼び出せるようにすることができるようになります。
Valueで呼び出すリソースを、Export.Nameで呼び出す際の変数を定義します。
これで、VPCなどの多く共通して使いそうなリソースを個別のテンプレートとして用意しておいて、あとは使途に応じて随時作成するテンプレートから呼び出すことができるようになります。

### パラメータと実行ファイル
テンプレートのParametersで、テンプレート内で使用する変数を定義することができます。
この際に変数に代入するものをテンプレート実行時に動的に与えても良いのですが、以下のようなjsonファイルとして外部に切り出して呼び出すこともできます。

```parameter.vpc.json
[
  {
    "ParameterKey": "ProjectName",
    "ParameterValue": "test"
  }
]

```

また、以下のshスクリプトを実行することで、jsonファイルを使ったテンプレートからスタックを作成することができます。

```deploy.vpc.sh
#!/bin/bash

aws cloudformation create-stack \
  --stack-name testStackVPC \
  --template-body file://template.vpc.yaml \
  --parameters file://parameter.vpc.json \
  --capabilities CAPABILITY_NAMED_IAM
```


## まとめ
上記でVPCを対象にCfnの簡単な動きをまとめました。
やはりGUIでぽちぽちやっていると、現在のリソースの状況を把握しにくくなるのでコードで管理できるのは非常にいいですね。
今後もCfn使っていこうと思います。