6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudFormation で閉域 VPC、TGW、Route 53 Resolver の検証環境を一気呵成に構築する

Posted at

最近ガバメントクラウド関係の業務で CloudFormation のテンプレートを読む機会がありました。

そこで、勉強がてら、これまでガバメントクラウド環境の勉強としてよく作ってきた閉域 VPC や Transit Gateway (TGW)、Route 53 インバウンドエンドポイント・アウトバウンドエンドポイント辺りの検証環境を CloudFormation で一気にデプロイできないかと思い立ったので、早速やってみました。

CloudFormation で構築する閉域 VPC 環境の概要

今回 CloudFormation で作成するリソースの構成図は以下のとおりです。赤点線の枠で囲った範囲を今回 CloudFormation で作成してみます。

AWSNetwork2.drawio.png

その他今回作成する環境の概要は次のとおりです。

  • ガバメントクラウドを想定した検証環境のため、マルチアカウント(アカウント A、B とします)間で VPC を接続します。
  • アカウント A 側には既に VPC があり、パブリックサブネットにある VPN インスタンス経由でオンプレミス(自宅)の構築済み DNS サーバーと接続されています。
  • アカウント A 側に CloudFormation で TGW、プライベートサブネット、Route 53 インバウンドエンドポイント、アウトバウンドエンドポイントを作成します。
  • アカウント B には一から VPC を作成し、TGW アタッチメントでアカウント A の VPC やオンプレミスに接続します。
  • マルチアカウント構成のため、途中 TGW や Route 53 リゾルバールールを CloudFormation ではなく RAM で直接共有をかけているので、これを想定して CloudFormation スタックは一発でリソースを作るのではなく、段階ごとに分けてリソースを作成するようにしています。

構築作業の流れ

  1. CloudFormation でアカウント A 側に TGW を作成し、既存 VPC と接続
  2. アカウント A の TGW を Resource Access Manager (RAM) でアカウント B へ共有
  3. アカウント B の RAM で TGW の共有を承認
  4. アカウント B の CloudFormation で VPC と DHCP オプションセットを作成し、共有された TGW にアタッチメントを作成
  5. アカウント A の CloudFormation でアカウント B のサブネットへ向かう VPC ルートテーブル、TGW ルートテーブルを作成(アカウント B の TGW アタッチメントが作成された後)
  6. アカウント A の CloudFormation で Route 53 インバウンドエンドポイント、アウトバウンドエンドポイント、作成したアウトバウンドエンドポイント経由でオンプレミス側のドメインへの名前解決をオンプレミス DNS サーバーへ向けるリゾルバールールを作成
  7. アカウント A のリゾルバールールを RAM でアカウント B へ共有
  8. アカウント B の RAM で共有されたリゾルバールールを承認
  9. アカウント A のCloudFormation で既存 VPC に関連付ける Route 53 プライベートホストゾーンを作成
  10. アカウント B の VPC に任意のインスタンスを作成し、オンプレミスのドメイン、アカウント A の Route 53 プライベートホストゾーンのドメインを名前解決できればゴール

それでは作成した CloudFormation テンプレートを見ていきます。

アカウント A の VPC に TGW などを作成

CloudFormation テンプレート

まず最初に流す CloudFormation テンプレートは以下のとおりです。アカウント A の既存 VPC に TGW などを作成していきます。

AWSTemplateFormatVersion: 2010-09-09
Description: This template deploys a Subnet, with a pair of private subnets spread across two Availability Zones.

Parameters:
  # TGW アタッチメントを接続するサブネット
  PrivateTGWCidr:
    Description: Please enter the IP range (CIDR notation) for the TGW subnet in the first Availability Zone
    Type: String
    Default: 10.128.16.0/20

  PrivateSubnet1Cidr:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.128.128.0/20

  PrivateSubnet2Cidr:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone
    Type: String
    Default: 10.128.144.0/20

  # 既存の VPC にリソースを作成するため、あらかじめ VPC ID をパラメーターへセット
  VPCId:
    Description: Please enter the VPC ID
    Type: AWS::EC2::VPC::Id
    Default: vpc-hogehoge123

  TGWAsn:
    Description: Please enter the ASN for TGW
    Type: Number
    Default: 65531

  OnpremissCidr:
    Description: Please enter the Onpremiss CIDR
    Type: String
    Default: 10.0.0.0/8

  # オンプレミスと VPN 接続している EC2 インスタンスのネットワークインターフェース ID を
  # オンプレミスへのルートテーブル用にパラメーターへセット
  VPNApplanceENIId:
    Description: Please enter the ENI ID that connecting to onpremiss by VPN
    Type: String
    Default: eni-fugafuga456

  CostTagValue:
    Type: String
    Description: Set CostTag Value
    Default: testtag01

Resources:
  #
  # VPC サブネットの作成
  #
  PrivateTGWSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPCId
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Ref PrivateTGWCidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: test01-dev-subnet-private-ns-az1

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPCId
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Ref PrivateSubnet1Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: test01-dev-subnet-private-endpoint-az1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPCId
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !Ref PrivateSubnet2Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: test01-dev-subnet-private-endpoint-az2

  #
  # VPC ルートテーブルの作成
  #
  PrivateTGWSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPCId
      Tags:
        - Key: Name
          Value: test01-dev-rtb-private-ns-az1

  PrivateTGWSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateTGWSubnetRouteTable
      SubnetId: !Ref PrivateTGWSubnet

  PrivateTGWSubnetRouteTableRoute1:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      RouteTableId: !Ref PrivateTGWSubnetRouteTable
      NetworkInterfaceId: !Ref VPNApplanceENIId

  PrivateSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPCId
      Tags:
        - Key: Name
          Value: test01-dev-rtb-private-endpoint-az1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet1RouteTableRoute1:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      RouteTableId: !Ref PrivateSubnet1RouteTable
      NetworkInterfaceId: !Ref VPNApplanceENIId

  PrivateSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPCId
      Tags:
        - Key: Name
          Value: test01-dev-rtb-private-endpoint-az2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      SubnetId: !Ref PrivateSubnet2

  PrivateSubnet2RouteTableRoute1:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      RouteTableId: !Ref PrivateSubnet2RouteTable
      NetworkInterfaceId: !Ref VPNApplanceENIId

  #
  # Transit Gateway の作成
  #
  # 別アカウントからの TGW アタッチメントの自動承認は
  # 本当は無効にしたいが、有効にしないと、
  # 手動で承認しないと別アカウントで TGW アタッチメントを作成する
  # CloudFormation スタックが失敗してしまうため、
  # 敢えて自動承認を有効とした。
  #
  TGW:
    Type: AWS::EC2::TransitGateway
    Properties:
      AmazonSideAsn: !Ref TGWAsn
      AutoAcceptSharedAttachments: enable
      DefaultRouteTableAssociation: disable
      DefaultRouteTablePropagation: disable
      Tags:
        - Key: Name
          Value: test01-dev-tgw

  TGWAttachment1:
    Type: AWS::EC2::TransitGatewayAttachment
    Properties:
      Options:
        DnsSupport: enable
        SecurityGroupReferencingSupport: enable
      SubnetIds:
        - !Ref PrivateTGWSubnet
      Tags:
        - Key: Name
          Value: test01-dev-tgw-attach-vpc-tokyo
        - Key: CostTag
          Value:
            Ref: CostTagValue
      TransitGatewayId: !Ref TGW
      VpcId: !Ref VPCId

  # アカウント A VPC 用の TGW ルートテーブル
  TGWRouteTable1:
    Type: AWS::EC2::TransitGatewayRouteTable
    DependsOn: TGWAttachment1
    Properties:
      Tags:
        - Key: Name
          Value: test01-dev-tgw-rtb-ns-ew
      TransitGatewayId: !Ref TGW

  TGWRouteTable1Route1:
    Type: AWS::EC2::TransitGatewayRoute
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      TransitGatewayAttachmentId: !Ref TGWAttachment1
      TransitGatewayRouteTableId: !Ref TGWRouteTable1

  TGWRouteTable1Association1:
    Type: AWS::EC2::TransitGatewayRouteTableAssociation
    Properties:
      TransitGatewayAttachmentId: !Ref TGWAttachment1
      TransitGatewayRouteTableId: !Ref TGWRouteTable1

  TGWRouteTable1Propagation1:
    Type: AWS::EC2::TransitGatewayRouteTablePropagation
    Properties:
      TransitGatewayAttachmentId: !Ref TGWAttachment1
      TransitGatewayRouteTableId: !Ref TGWRouteTable1

  #
  # S3 ゲートウェイエンドポイントの作成
  #
  # S3 ゲートウェイエンドポイントは作成しておいた方が何かと便利
  #
  S3GatewayEndpoint:
    Type: 'AWS::EC2::VPCEndpoint'
    Properties:
      VpcEndpointType: 'Gateway'
      VpcId: !Ref VPCId
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
      RouteTableIds:
        - !Ref PrivateSubnet1RouteTable
        - !Ref PrivateSubnet2RouteTable

#
# 別アカウントの VPC へのルートテーブルを作成する後続のスタックに渡したい値を出力
#
Outputs:
  TGW:
    Value: !Ref TGW
    Export:
      Name: TGW
  TGWAttachment1:
    Value: !Ref TGWAttachment1
    Export:
      Name: TGWAttachment1
  PrivateTGWSubnetRouteTable:
    Value: !Ref PrivateTGWSubnetRouteTable
    Export:
      Name: PrivateTGWSubnetRouteTable
  PrivateSubnet1RouteTable:
    Value: !Ref PrivateSubnet1RouteTable
    Export:
      Name: PrivateSubnet1RouteTable
  PrivateSubnet2RouteTable:
    Value: !Ref PrivateSubnet2RouteTable
    Export:
      Name: PrivateSubnet2RouteTable
  TGWRouteTable1:
    Value: !Ref TGWRouteTable1
    Export:
      Name: TGWRouteTable1

テンプレートから CloudFormation スタックを作成

作成したテンプレートをアップロードして CloudFormation スタックを作成します。

スクリーンショット 2025-10-04 18.08.55.png

パラメーターで設定値を変えられるようにしています。

スクリーンショット 2025-10-04 18.09.43.png

スタックの作成に成功するとテンプレートで指定したリソースが作成されます。TGW の ID を確認して RAM へ移ります。

スクリーンショット 2025-10-04 23.05.26.png

RAM でアカウント B へ TGW を共有

RAM のリソース共有へ進み、作成された TGW を選択します。

スクリーンショット 2025-10-04 18.16.59.png

アカウント B へ TGW を共有します。

スクリーンショット 2025-10-04 18.17.59.png

アカウント B の RAM で TGW の共有を承認

アカウント B 側の RAM で共有された TGW を承認します。これでアカウント A の TGW にアタッチメントを作成できます。

スクリーンショット 2025-10-04 18.19.17.png

アカウント B に VPC と TGW アタッチメントを作成

CloundFormation テンプレート

次にアカウント B の VPC と、TGW アタッチメントを作成します。

AWSTemplateFormatVersion: 2010-09-09
Description: This template deploys a VPC, with a pair of private subnets spread across two Availability Zones.

Parameters:
  EnvironmentName:
    Description: An environment name that is prefixed to resource names
    Type: String
    Default: test02-dev-vpc

  VPCCidr:
    Description: Please enter the IP range (Cidr notation) for this VPC
    Type: String
    Default: 10.129.0.0/16

  # TGW 用のサブネット
  PrivateTGWCidr:
    Description: Please enter the IP range (Cidr notation) for the TGW subnet in the first Availability Zone
    Type: String
    Default: 10.129.16.0/20

  PrivateSubnet1Cidr:
    Description: Please enter the IP range (Cidr notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.129.128.0/20

  PrivateSubnet2Cidr:
    Description: Please enter the IP range (Cidr notation) for the private subnet in the second Availability Zone
    Type: String
    Default: 10.129.144.0/20

  OnpremissCidr:
    Description: Please enter the Onpremiss CIDR
    Type: String
    Default: 10.0.0.0/8

  # Transit Gateway はアカウント A のスタックで作成しているため手作業で値をパラメーターへセット
  TGWId:
    Description: Please enter the TGW ID
    Type: String

  VPC2Cidr:
    Description: Please enter the other VPC CIDR
    Type: String
    Default: 10.128.0.0/16

  LocalDomainName:
    Description: Please enter the local domain name in this VPC
    Type: String
    Default: test02.intra.morori.jp

  # Route 53 インバウンドエンドポイントで使う IP アドレスは予め決めておきパラメーターへセット
  # DHCP オプションセットに使うため
  Route53InboundEndpointIpAddress1:
    Description: Please enter the IP Address for the first Route 53 Inbound Endpoint Address
    Type: String
    Default: 10.128.128.50

  Route53InboundEndpointIpAddress2:
    Description: Please enter the IP Address for the second Route 53 Inbound Endpoint Address
    Type: String
    Default: 10.128.144.50

  CostTagValue:
    Type: String
    Description: Set CostTag Value
    Default: testtag02

Resources:
  #
  # VPC の作成
  #
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidr
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
  #
  # サブネットの作成
  #
  PrivateTGWSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Ref PrivateTGWCidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: test02-dev-subnet-private-ns-az1

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Ref PrivateSubnet1Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: test02-dev-subnet-private-server-az1

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      CidrBlock: !Ref PrivateSubnet2Cidr
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: test02-dev-subnet-private-server-az2

  #
  # VPC ルートテーブルの作成
  #
  PrivateTGWSubnetRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: test02-dev-rtb-private-ns-az1

  PrivateTGWSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateTGWSubnetRouteTable
      SubnetId: !Ref PrivateTGWSubnet

  PrivateTGWSubnetRouteTableRoute1:
    Type: AWS::EC2::Route
    DependsOn: TGWAttachment1
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      RouteTableId: !Ref PrivateTGWSubnetRouteTable
      TransitGatewayId: !Ref TGWId

  PrivateTGWSubnetRouteTableRoute2:
    Type: AWS::EC2::Route
    DependsOn: TGWAttachment1
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !Ref PrivateTGWSubnetRouteTable
      TransitGatewayId: !Ref TGWId

  PrivateSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: test02-dev-rtb-private-server-az1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      SubnetId: !Ref PrivateSubnet1

  # オンプレミス宛てのルートテーブルはネクストホップを TGW とする
  PrivateSubnet1RouteTableRoute1:
    Type: AWS::EC2::Route
    DependsOn: TGWAttachment1
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      RouteTableId: !Ref PrivateSubnet1RouteTable
      TransitGatewayId: !Ref TGWId

  # アカウント A の VPC 宛てのルートテーブルはネクストホップを TGW とする
  PrivateSubnet1RouteTableRoute2:
    Type: AWS::EC2::Route
    DependsOn: TGWAttachment1
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !Ref PrivateSubnet1RouteTable
      TransitGatewayId: !Ref TGWId

  PrivateSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: test02-dev-rtb-private-server-az2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      SubnetId: !Ref PrivateSubnet2

  PrivateSubnet2RouteTableRoute1:
    Type: AWS::EC2::Route
    DependsOn: TGWAttachment1
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      RouteTableId: !Ref PrivateSubnet2RouteTable
      TransitGatewayId: !Ref TGWId

  PrivateSubnet2RouteTableRoute2:
    Type: AWS::EC2::Route
    DependsOn: TGWAttachment1
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !Ref PrivateSubnet2RouteTable
      TransitGatewayId: !Ref TGWId

  #
  # TGW アタッチメントを作成
  #
  TGWAttachment1:
    Type: AWS::EC2::TransitGatewayAttachment
    Properties:
      Options:
        DnsSupport: enable
        SecurityGroupReferencingSupport: enable
      SubnetIds:
        - !Ref PrivateTGWSubnet
      Tags:
        - Key: Name
          Value: test02-dev-tgw-attach-vpc-tokyo
        - Key: CostTag
          Value:
            Ref: CostTagValue
      TransitGatewayId: !Ref TGWId
      VpcId: !Ref VPC

  #
  # DHCP オプションセットを作成
  #
  # DNS 参照先をアカウント A VPC の Route 53 インバウンドエンドポイントに設定する
  DHCPOptionSet:
    Type: AWS::EC2::DHCPOptions
    Properties:
      DomainName: !Ref LocalDomainName
      DomainNameServers:
        - !Ref Route53InboundEndpointIpAddress1
        - !Ref Route53InboundEndpointIpAddress2
      Tags:
        - Key: Name
          Value: test02-dev-dhcpoptionset

  DHCPOptionSetAssociation:
    Type: AWS::EC2::VPCDHCPOptionsAssociation
    Properties:
      DhcpOptionsId: !Ref DHCPOptionSet
      VpcId: !Ref VPC

  #
  # S3 ゲートウェイエンドポイントを作成
  #
  S3GatewayEndpoint:
    Type: 'AWS::EC2::VPCEndpoint'
    Properties:
      VpcEndpointType: 'Gateway'
      VpcId: !Ref VPC
      ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'
      RouteTableIds:
        - !Ref PrivateSubnet1RouteTable
        - !Ref PrivateSubnet2RouteTable

アカウント B でテンプレートから CloudFormation スタックを作成

テンプレートのパラメーターのうち TGW ID については、アカウント A で作成した TGW の ID を手作業でセットします。

スクリーンショット 2025-10-04 18.22.06.png

アカウント B の VPC と TGW アタッチメントが作成できたら、アカウント A の VPC にこれらへ向けたルートテーブルを作成していきます。

アカウント A の VPC ルートテーブル、TGW ルートテーブルを作成

CloudFormation テンプレートは以下のとおりです。

AWSTemplateFormatVersion: 2010-09-09
Description: This template deploys a route table for other vpc is connected by TGW

Parameters:
  VPC1Cidr:
    Description: Please enter the VPC CIDR
    Type: String
    Default: 10.128.0.0/16

  VPC2Cidr:
    Description: Please enter the other VPC CIDR
    Type: String
    Default: 10.129.0.0/16

  OnpremissCidr:
    Description: Please enter the Onpremiss CIDR
    Type: String
    Default: 10.0.0.0/8

  # オンプレミスに VPN 接続している EC2 インスタンスがあるパブリックサブネットに
  # アカウント B VPC へのルートテーブルを追加するため値をセット
  VPNSubnetRouteTable:
    Description: Please enter vpn  subnet which is conneceted to the onpremiss route table ID
    Type: String
    Default: rtb-hogefuga789

  # 別アカウントの TGW アタッチメント ID は手動でセット
  TGWAttachment2:
    Description: Please enter the other TGW Attachment ID
    Type: String

Resources:
  #
  #アカウント B VPC へのルートを各 VPC ルートテーブルへ追加
  #
  VPNSubnetRouteTableRoute:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !Ref VPNSubnetRouteTable
      TransitGatewayId: !ImportValue TGW

  PrivateTGWSubnetRouteTableRoute2:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !ImportValue PrivateTGWSubnetRouteTable
      TransitGatewayId: !ImportValue TGW

  PrivateSubnet1RouteTableRoute2:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !ImportValue PrivateSubnet1RouteTable
      TransitGatewayId: !ImportValue TGW

  PrivateSubnet2RouteTableRoute2:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      RouteTableId: !ImportValue PrivateSubnet2RouteTable
      TransitGatewayId: !ImportValue TGW

  #
  # アカウント B VPC へのルートを TGW ルートテーブルへ追加
  #
  TGWRouteTable1Route2:
    Type: AWS::EC2::TransitGatewayRoute
    Properties:
      DestinationCidrBlock: !Ref VPC2Cidr
      TransitGatewayAttachmentId: !Ref TGWAttachment2
      TransitGatewayRouteTableId: !ImportValue TGWRouteTable1

  #
  # アカウント B VPC 用の TGW ルートテーブルを作成
  #
  TGWRouteTable2:
    Type: AWS::EC2::TransitGatewayRouteTable
    Properties:
      Tags:
        - Key: Name
          Value: test02-dev-tgw-rtb-ns-ew
      TransitGatewayId: !ImportValue TGW

  TGWRouteTable2Route1:
    Type: AWS::EC2::TransitGatewayRoute
    Properties:
      DestinationCidrBlock: !Ref OnpremissCidr
      TransitGatewayAttachmentId: !ImportValue TGWAttachment1
      TransitGatewayRouteTableId: !Ref TGWRouteTable2

  TGWRouteTable2Route2:
    Type: AWS::EC2::TransitGatewayRoute
    Properties:
      DestinationCidrBlock: !Ref VPC1Cidr
      TransitGatewayAttachmentId: !ImportValue TGWAttachment1
      TransitGatewayRouteTableId: !Ref TGWRouteTable2

  TGWRouteTable2Association1:
    Type: AWS::EC2::TransitGatewayRouteTableAssociation
    Properties:
      TransitGatewayAttachmentId: !Ref TGWAttachment2
      TransitGatewayRouteTableId: !Ref TGWRouteTable2

  TGWRouteTable2Propagation1:
    Type: AWS::EC2::TransitGatewayRouteTablePropagation
    Properties:
      TransitGatewayAttachmentId: !Ref TGWAttachment2
      TransitGatewayRouteTableId: !Ref TGWRouteTable2

先に TGW などを作成したスタックから必要な情報を出力して取り込んでいるので、パラメーターは先ほどより少なくなっています。

ただし、アカウント B の TGW アタッチメントの ID は手動でセットするようになっています。

スクリーンショット 2025-10-04 18.31.18.png

アカウント A VPC に Route 53 Resolver などを作成

CloudFormation テンプレートは以下のとおりです。

AWSTemplateFormatVersion: 2010-09-09
Description: This template deploys a Route 53 Resolvers.

Parameters:
  VPCId:
    Description: Please enter the VPC ID
    Type: AWS::EC2::VPC::Id
    Default: vpc-hogehoge123

  # Route 53 Resolver を作成するサブネットを指定
  PrivateSubnet1:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: AWS::EC2::Subnet::Id

  PrivateSubnet2:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone
    Type: AWS::EC2::Subnet::Id

  Route53InboundEndpointIpAddress1:
    Type: String
    Description: Please enter the IP Address for the first Route 53 Inbound Endpoint Address
    Default: 10.128.128.50

  Route53InboundEndpointIpAddress2:
    Type: String
    Description: Please enter the IP Address for the second Route 53 Inbound Endpoint Address
    Default: 10.128.144.50

  Route53OutboundEndpointIpAddress1:
    Type: String
    Description: Please enter the IP Address for the first Route 53 Inbound Endpoint Address
    Default: 10.128.128.51

  Route53OutboundEndpointIpAddress2:
    Type: String
    Description: Please enter the IP Address for the second Route 53 Inbound Endpoint Address
    Default: 10.128.144.51

  OnpremissCidr:
    Description: Please enter the Onpremiss CIDR
    Type: String
    Default: 10.0.0.0/8

  # オンプレミス側のリゾルバー DNS サーバーの IP アドレスを指定
  OnpremissResolverIpAddress:
    Type: String
    Description: Please enter the IP Address for the onpremiss resolover Address
    Default: 10.1.1.3

  CostTagValue:
    Type: String
    Description: Set CostTag Value
    Default: testtag01

Resources:
  #
  # Route 53 インバウンドエンドポイントの作成
  #
  Route53InboundEndpoint:
    Type: AWS::Route53Resolver::ResolverEndpoint
    Properties:
      Direction: INBOUND
      IpAddresses:
        - Ip: !Ref Route53InboundEndpointIpAddress1
          SubnetId: !Ref PrivateSubnet1
        - Ip: !Ref Route53InboundEndpointIpAddress2
          SubnetId: !Ref PrivateSubnet2
      Name: test01-dev-vpce-route53inbound-interface
      Protocols:
        - Do53
      ResolverEndpointType: IPV4
      SecurityGroupIds:
        - !Ref AllowDNSSecurityGroup
      Tags:
        - Key: CostTag
          Value:
            Ref: CostTagValue

  #
  # Route 53 アウトバウンドエンドポイントの作成
  #
  Route53OutboundEndpoint:
    Type: AWS::Route53Resolver::ResolverEndpoint
    Properties:
      Direction: OUTBOUND
      IpAddresses:
        - Ip: !Ref Route53OutboundEndpointIpAddress1
          SubnetId: !Ref PrivateSubnet1
        - Ip: !Ref Route53OutboundEndpointIpAddress2
          SubnetId: !Ref PrivateSubnet2
      Name: test01-dev-vpce-route53outbound-interface
      Protocols:
        - Do53
      ResolverEndpointType: IPV4
      SecurityGroupIds:
        - !Ref AllowDNSSecurityGroup
      Tags:
        - Key: CostTag
          Value:
            Ref: CostTagValue

  #
  # UDP 53, TCP 53 を許可するエンドポイント用のセキュリティグループを作成
  #
  AllowDNSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: test01-dev-sg-route53resolver-dns-vpce
      GroupDescription: Security group with allowing DNS query rule
      SecurityGroupIngress:
        - CidrIp: !Ref OnpremissCidr
          FromPort: 53
          IpProtocol: udp
          ToPort: 53
        - CidrIp: !Ref OnpremissCidr
          FromPort: 53
          IpProtocol: tcp
          ToPort: 53
      VpcId: !Ref VPCId

  #
  # リゾルバールールの作成
  #
  # オンプレミスのドメインの名前解決要求をアウトバウンドエンドポイント経由で
  # オンプレミスのリゾルバー DNS サーバーへ転送する
  Route53ResolverRule:
    Type: AWS::Route53Resolver::ResolverRule
    Properties: 
      DomainName: intra.morori.jp
      Name: test01-dev-resolverrule-onpremiss
      ResolverEndpointId: !Ref Route53OutboundEndpoint
      RuleType: FORWARD 
      TargetIps:
        - 
          Ip: !Ref OnpremissResolverIpAddress
          Port: 53

  Route53ResolverRuleAssociation:
    Type: AWS::Route53Resolver::ResolverRuleAssociation
    Properties:
      Name: test01-dev-route53resolverruleassociation
      ResolverRuleId: !Ref Route53ResolverRule
      VPCId: !Ref VPCId

リゾルバールールをアカウント B へ RAM で共有する

作成したリゾルバールールは RAM でアカウント B へ共有します。手順は割愛します。

スクリーンショット 2025-10-04 21.00.10.png

Route 53 プライベートホストゾーンを作成しアカウント A VPC に関連付ける

CloudFormation テンプレートは以下のとおりです。

AWSTemplateFormatVersion: 2010-09-09
Description: This template deploys a Route 53 Private Hosted Zone.

Parameters:
  VPCId:
    Description: Please enter the VPC ID you will associate with
    Type: AWS::EC2::VPC::Id
  PrivateDomainName:
    Description: Please enter the private domain name
    Type: String
    Default: test01.intra.morori.jp
  Record1FQDN:
    Description: Please enter the first record's FQDN
    Type: String
    Default: dummy.test01.intra.morori.jp.
  Record1IpAddress:
    Description: Please enter the first record's IP Address
    Type: String
    Default: 10.128.128.52

Resources:
  PrivateHostedZone:
    Type: AWS::Route53::HostedZone
    Properties:
      Name: !Ref PrivateDomainName
      VPCs:
        - VPCId: !Ref VPCId
          VPCRegion: !Ref AWS::Region

  RecordSet1:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref PrivateHostedZone
      Name: !Ref Record1FQDN
      ResourceRecords:
        - !Ref Record1IpAddress
      TTL: 900
      Type: A

アカウント B の VPC から TGW と Route 53 Resolver への疎通を確認

期待どおりに環境ができていれば、アカウント B の VPC から DHCP オプションセットで DNS は Route 53 インバウンドエンドポイントを参照し、TGW 経由でルーティングしてアクセスできるはずで、また、オンプレミスのドメインはリゾルバールールでアウトバウンドエンドポイントを経由してオンプレミスの DNS サーバーへ名前解決要求がルーティングして到達するはずです。

確認のため、アカウント B の VPC に EC2 インスタンスを立ててみます。これも CloudFormation
でやってみます。

AWSTemplateFormatVersion: 2010-09-09
Description: This template deploys a EC2 instance.

Parameters:
  # Amazon Linx 2023 の最新の AMI ID を自動で取得する
  AmiId:
    Description: Please enter the AMI ID for this intance
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-arm64

  InstanceType:
    Description: Please enter the instance type for this intance
    Type: String
    Default: t4g.nano

  InstanceName:
    Description: Please enter the instance name
    Type: String
    Default: test02-dev-ec2-test-ap
  KeyName:
    Description: Please enter the key-pair name
    Type: String
    Default: test02

  NetworkInterfaceSubnetId:
    Description: Please enter the subnet id for this instance's network interface
    Type: AWS::EC2::Subnet::Id

  InstanceIpAddress:
    Description: Please enter the IP Address for this instance
    Type: String
    Default: 10.129.128.101

  VPCId:
    Description: Please enter the VPC ID
    Type: AWS::EC2::VPC::Id

  OnpremissCidr:
    Description: Please enter the Onpremiss CIDR
    Type: String
    Default: 10.0.0.0/8

  CostTagValue:
    Type: String
    Description: Set CostTag Value
    Default: testtag02

Resources:
  #
  # EC2 インスタンスの作成
  #
  TestInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AmiId
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      NetworkInterfaces:
        - AssociatePublicIpAddress: false
          DeleteOnTermination: yes
          DeviceIndex: 0
          GroupSet:
            - !Ref EC2SecurityGroup
          SubnetId: !Ref NetworkInterfaceSubnetId
          PrivateIpAddress: !Ref InstanceIpAddress
      Tags:
        - Key: Name
          Value: !Ref InstanceName
        - Key: CostTag
          Value: !Ref CostTagValue

  #
  # EC2 インスタンス用のセキュリティグループを作成
  #
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: test01-dev-sg-test-ap-ec2
      GroupDescription: Security group with allowing all ICMP and SSH rule
      SecurityGroupIngress:
        - CidrIp: !Ref OnpremissCidr
          FromPort: -1
          IpProtocol: icmp
          ToPort: -1
        - CidrIp: !Ref OnpremissCidr
          FromPort: 22
          IpProtocol: tcp
          ToPort: 22
      VpcId: !Ref VPCId

インスタンスが作成できたら、早速オンプレミスのドメイン (intra.morori.jp) と、アカウント A の VPC に関連付けられている Route 53 プライベートホストゾーンのドメイン (test01.intra.morori.jp) の名前解決を試してみます。

スクリーンショット 2025-10-04 21.07.40.png

これで TGW も Route 53 Resolver も疎通できていることが確認できました。

スタックを削除してリソースを全削除する

今回作成した CloudFormation テンプレートでは、スタック削除時にリソースを残さない設定にしているため、スタックを削除することでテンプレートから作成したリソースは全て削除できます。

TGW アタッチメントや Route 53 Reslover は料金が高いため、検証が終わったらすぐ削除したいですし、また、もし削除を忘れてしまうと高額な課金に繋がるため、作成したリソースを一気に削除できるのはありがたいです。

まとめ

このように検証環境を CloudFormation で作ると、短時間で環境を再現したり、使い終わった環境を簡単に削除できるようになります。

また、CloudFormation のユーザーズガイドを読むと、各リソースの詳細なオプションが確認できるため、とても勉強になりますのでおすすめです。

最後に、今回の環境で使っているオンプレミス環境からの VPN 接続や DNS サーバーの構築方法を、過去の記事にまとめていますので、良かったら参考にしてください。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?