CloudFormationで基本的なネットワーク（VPC,subnet,rtb）作成時に躓いた話

# はじめに
はじめまして。かわむと申します。
私は今年度SIerに入社し、現場配属されてから2か月ほどになります。最初に担当した業務がAWSのCloudFormationを使用してごく一般的なネットワーク（VPC、サブネットなど）を作成するという簡単な業務だったのですが、無知であるが故につまずいた部分がありましたので、アウトプットを兼ねて共有したいと思います。



# 構成図
かなり簡略化しておりますが、以下のコンポーネントを作成しました
・VPC（Virtual Private Cloud）
・サブネット
・ルートテーブル
・VPCエンドポイント（ゲートウェイ型）

![ネットワーク構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3944793/f993bfb1-f60e-efc8-d2de-214b4ee4e516.png)

# 最初に作成したyamlテンプレート（ミスあり）
まず初めに、以下のようなyamlテンプレートを作成しました。こちらのコードには誤りが二か所存在します。（そもそもIGWを設置していないことは別として、デプロイ時にエラーが発生します。）

```ruby:qiita.rb
AWSTemplateFormatVersion: "2010-09-09"
Description: "CloudFormation template for a VPC with Multi-AZ subnets and VPC Endpoint."

#VPC作成
Resources:
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"

#サブネット作成
  SubnetAZ1:
    Type: "AWS::EC2::Subnet"
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.1.0/24"
      AvailabilityZone: "ap-northeast-1a"

  SubnetAZ2:
    Type: "AWS::EC2::Subnet"
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.2.0/24"
      AvailabilityZone: "ap-northeast-1b"

#ルートテーブル作成
  RouteTableAZ1:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC

  RouteTableAZ2:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC

#ルート追加
#インターネットへのルートを追加
  RouteAZ1Internet:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ1
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: "local"

  RouteAZ2Internet:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ2
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: "local"
      
#VPC内へのルートを追加
  RouteAZ1Local:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ1
      DestinationCidrBlock: "10.0.0.0/16"
      GatewayId: "local"

  RouteAZ2Local:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ2
      DestinationCidrBlock: "10.0.0.0/16"
      GatewayId: "local"
      
#VPCエンドポイントへのルート追加
  RouteAZ1Endpoint:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ1
      DestinationCidrBlock: "com.amazonaws.ap-northeast-1.s3"
      GatewayId: !Ref VPCGatewayEndpoint

  RouteAZ2Endpoint:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ2
      DestinationCidrBlock: "com.amazonaws.ap-northeast-1.s3"
      GatewayId: !Ref VPCGatewayEndpoint

#VPCエンドポイント作成
  VPCGatewayEndpoint:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      VpcId: !Ref VPC
      ServiceName: "com.amazonaws.ap-northeast-1.s3"
      RouteTableIds:
        - !Ref RouteTableAZ1
        - !Ref RouteTableAZ2

#ルートテーブルをサブネットに紐づける
  SubnetRouteTableAssociationAZ1:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref SubnetAZ1
      RouteTableId: !Ref RouteTableAZ1

  SubnetRouteTableAssociationAZ2:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref SubnetAZ2
      RouteTableId: !Ref RouteTableAZ2
```

エラーの箇所はわかりましたでしょうか。答えはどちらもRouteの定義中にあります。
1点目は以下の部分です。
```
  RouteAZ1Local:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ1
      DestinationCidrBlock: "10.0.0.0/16"
      GatewayId: "local"

  RouteAZ2Local:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ2
      DestinationCidrBlock: "10.0.0.0/16"
      GatewayId: "local"
```
上記のコードでは、VPC CIDR宛（local）へのルートを追加しています。しかし、**AWSのルートテーブルは作成時にデフォルトでlocalルートが定義される**ため、重複が発生します。

そして二点目は以下の部分です。
```
#VPCエンドポイントへのルート追加
  RouteAZ1Endpoint:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ1
      DestinationCidrBlock: "com.amazonaws.ap-northeast-1.s3"
      GatewayId: !Ref VPCGatewayEndpoint

  RouteAZ2Endpoint:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ2
      DestinationCidrBlock: "com.amazonaws.ap-northeast-1.s3"
      GatewayId: !Ref VPCGatewayEndpoint

#VPCエンドポイント作成
  VPCGatewayEndpoint:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      VpcId: !Ref VPC
      ServiceName: "com.amazonaws.ap-northeast-1.s3"
      RouteTableIds:
        - !Ref RouteTableAZ1
        - !Ref RouteTableAZ2
```
上記のコードではVPCエンドポイントへのルートを追加していますが、そのすぐ下でVPCエンドポイントとルートテーブルとの紐づけを定義してしまっています。この場合もルートに重複が生じてエラーになってしまいます。

# 修正後のyamlテンプレート
先ほどのミスを修正したテンプレートが以下になります。

```ruby:qiita.rb
AWSTemplateFormatVersion: "2010-09-09"
Description: "CloudFormation template for a VPC with Multi-AZ subnets and VPC Endpoint."

#VPC作成
Resources:
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: "10.0.0.0/16"

#サブネット作成
  SubnetAZ1:
    Type: "AWS::EC2::Subnet"
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.1.0/24"
      AvailabilityZone: "ap-northeast-1a"

  SubnetAZ2:
    Type: "AWS::EC2::Subnet"
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.2.0/24"
      AvailabilityZone: "ap-northeast-1b"

#ルートテーブル作成
  RouteTableAZ1:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC

  RouteTableAZ2:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC

#ルート追加
  RouteAZ1Internet:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ1
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: "local"

  RouteAZ2Internet:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref RouteTableAZ2
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: "local"

#VPCエンドポイント作成
  VPCGatewayEndpoint:
    Type: "AWS::EC2::VPCEndpoint"
    Properties:
      VpcId: !Ref VPC
      ServiceName: "com.amazonaws.ap-northeast-1.s3"
      RouteTableIds:
        - !Ref RouteTableAZ1
        - !Ref RouteTableAZ2

#ルートテーブルをサブネットに紐づける
  SubnetRouteTableAssociationAZ1:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref SubnetAZ1
      RouteTableId: !Ref RouteTableAZ1

  SubnetRouteTableAssociationAZ2:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref SubnetAZ2
      RouteTableId: !Ref RouteTableAZ2
```


こちらで無事正常にデプロイすることができました。


# おわりに
今回のミスの原因は、ルートテーブル作成時にデフォルトでlocal宛てのルートが定義されることや、VPCエンドポイントとルートテーブルの適切な紐づけ方を知らなかったことという初歩的なものでした。ただ、CloudFormationではミスをしていても、デプロイ時に基本的にロールバックされるためある程度トライ＆エラーの形で実施していくことができるのかと思います。（コードはあっているけど構成が違うといったミスは非常に怖いですが）

また、初めての業務がマネジメントコンソールを利用したデプロイ作業でなく、CloudFormationを利用する作業となりやや困惑していましたが、スタックをすぐに修正可能であったり、テンプレートを流用して同じリソースを作成可能なことなど、非常に有用な技術だなと感じました。今回テンプレートの中で使用していない、ParametersやConditons等を徐々に覚えていきながら業務に取り組んでいきたいと思う次第です。