必要な時だけNAT Gatewayを作成する方法

2019.08.22

検証用のVPCを作成しておき、必要な時だけNAT Gatewayがあればいいのになぁ、と思っていたので簡単にNAT Gatewayを作成、削除できるCloudFormationテンプレートを作成しました。

NAT GatewayはVPCと違って利用可能となっている時間で料金が発生するので、できるだけ利用可能となっている時間を減らしたいわけです。

構成

今回作成するVPCは下記のようになっています。NAT Gatewayを必要に応じて作成、削除を行います。NAT GatewayはAZ障害を考慮して冗長化していますが、お好みでカスタマイズしてください。

  • Frontend subnet
    • パブリックサブネットになります。インターネットゲートウェイを通じて外部との通信が可能です。
    • ELBや踏み台サーバを配置する想定です
  • Application subnet
    • NAT Gatewayが無い場合は外部アクセス不可のプライベートサブネットになります。ある場合はVPC内からの通信のみが可能になります。その状態を図ではProtectedとしています。
    • APサーバなどを配置する想定です
  • DataStore subnet
    • 外部アクセス不可なプライベートサブネットになります
    • RDSなどを配置する想定です

解説

NAT Gatewayの部分だけ抜粋して説明していきます。全体のCloudFormationテンプレートは記事の最後に記載しています。

Parameters

NAT Gatewayを作成するかどうかをパラメータとして受け取ります。デフォルトでは作成しないになっています。

Parameters:
  EnableNatGateway:
    Description: Enable NAT Gateway.
    Type: String
    Default: false
    AllowedValues: [true, false]

Conditions

NAT Gatewayを作成するかどうかの条件を作成します。

Conditions:
  EnableNatGateway:
    !Equals [true, !Ref EnableNatGateway]

Resources

作成した条件が真であれば、NAT GatewayとEIPを作成します。

Resources:
  ・・・

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Condition: EnableNatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP1.AllocationId
      SubnetId: !Ref FrontendSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-ngw1

  NatGatewayEIP1:
    Type: AWS::EC2::EIP
    Condition: EnableNatGateway
    Properties:
      Domain: vpc

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Condition: EnableNatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP2.AllocationId
      SubnetId: !Ref FrontendSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-ngw2

  NatGatewayEIP2:
    Type: AWS::EC2::EIP
    Condition: EnableNatGateway
    Properties:
      Domain: vpc

  ・・・

NAT Gatewayが作成されていれば、各Application subnetのルートテーブルにNAT Gatewayへのルートを作成しています。

Resources:
  ・・・

  ApplicationRoute1:
    Type: AWS::EC2::Route
    Condition: EnableNatGateway
    Properties:
      RouteTableId: !Ref ApplicationRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  ApplicationRoute2:
    Type: AWS::EC2::Route
    Condition: EnableNatGateway
    Properties:
      RouteTableId: !Ref ApplicationRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  ・・・

スタックを作成する

まずは記事の最後にあるテンプレートをデフォルト設定のままスタックを作成します。パラメータは下図のとおりです。作成が完了したらNAT Gateway、EIP、ルートテーブルを確認し、NAT Gatewayの設定が無いことを確認します。

NAT Gatewayを作成する

作成したVPCにNAT Gatewayを作成してみます。コンソールからのポチポチだけでできます。

先程作成したスタックを選択し、「更新する」をクリックします。

「現在のテンプレートを使用」を選択します

EnableNatGatewaytrueに変更し、NAT Gatewayが作成されるようにします。あとは「次へ」をクリックしていきます。

変更セットのプレビューでNAT Gateway、EIP、ルートが追加されることが分かります。「スタックの更新」をクリックします。

スタックの更新が完了したらNAT Gatewayが作成されているか確認して完了です。

NAT Gatewayを削除する

削除する場合はEnableNatGatewayfalseにして更新すればOKです。

変更セットを見れば下記のように、NAT Gateway関連のリソースが削除されることがわかると思います。

テンプレート全体

今回使用したテンプレートになります。

---
AWSTemplateFormatVersion: '2010-09-09'
Description: Network Layer Template

#------------------------------------------------------------------------------
Parameters:
#------------------------------------------------------------------------------
  SystemName:
    Description: This value is used as the resource prefix.
    Type: String
    MinLength: 1
    Default: example
  Env:
    Description: Environment Name
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - prd
  VpcCidr:
    Description: First and Second Octet of VPC, For example (10.0/172.16/192.168)
    Type: String
    Default: 10.0
    AllowedPattern: "^(10\\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|172\\.(1[6-9]|2[0-9]|3[0-1])|192\\.168)$"
    ConstraintDescription: xxx.xxx
  EnableNatGateway:
    Description: Enable NAT Gateway.
    Type: String
    Default: false
    AllowedValues: [true, false]

#------------------------------------------------------------------------------
Conditions:
#------------------------------------------------------------------------------
  EnableNatGateway:
    !Equals [true, !Ref EnableNatGateway]

#------------------------------------------------------------------------------
Mappings:
#------------------------------------------------------------------------------
  VpcConfig:
    dev:
      Vpc                : .0.0/16
      FrontendSubnet1    : .0.0/24
      FrontendSubnet2    : .1.0/24
      ApplicationSubnet1 : .10.0/24
      ApplicationSubnet2 : .11.0/24
      DatastoreSubnet1   : .20.0/24
      DatastoreSubnet2   : .21.0/24

#------------------------------------------------------------------------------
Resources:
#------------------------------------------------------------------------------

  #------------------------------------------------------------------------------
  Vpc:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, Vpc ]}]
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-vpc

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

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway

  #------------------------------------------------------------------------------
  FrontendRouteTable:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-frontend-rtb

  FrontendRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref FrontendRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  #------------------------------------------------------------------------------
  ApplicationRouteTable1:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-application1-rtb

  ApplicationRoute1:
    Type: AWS::EC2::Route
    Condition: EnableNatGateway
    Properties:
      RouteTableId: !Ref ApplicationRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  #------------------------------------------------------------------------------
  ApplicationRouteTable2:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-application2-rtb

  ApplicationRoute2:
    Type: AWS::EC2::Route
    Condition: EnableNatGateway
    Properties:
      RouteTableId: !Ref ApplicationRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  #------------------------------------------------------------------------------
  DataStoreRouteTable:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-datastore-rtb

  #------------------------------------------------------------------------------
  NetworkACL:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::NetworkAcl
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-nacl
      VpcId: !Ref Vpc

  NetworkACLEntryEgress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: true
      NetworkAclId: !Ref NetworkACL
      Protocol: -1
      RuleAction : allow
      RuleNumber : 100

  NetworkACLEntryIngress:
    Type: AWS::EC2::NetworkAclEntry
    Properties:
      CidrBlock: 0.0.0.0/0
      Egress: false
      NetworkAclId: !Ref NetworkACL
      Protocol: -1
      RuleAction : allow
      RuleNumber : 100

  #------------------------------------------------------------------------------
  FrontendSubnet1:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [ 0, "Fn::GetAZs": { Ref: "AWS::Region" } ]
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, FrontendSubnet1 ]}]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-frontend1-subnet
      VpcId: !Ref Vpc

  FrontendSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref FrontendSubnet1
      RouteTableId: !Ref FrontendRouteTable

  FrontendSubnet1NACLAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref FrontendSubnet1
      NetworkAclId: !Ref NetworkACL

  #------------------------------------------------------------------------------
  FrontendSubnet2:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [ 1, "Fn::GetAZs": { Ref: "AWS::Region" } ]
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, FrontendSubnet2 ]}]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-frontend2-subnet
      VpcId: !Ref Vpc

  FrontendSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref FrontendSubnet2
      RouteTableId: !Ref FrontendRouteTable

  FrontendSubnet2NACLAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref FrontendSubnet2
      NetworkAclId: !Ref NetworkACL

  #------------------------------------------------------------------------------
  ApplicationSubnet1:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [ 0, "Fn::GetAZs": { Ref: "AWS::Region" } ]
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, ApplicationSubnet1 ]}]
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-application1-subnet
      VpcId: !Ref Vpc

  ApplicationSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref ApplicationSubnet1
      RouteTableId: !Ref ApplicationRouteTable1

  ApplicationSubnet1NACLAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref ApplicationSubnet1
      NetworkAclId: !Ref NetworkACL

  #------------------------------------------------------------------------------
  ApplicationSubnet2:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [ 1, "Fn::GetAZs": { Ref: "AWS::Region" } ]
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, ApplicationSubnet2 ]}]
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-application2-subnet
      VpcId: !Ref Vpc

  ApplicationSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref ApplicationSubnet2
      RouteTableId: !Ref ApplicationRouteTable2

  ApplicationSubnet2NACLAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref ApplicationSubnet2
      NetworkAclId: !Ref NetworkACL

  #------------------------------------------------------------------------------
  DatastoreSubnet1:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [ 0, "Fn::GetAZs": { Ref: "AWS::Region" } ]
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, DatastoreSubnet1 ]}]
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-datastore1-subnet
      VpcId: !Ref Vpc

  DatastoreSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref DatastoreSubnet1
      RouteTableId: !Ref DataStoreRouteTable

  DatastoreSubnet1NACLAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref DatastoreSubnet1
      NetworkAclId: !Ref NetworkACL

  #------------------------------------------------------------------------------
  DatastoreSubnet2:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [ 1, "Fn::GetAZs": { Ref: "AWS::Region" } ]
      CidrBlock: !Sub [ "${VpcCidr}${Subnet}", { Subnet: !FindInMap [ VpcConfig, !Ref Env, DatastoreSubnet2 ]}]
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-datastore2-subnet
      VpcId: !Ref Vpc

  DatastoreSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref DatastoreSubnet2
      RouteTableId: !Ref DataStoreRouteTable

  DatastoreSubnet2NACLAssociation:
    Type: AWS::EC2::SubnetNetworkAclAssociation
    Properties:
      SubnetId: !Ref DatastoreSubnet2
      NetworkAclId: !Ref NetworkACL

  #------------------------------------------------------------------------------
  NatGateway1:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::NatGateway
    Condition: EnableNatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP1.AllocationId
      SubnetId: !Ref FrontendSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-ngw1

  NatGatewayEIP1:
    Type: AWS::EC2::EIP
    Condition: EnableNatGateway
    Properties:
      Domain: vpc

  #------------------------------------------------------------------------------
  NatGateway2:
  #------------------------------------------------------------------------------
    Type: AWS::EC2::NatGateway
    Condition: EnableNatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP2.AllocationId
      SubnetId: !Ref FrontendSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${SystemName}-${Env}-ngw2

  NatGatewayEIP2:
    Type: AWS::EC2::EIP
    Condition: EnableNatGateway
    Properties:
      Domain: vpc

#------------------------------------------------------------------------------
Outputs:
#------------------------------------------------------------------------------
  Vpc:
    Value: !Ref Vpc
    Export:
      Name: !Sub ${SystemName}-${Env}-vpc
  FrontendSubnet1:
    Value: !Ref FrontendSubnet1
    Export:
      Name: !Sub ${SystemName}-${Env}-frontend1-subnet
  FrontendSubnet2:
    Value: !Ref FrontendSubnet2
    Export:
      Name: !Sub ${SystemName}-${Env}-frontend2-subnet
  ApplicationSubnet1:
    Value: !Ref ApplicationSubnet1
    Export:
      Name: !Sub ${SystemName}-${Env}-application1-subnet
  ApplicationSubnet2:
    Value: !Ref ApplicationSubnet2
    Export:
      Name: !Sub ${SystemName}-${Env}-application2-subnet
  DatastoreSubnet1:
    Value: !Ref DatastoreSubnet1
    Export:
      Name: !Sub ${SystemName}-${Env}-datastore1-subnet
  DatastoreSubnet2:
    Value: !Ref DatastoreSubnet2
    Export:
      Name: !Sub ${SystemName}-${Env}-datastore2-subnet

まとめ

NAT Gatewayを簡単に作成、削除できることが確認できました。条件の使い過ぎは可読性や保守性が落ちるのでおすすめしませんが、用途によっては非常に便利なことがわかりました。

なにかの参考になれば幸いです。