AWS CloudFormation でパブリック・プライベートサブネットをもつ VPC を構築する

パブリックサブネットとプライベートサブネットを一つずつもつ VPC を CloudFormation を使って構築する。 プライベートサブネットからインターネットにアクセスできるように NAT ゲートウェイを利用する。 AWS CloudFormation は AWS CLI で操作する。

以降では、段階的に構築していく手順を示す。 途中の手順を省いて完成したテンプレートをみたい方はこちら

目次

  1. 構成図
  2. VPC を作成
  3. パブリックサブネットを作成
  4. スタックに変更を適用してサブネットを作成
  5. ルートテーブルを作成してインターネットへのルートを登録
  6. プライベートサブネットを作成
  7. ルートテーブルを作成して NAT ゲートウェイ を登録
  8. 最終的なテンプレート
  9. スタックを削除
  10. 参考

構成図

VPC を作成

まずはシンプルに VPC だけを作成するテンプレート。

1
2
3
4
5
6
7
8
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  VPC: # Logical ID. Unique in template
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true # Default true
      EnableDnsHostnames: true # EC2 インスタンスが DNS ホスト名を取得するかどうか。 Default false

Resources 以下に作成するリソースを定義していく。 これを template.yaml に保存する。

まずは次のコマンドを実行して CloudFormation のスタックを作成する。

1
$ aws cloudformation create-stack --stack-name sample-vpc --template-body file://template.yaml

次のコマンドでスタックの状態を確認する。

1
$ aws cloudformation describe-stacks --stack-name sample-vpc

次のような出力が得られるはず。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/sample-vpc/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "StackName": "sample-vpc",
            "CreationTime": "2020-02-23T07:10:51.420000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

StackStatusCREATE_COMPLETE になっていれば作成完了 。 CREATE_IN_PROGRESS あればまだ作成中。

パブリックサブネットを作成

パブリックサブネットを追加するには先ほど作成した template.yaml の Resources 以下に次のパブリックサブネットの定義を追加する。

1
2
3
4
5
6
7
8
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか

VPC 定義と合わせたテンプレート template.yaml は次のようになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  VPC: # Logical ID. Unique in template
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true # Default true
      EnableDnsHostnames: true # EC2 インスタンスが DNS ホスト名を取得するかどうか。 Default false

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか

スタックに変更を適用してサブネットを作成

この変更を適用するために、まず変更セットを作成する。

1
$ changeset_id=$(aws cloudformation create-change-set --stack-name sample-vpc --template-body file://template.yaml --change-set-name change-$(date +%Y%m%d-%H%M%S) --query 'Id')

変更セットを作成しても、変更はまだ適用されない。 次のコマンドで実際に変更が適用されてパブリックサブネットが作成される。

1
$ aws cloudformation execute-change-set --change-set-name ${changeset_id//\"}

スタックを作成したときと同様にスタックの状態を確認する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ aws cloudformation describe-stacks --stack-name sample-vpc
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/sample-vpc/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "StackName": "sample-vpc",
            "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:changeSet/change-20200223-162707/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "CreationTime": "2020-02-23T07:10:51.420000+00:00",
            "LastUpdatedTime": "2020-02-23T07:35:10.450000+00:00",
            "RollbackConfiguration": {},
            "StackStatus": "UPDATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}

StackStatusUPDATE_COMPLETE になっていれば変更完了。 まだ変更適用中であれば UPDATE_IN_PROGRESS になる。

ルートテーブルを作成してインターネットへのルートを登録

パブリックサブネットからインターネットにアクセスできるようにするには、 パブリックサブネットに関連付けられたルートテーブルにインターネットゲートウェイを経由してインターネットにアクセスするルートを設定する必要がある。

VPC には、作成時に自動的に作成されるメインルートテーブルが存在する。 CloudFormation でメインルートテーブルにルートを追加するには Lambda-backed カスタムリソースを利用する必要がある。 参考

今回はルートテーブルを別途作成してそちらを利用する。

すなわち CloudFormation で以下のことを行う。

  • ルートテーブルを作成
  • インターネットゲートウェイを作成
  • インターネットゲートウェイを経由したルートをルートテーブルに登録
  • 作成したルートテーブルをパブリックサブネットに関連付け

これを行うには template.yaml の Resources に次の定義を追加する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  InternetGateway:
    Type: AWS::EC2::InternetGateway

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

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PublicRoute:
    Type: AWS::EC2::Route
    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

template.yaml に変更を加えたら、変更セットを作成し、適用する。

1
2
$ changeset_id=$(aws cloudformation create-change-set --stack-name sample-vpc --template-body file://template.yaml --change-set-name change-$(date +%Y%m%d-%H%M%S) --query 'Id')
$ aws cloudformation execute-change-set --change-set-name ${changeset_id//\"}

aws cloudformation describe-stacks --stack-name sample-vpc の結果で StackStatusUPDATE_COMPLETE になれば完了。

プライベートサブネットを作成

パブリックサブネットを作成したときと同様にしてプライベートサブネットを作成する。 CidrBlockMapPublicIpOnLaunch だけ変更する。

1
2
3
4
5
6
7
8
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: false # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか

変更セットを作成して適用する。

1
2
$ changeset_id=$(aws cloudformation create-change-set --stack-name sample-vpc --template-body file://template.yaml --change-set-name change-$(date +%Y%m%d-%H%M%S) --query 'Id')
$ aws cloudformation execute-change-set --change-set-name ${changeset_id//\"}

aws cloudformation describe-stacks --stack-name sample-vpc の結果で StackStatusUPDATE_COMPLETE になれば完了。

ルートテーブルを作成して NAT ゲートウェイを登録

プライベートサブネットからインターネットにアクセスするには、 パブリックサブネットに NAT ゲートウェイを作成し、そこを経由してアクセスするようにする。 パブリックサブネットのルートテーブルでインターネットゲートウェイを経由するルートを登録したように、 プライベートサブネットでは NAT ゲートウェイを経由するルートを登録する。 また NAT ゲートウェイには Elastic IP が必要になる。

これを実現するには template.yaml の Resources に次の定義を追加する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  ElasticIPForNAT:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - ElasticIPForNAT
          - AllocationId
      SubnetId: !Ref PublicSubnet

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PrivateRouteToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  PrivateSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable

これまでと同様に変更セットを作成して適用する。

1
2
$ changeset_id=$(aws cloudformation create-change-set --stack-name sample-vpc --template-body file://template.yaml --change-set-name change-$(date +%Y%m%d-%H%M%S) --query 'Id')
$ aws cloudformation execute-change-set --change-set-name ${changeset_id//\"}

aws cloudformation describe-stacks --stack-name sample-vpc の結果で StackStatusUPDATE_COMPLETE になれば完了。

これでパブリックサブネットとプライベートサブネットを持つ VPC が完成した。

最終的なテンプレート

今回作成した VPC の最終的なテンプレートは次のようになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
AWSTemplateFormatVersion: "2010-09-09"
Resources:
  VPC: # Logical ID. Unique in template
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true # Default true
      EnableDnsHostnames: true # EC2 インスタンスが DNS ホスト名を取得するかどうか。 Default false

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.0.0/24
      MapPublicIpOnLaunch: true # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか

  InternetGateway:
    Type: AWS::EC2::InternetGateway

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

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PublicRoute:
    Type: AWS::EC2::Route
    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

  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: false # このサブネットで起動されたインスタンスがパブリック IPv4 アドレスを受け取るかどうか

  ElasticIPForNAT:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - ElasticIPForNAT
          - AllocationId
      SubnetId: !Ref PublicSubnet

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PrivateRouteToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  PrivateSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable

スタックを削除

作成した VPC が不要になれば、次のコマンドで削除できる。

1
$ aws cloudformation delete-stack --stack-name sample-vpc

参考