AWS CloudFormationを使用したVPC環境作成

# はじめに
最近、自身のAWS環境でネットワークのスタディーをするために、主要なネットワーク関連のサービス(S3ゲートウェイ・エンドポイント、PrivateLink、トランジット・ゲートウェイ、ゲートウェイ・ロードバランサー)の環境構築・実機検証をやっていました。その際、事前準備としてVPC環境作成(サブネットや、インターネットゲートウェイ、セキュリティー・グループ作成など)が必要となりますが、毎度毎度同じセットアップをすることになり手間もかかります。そこで、本来試したいネットワーク関連サービスの検証に時間を割けるようにすべく、事前準備部分のVPC環境作成をCloudFormationのコードを書いて自動作成できるようにしてみました。本記事では、筆者が作成したCloudFormationのサンプルコードをご紹介します。
なお、CloudFormationの詳細については、[AWS CloudFormationドキュメント](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html)を参照ください。


# 作成するVPC環境
今回作成する構成は下記の通りです。
* AWSのリージョン：東京リージョン(ap-northeast-1)を使用
* 以下のCIDRアドレス範囲を持つVPCを作成
  * VPC (10.0.0.0/16)
* AZ(ap-northeast-1a)に、以下のCIDRアドレス範囲をもつサブネットを作成
  * パブリック・サブネット (10.0.1.0/24)
  * プライベート・サブネット (10.0.2.0/24)
* AZ(ap-northeast-1c)に、以下のCIDRアドレス範囲をもつサブネットを作成
  * パブリック・サブネット (10.0.3.0/24)
  * プライベート・サブネット (10.0.4.0/24)
* インターネットゲートウェイ
* ルートテーブル
  * パブリック・サブネットに対するルートテーブルには、下記の宛先とターゲットを別途追記指定
    * 宛先：0.0.0.0/0、ターゲット：インターネットゲートウェイ
* セキュリティー・グループ
  * パブリック・サブネットとプライベート・サブネットそれぞれに対してグループを用意 
    * パブリック・サブネット用
      *  外部からのSSH、HTTP通信を許可
    * プライベート・サブネット用  
      *  パブリック・サブネットからのSSH通信とPINGを許可

# CloudFormationサンプルコード
今回作成したサンプルコードは下記のようなYAMLファイルです。
```yaml:CreateNetWork_Stack.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Sample Script for Creating NetWork Stack (VPC,Public Subnet,Private Subnet,Internet Gateway,Route Table,Security Group)
Parameters:
  AzName01:
    Description: Set 1st az-name
    Type: String
    Default: ap-northeast-1a
    AllowedValues:
      - ap-northeast-1a
      - ap-northeast-1c
      - ap-northeast-1d
  AzName02:
    Description: Set 2nd az-name
    Type: String
    Default: ap-northeast-1c
    AllowedValues:
      - ap-northeast-1a
      - ap-northeast-1c
      - ap-northeast-1d
  VPCCidrBlock:
    Description: VPC Cidr Block
    Type: String
    Default: 10.0.0.0/16
  PublicSubnetCidrBlock01:
    Description: Public Subnet Cidr Block for 1st az-name
    Type: String
    Default: 10.0.1.0/24
  PrivateSubnetCidrBlock01:
    Description: Private Subnet Cidr Block for 1st az-name
    Type: String
    Default: 10.0.2.0/24
  PublicSubnetCidrBlock02:
    Description: Public Subnet Cidr Block for 2nd az-name
    Type: String
    Default: 10.0.3.0/24
  PrivateSubnetCidrBlock02:
    Description: Private Subnet Cidr Block for 2nd az-name
    Type: String
    Default: 10.0.4.0/24
    
Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties: 
      CidrBlock: !Ref VPCCidrBlock
      EnableDnsHostnames: 'true'
      EnableDnsSupport: 'true'
      Tags: 
        - Key: Name
          Value: My-vpc
  MyPublicSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: !Ref PublicSubnetCidrBlock01
      AvailabilityZone: !Ref AzName01
      Tags:
        - Key: Name
          Value: My-Public-Subnet-01
    DependsOn: MyVPC
  MyPrivateSubnet01:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: !Ref PrivateSubnetCidrBlock01
      AvailabilityZone: !Ref AzName01
      Tags:
        - Key: Name
          Value: My-Private-Subnet-01
    DependsOn: MyVPC
  MyPublicSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: !Ref PublicSubnetCidrBlock02
      AvailabilityZone: !Ref AzName02
      Tags:
        - Key: Name
          Value: My-Public-Subnet-02
    DependsOn: MyVPC
  MyPrivateSubnet02:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: !Ref PrivateSubnetCidrBlock02
      AvailabilityZone: !Ref AzName02
      Tags:
        - Key: Name
          Value: My-Private-Subnet-02
    DependsOn: MyVPC
  MyIGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: My-internet-gateway
    DependsOn: MyVPC
  MyIGWAttachToVPC:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref MyIGW 
  MyPublicRtbl01:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-Public-Rtbl-01
    DependsOn: MyIGW
  MyPublicRoute01:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: MyPublicRtbl01
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyIGW
  MyPublicSubnetRouteTblAssociation01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRtbl01
      SubnetId: !Ref MyPublicSubnet01
  MyPrivateRtbl01:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-Private-Rtbl-01
  MyPrivateSubnetRouteTblAssociation01:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRtbl01
      SubnetId: !Ref MyPrivateSubnet01
  MyPublicRtbl02:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-Public-Rtbl-02
    DependsOn: MyIGW
  MyPublicRoute02:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: MyPublicRtbl02
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref MyIGW
  MyPublicSubnetRouteTblAssociation02:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPublicRtbl02
      SubnetId: !Ref MyPublicSubnet02
  MyPrivateRtbl02:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
        - Key: Name
          Value: My-Private-Rtbl-02
  MyPrivateSubnetRouteTblAssociation02:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref MyPrivateRtbl02
      SubnetId: !Ref MyPrivateSubnet02
  MyPublicSecGrp01:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId: !Ref MyVPC
      SecurityGroupIngress: 
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
      Tags:
        - Key: Name
          Value: My-Public-Security-Group-01
  MyPrivateSecGrp01:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow Ingress trafic from Public Subnet
      VpcId: !Ref MyVPC
      SecurityGroupIngress: 
        - SourceSecurityGroupId: !Ref MyPublicSecGrp01
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
        - SourceSecurityGroupId: !Ref MyPublicSecGrp01
          IpProtocol: icmp
          FromPort: 8
          ToPort: -1
      Tags:
        - Key: Name
          Value: My-Private-Security-Group-01
  MyPublicSecGrp02:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow http to client host
      VpcId: !Ref MyVPC
      SecurityGroupIngress: 
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
      Tags:
        - Key: Name
          Value: My-Public-Security-Group-02
  MyPrivateSecGrp02:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow Ingress trafic from Public Subnet
      VpcId: !Ref MyVPC
      SecurityGroupIngress: 
        - SourceSecurityGroupId: !Ref MyPublicSecGrp02
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
        - SourceSecurityGroupId: !Ref MyPublicSecGrp02
          IpProtocol: icmp
          FromPort: 8
          ToPort: -1
      Tags:
        - Key: Name
          Value: My-Private-Security-Group-02

Outputs:
  MyStackPublicSubnetID01:
    Description: The ID of My Public Subnet for AzName01
    Value: !Ref MyPublicSubnet01
    Export:
      Name: !Sub "${AWS::StackName}--PublicSubnetId01"
  MyStackPrivateSubnetID01:
    Description: The ID of My Private Subnet for AzName01
    Value: !Ref MyPrivateSubnet01
    Export:
      Name: !Sub "${AWS::StackName}--PrivateSubnetId01"
  MyStackPublicSubnetSecurityGroupID01:
    Description: The ID of My Public Subnet Security Group for AzName01
    Value: !Ref MyPublicSecGrp01
    Export:
      Name: !Sub "${AWS::StackName}--PublicSubnetSecGrpId01"
  MyStackPrivateSubnetSecurityGroupID01:
    Description: The ID of My Private Subnet Security Group for AzName01
    Value: !Ref MyPrivateSecGrp01
    Export:
      Name: !Sub "${AWS::StackName}--PrivateSubnetSecGrpId01"
  MyStackPublicSubnetID02:
    Description: The ID of My Public Subnet for AzName02
    Value: !Ref MyPublicSubnet02
    Export:
      Name: !Sub "${AWS::StackName}--PublicSubnetId02"
  MyStackPrivateSubnetID02:
    Description: The ID of My Private Subnet for AzName02
    Value: !Ref MyPrivateSubnet02
    Export:
      Name: !Sub "${AWS::StackName}--PrivateSubnetId02"
  MyStackPublicSubnetSecurityGroupID02:
    Description: The ID of My Public Subnet Security Group for AzName02
    Value: !Ref MyPublicSecGrp02
    Export:
      Name: !Sub "${AWS::StackName}--PublicSubnetSecGrpId02"
  MyStackPrivateSubnetSecurityGroupID02:
    Description: The ID of My Private Subnet Security Group for AzName02
    Value: !Ref MyPrivateSecGrp02
    Export:
      Name: !Sub "${AWS::StackName}--PrivateSubnetSecGrpId02"
```
今回のサンプルコードの主なポイントは下記の部分になります。
* Parametersセクション
  * 作成する環境に応じて、AZやCIDRアドレス範囲を指定可能にしました。ただし、東京リージョンにあるもののみが利用可能です。
* Resourcesセクション
  * 作成するリソース(VPC、サブネット、インターネットゲートウェイ等)の構成を定義します。
  * CloudFormationはリソースを並行して作成されるため、例えばサブネットやインターネットゲートウェイは必ずVPCの作成完了後に作成を開始する等の考慮が必要になります。その為、そのようなリソースの作成においては、<font color="Blue">DependsOn属性</font>を指定する必要があります。 
* Outputsセクション
  * 各サブネットのIDとセキュリティー・グループのID情報を出力します。これらの情報は、別に作成しているEC2プロビジョニング用のCloudFormationのコード実行時に必要になります。(ネストスタックのスタディーがやりたく、敢えて本記事で紹介しているサンプルコードでは、EC2インスタンスのプロビジョニング部分はスコープ外としています。)

# 作成したサンプルコードの実行
AWSのマネジメントコンソールにログオンし、CloudFormationの「スタックの作成」の画面において、最初に今回のサンプルコードをアップロードします。その後、「スタックの詳細を指定」の画面において、スタック名等を入力していきます。(以下はサンプルです。)
* スタック名を入力
![p1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/fbdbf316-03eb-9e96-9c80-ace5c3a810d9.jpeg)
* パラメータ確認
下記のようにAZの名前や、パブリック・サブネット、プライベート・サブネット等のCIDRアドレス範囲等が表示されますので、確認します。アドレス範囲等はここで指定が可能です。
![p2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/2c61d66c-488b-9775-eb2c-e19943aa84ca.jpeg)
![p3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/275e3279-35f0-4751-aa6d-3d53d63005c6.jpeg)
確認が終了したら画面右下の「次へ」ボタンを押下げします。その後に遷移した先の画面では特に何もせずに「次へ」ボタンを押下げします。

* レビュー画面の確認
下記のようなパラメータ一覧が表示されますので、問題なければ右下の「送信」ボタンを押下げして、CloudFormationのコードを実行します。
![p7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/6b05a7d0-c5ee-d4d2-9087-14d881baac65.jpeg)
* コードの実行
下記画面のようにスタックの作成が開始されます。スタックのステータスが最終的に「<font color="Green">CREATE_COMPLETE</font>」になればOKです。
![p9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/fc14768b-13fc-fe62-5c75-eddeaaa06cb1.jpeg)
スタックの作成完了後のイメージは下記の通りです。
![p10.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/73080d17-491b-5d42-bb1b-6d2fafc03f06.jpeg)
* 「出力」タブの確認
サンプルコードのOutputsセクションに指定した、サブネットIDやセキュリティー・グループID等の一覧が表示されます。
![p11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/04c34583-4f35-267f-4be6-09840c4c27d6.jpeg)
![p12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1567099/8f38eefd-30a5-6847-946c-c455f1468cdc.jpeg)

# おわりに
今回は、CloudFormationを使用して、VPC環境を自動作成するサンプルコードを紹介しました。このVPC環境上にEC2をプロビジョニングするサンプルコードについては、また別の記事で紹介予定です。