リージョナル NAT Gateway がトランジットゲートウェイ(TGW)をサポートしたのでアウトバウンド通信の集約構成を試してみた
2026年5月、AWS 公式ドキュメントが更新され、以下の記載が追加されていることを確認しました。
リージョン NAT ゲートウェイは、リージョン NAT ゲートウェイルートテーブルで有効なルートとして AWS Transit Gateway をサポートします
本記事では実際にアウトバウンド通信の集約(Centralized Egress)構成を構築し、動作を確認しました。
のんピさんの記事(2025/11/22)で「リージョナル NAT Gateway のエッジルートテーブルのルートのターゲットとして Transit Gateway を指定できるようになるのを待ちましょう」と結論づけられていた制限が解消されたことを確認できました。
背景:GA 時の制限と先行検証
Centralized Egress(アウトバウンド通信の集約)とは
複数の VPC のインターネット出口を 1 つの Egress VPC に集約する構成です。Transit Gateway で Spoke VPC のトラフィックを Egress VPC に転送し、NAT Gateway 経由でインターネットに出ます。VPC ごとに NAT Gateway を配置する必要がなくなり、EIP の管理やセキュリティポリシーの集約が可能になります。
なぜ「復路」で TGW が必要か
アウトバウンド通信の集約構成では、往路(EC2 → Internet)は Spoke → TGW → Egress VPC → NAT GW → IGW と流れます。問題は復路(Internet → EC2)です。インターネットからの戻りパケットは IGW → NAT GW(DNAT)まで来た後、Spoke VPC に返す必要があります。この「NAT GW から Spoke VPC への戻り」を制御するのがエッジルートテーブルであり、ここに TGW をターゲットとして指定できるかがこの構成成立の鍵です。
GA 時の問題
| 項目 | GA 時(2025/11) | 現在(2026/05) |
|---|---|---|
| エッジ RT に TGW 指定 | ❌ GA 後2日で削除 | ✅ |
| ワークアラウンド | ENI 直接指定(SPOF) | 不要 |
| AWS Blog のアウトバウンド集約手順 | 削除された | ドキュメントに明記 |
GA 時はエッジ RT に TGW を指定できなかったため、TGW Attachment の ENI を直接指定するワークアラウンドが使われていました。しかし ENI は単一 AZ に存在するため、その AZ が障害を起こすと全 AZ の戻りトラフィックが不通になる = SPOF リスクがありました。
検証環境の構成
※図は簡略化しています。実際は EC2 → Spoke TGW Subnet → TGW → Egress TGW Subnet → NAT GW の順に経由します。
構成のポイント:
- NAT GW は手動モード(
AvailabilityZoneAddresses)で 2AZ に EIP を明示指定しています- デプロイ直後から 2AZ で稼働します(auto mode の AZ 展開待ちが不要)
disassociate-nat-gateway-addressで特定 AZ の EIP を外すことで 1AZ 稼働を再現でき、片 AZ 障害のシミュレーションが可能になります- auto mode の自動割当 EIP ではこの操作ができないため、検証目的で手動モードを採用しました
- Appliance Mode は disable です(AZ アフィニティ維持のため)
CloudFormation テンプレート
infra.yaml(クリックで展開)
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Regional NAT Gateway + Transit Gateway - Infrastructure Stack.
Creates VPCs, TGW, Regional NAT GW, and routing.
Parameters:
ProjectName:
Type: String
Default: rnat-tgw
AZ1:
Type: AWS::EC2::AvailabilityZone::Name
Description: First Availability Zone
AZ2:
Type: AWS::EC2::AvailabilityZone::Name
Description: Second Availability Zone
Resources:
# Egress VPC
EgressVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${ProjectName}-egress-vpc
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${ProjectName}-igw
IGWAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref EgressVPC
InternetGatewayId: !Ref InternetGateway
EgressTGWSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref EgressVPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Ref AZ1
Tags:
- Key: Name
Value: !Sub ${ProjectName}-egress-tgw-az1
EgressTGWSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref EgressVPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Ref AZ2
Tags:
- Key: Name
Value: !Sub ${ProjectName}-egress-tgw-az2
EgressTGWRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EgressVPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-egress-tgw-rt
EgressTGWRouteToNAT:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentEgress
Properties:
RouteTableId: !Ref EgressTGWRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref RegionalNATGateway
EgressTGWSubnet1Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref EgressTGWSubnet1
RouteTableId: !Ref EgressTGWRouteTable
EgressTGWSubnet2Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref EgressTGWSubnet2
RouteTableId: !Ref EgressTGWRouteTable
# Regional NAT Gateway (manual mode, 2AZ with explicit EIPs)
EIP1:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${ProjectName}-nat-eip-az1
EIP2:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub ${ProjectName}-nat-eip-az2
RegionalNATGateway:
Type: AWS::EC2::NatGateway
DependsOn: IGWAttachment
Properties:
VpcId: !Ref EgressVPC
AvailabilityMode: regional
ConnectivityType: public
AvailabilityZoneAddresses:
- AvailabilityZone: !Ref AZ1
AllocationIds:
- !GetAtt EIP1.AllocationId
- AvailabilityZone: !Ref AZ2
AllocationIds:
- !GetAtt EIP2.AllocationId
Tags:
- Key: Name
Value: !Sub ${ProjectName}-regional-nat
# Edge Route: Spoke CIDR → TGW (CFn native, no Custom Resource needed)
EdgeRouteToSpoke:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentEgress
Properties:
RouteTableId: !GetAtt RegionalNATGateway.RouteTableId
DestinationCidrBlock: 10.1.0.0/16
TransitGatewayId: !Ref TransitGateway
# Spoke VPC
SpokeVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.1.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-vpc
SpokePrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SpokeVPC
CidrBlock: 10.1.1.0/24
AvailabilityZone: !Ref AZ1
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-private-az1
SpokePrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SpokeVPC
CidrBlock: 10.1.2.0/24
AvailabilityZone: !Ref AZ2
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-private-az2
SpokeTGWSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SpokeVPC
CidrBlock: 10.1.11.0/24
AvailabilityZone: !Ref AZ1
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-tgw-az1
SpokeTGWSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref SpokeVPC
CidrBlock: 10.1.12.0/24
AvailabilityZone: !Ref AZ2
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-tgw-az2
SpokePrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref SpokeVPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-private-rt
SpokeDefaultRoute:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentSpoke
Properties:
RouteTableId: !Ref SpokePrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayId: !Ref TransitGateway
SpokePrivateSubnet1Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SpokePrivateSubnet1
RouteTableId: !Ref SpokePrivateRouteTable
SpokePrivateSubnet2Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SpokePrivateSubnet2
RouteTableId: !Ref SpokePrivateRouteTable
SpokeTGWRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref SpokeVPC
Tags:
- Key: Name
Value: !Sub ${ProjectName}-spoke-tgw-rt
SpokeTGWSubnet1Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SpokeTGWSubnet1
RouteTableId: !Ref SpokeTGWRouteTable
SpokeTGWSubnet2Assoc:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref SpokeTGWSubnet2
RouteTableId: !Ref SpokeTGWRouteTable
# Transit Gateway
TransitGateway:
Type: AWS::EC2::TransitGateway
Properties:
Description: !Sub ${ProjectName} TGW
DefaultRouteTableAssociation: disable
DefaultRouteTablePropagation: disable
DnsSupport: enable
VpnEcmpSupport: enable
Tags:
- Key: Name
Value: !Sub ${ProjectName}-tgw
TGWAttachmentEgress:
Type: AWS::EC2::TransitGatewayVpcAttachment
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref EgressVPC
SubnetIds:
- !Ref EgressTGWSubnet1
- !Ref EgressTGWSubnet2
Options:
ApplianceModeSupport: disable
Tags:
- Key: Name
Value: !Sub ${ProjectName}-tgw-att-egress
TGWAttachmentSpoke:
Type: AWS::EC2::TransitGatewayVpcAttachment
Properties:
TransitGatewayId: !Ref TransitGateway
VpcId: !Ref SpokeVPC
SubnetIds:
- !Ref SpokeTGWSubnet1
- !Ref SpokeTGWSubnet2
Options:
ApplianceModeSupport: disable
Tags:
- Key: Name
Value: !Sub ${ProjectName}-tgw-att-spoke
TGWRouteTableApp:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: !Sub ${ProjectName}-tgw-rt-app
TGWRouteTableEgress:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
TransitGatewayId: !Ref TransitGateway
Tags:
- Key: Name
Value: !Sub ${ProjectName}-tgw-rt-egress
TGWRTAssocSpoke:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref TGWAttachmentSpoke
TransitGatewayRouteTableId: !Ref TGWRouteTableApp
TGWRTAssocEgress:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref TGWAttachmentEgress
TransitGatewayRouteTableId: !Ref TGWRouteTableEgress
TGWRouteAppDefault:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableApp
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayAttachmentId: !Ref TGWAttachmentEgress
TGWRouteEgressToSpoke:
Type: AWS::EC2::TransitGatewayRoute
Properties:
TransitGatewayRouteTableId: !Ref TGWRouteTableEgress
DestinationCidrBlock: 10.1.0.0/16
TransitGatewayAttachmentId: !Ref TGWAttachmentSpoke
Outputs:
SpokeVPCId:
Value: !Ref SpokeVPC
Export:
Name: !Sub ${AWS::StackName}-SpokeVPCId
SpokePrivateSubnet1Id:
Value: !Ref SpokePrivateSubnet1
Export:
Name: !Sub ${AWS::StackName}-SpokePrivateSubnet1Id
SpokePrivateSubnet2Id:
Value: !Ref SpokePrivateSubnet2
Export:
Name: !Sub ${AWS::StackName}-SpokePrivateSubnet2Id
TransitGatewayId:
Value: !Ref TransitGateway
Export:
Name: !Sub ${AWS::StackName}-TransitGatewayId
RegionalNATGatewayId:
Value: !Ref RegionalNATGateway
Export:
Name: !Sub ${AWS::StackName}-RegionalNATGatewayId
ec2.yaml(クリックで展開)
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Regional NAT Gateway + Transit Gateway - EC2 Stack.
Deploys verification EC2 instances into Spoke VPC.
Parameters:
InfraStackName:
Type: String
Description: Name of the infrastructure stack
Resources:
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow outbound only (SSM Session Manager)
VpcId: !ImportValue
Fn::Sub: ${InfraStackName}-SpokeVPCId
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${InfraStackName}-ec2-sg
EC2Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
EC2InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref EC2Role
EC2Instance1:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}'
SubnetId: !ImportValue
Fn::Sub: ${InfraStackName}-SpokePrivateSubnet1Id
SecurityGroupIds:
- !Ref EC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfile
Tags:
- Key: Name
Value: !Sub ${InfraStackName}-ec2-az1
EC2Instance2:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Sub '{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}'
SubnetId: !ImportValue
Fn::Sub: ${InfraStackName}-SpokePrivateSubnet2Id
SecurityGroupIds:
- !Ref EC2SecurityGroup
IamInstanceProfile: !Ref EC2InstanceProfile
Tags:
- Key: Name
Value: !Sub ${InfraStackName}-ec2-az2
Outputs:
EC2Instance1Id:
Description: EC2 in AZ1
Value: !Ref EC2Instance1
EC2Instance2Id:
Description: EC2 in AZ2
Value: !Ref EC2Instance2
エッジ RT への TGW ルート定義
テンプレート内で以下のように定義しました。Custom Resource は不要です。
EdgeRouteToSpoke:
Type: AWS::EC2::Route
DependsOn: TGWAttachmentEgress
Properties:
RouteTableId: !GetAtt RegionalNATGateway.RouteTableId
DestinationCidrBlock: 10.1.0.0/16
TransitGatewayId: !Ref TransitGateway
!GetAtt RegionalNATGateway.RouteTableId でエッジルートテーブルの ID を取得できます。NAT GW と TGW は同一スタック(infra.yaml)内で定義しているため !Ref で参照可能です。CFn デプロイのみで手動 CLI 操作なしに完全な環境が構築されることを確認しました。
デプロイ手順
# インフラスタック
aws cloudformation create-stack --stack-name rnat-tgw-infra \
--template-body file://cfn/infra.yaml --region ap-northeast-1 \
--parameters ParameterKey=AZ1,ParameterValue=ap-northeast-1a \
ParameterKey=AZ2,ParameterValue=ap-northeast-1d
# EC2スタック(infraスタック完了後)
aws cloudformation create-stack --stack-name rnat-tgw-ec2 \
--template-body file://cfn/ec2.yaml --region ap-northeast-1 \
--capabilities CAPABILITY_IAM \
--parameters ParameterKey=InfraStackName,ParameterValue=rnat-tgw-infra
テンプレートの実装ポイント:
!GetAtt RegionalNATGateway.RouteTableIdでエッジ RT の ID を取得し、Custom Resource 不要で TGW ルートを定義AvailabilityZoneAddressesで AZ と EIP を明示指定し、auto mode の展開待ちを回避- AZ はパラメータで明示指定(
!GetAZsはデフォルトサブネットが欠落した AZ を返さないため)
AZ アフィニティとトラフィックフロー
SSM Session Manager で各 EC2 に接続し、curl http://checkip.amazonaws.com を複数回実行しました。EC2 は Private Subnet に配置しており、SSM 接続も NAT GW 経由のパブリックエンドポイントを利用しています。
=== EC2-1 (ap-northeast-1a) ===
xx.xx.xx.1 ← AZ-a の EIP
=== EC2-2 (ap-northeast-1d) ===
yy.yy.yy.2 ← AZ-d の EIP
| EC2 | AZ | 使用 EIP | NAT GW AZ | 一致 |
|---|---|---|---|---|
| EC2-1 | ap-northeast-1a | xx.xx.xx.1 | ap-northeast-1a | ✅ |
| EC2-2 | ap-northeast-1d | yy.yy.yy.2 | ap-northeast-1d | ✅ |
両 AZ の EC2 からインターネットアクセスが成功し、TGW 直接指定で AZ アフィニティが完全に維持されていました。
往路のトラフィックフロー
EC2-1 (AZ-a) → Spoke TGW Subnet (AZ-a) → TGW → Egress TGW Subnet (AZ-a) → NAT GW (AZ-a EIP) → IGW → Internet
復路のトラフィックフロー(ここが新機能)
Internet → IGW → NAT GW (DNAT: EIP → 10.1.1.x)
→ エッジ RT: 10.1.0.0/16 → TGW ← ここが今回サポートされたルート
→ TGW Egress RT: 10.1.0.0/16 → Spoke Attachment
→ Spoke VPC TGW Subnet (AZ-a) → EC2-1
NAT GW が DNAT 後にエッジ RT を評価する時点で、パケットは AZ-a の NAT GW インスタンスが処理しています。エッジ RT から TGW に渡す際、TGW は Egress VPC Attachment の AZ-a 側 ENI でパケットを受け取ります。Appliance Mode が無効の TGW は、入口 Attachment で受け取った AZ と同じ AZ にある出口 Attachment(Spoke VPC 側)の ENI へトラフィックを転送します。そのため、復路でも AZ を跨ぎませんでした。
コスト面のメリット
AZ アフィニティが維持されることで、クロス AZ データ転送料金($0.01/GB per direction、往復で $0.02/GB)が正常時は発生しません。ENI 指定ワークアラウンドでは全トラフィックが 1AZ に集中するため、他 AZ からのトラフィックには常にクロス AZ 料金が発生していました。
片 AZ 障害時のフォールバック確認
AZ-a の EIP を NAT GW から解除して擬似障害を発生させました。
# association-id は describe-nat-gateways の NatGatewayAddresses[].AssociationId で確認
aws ec2 disassociate-nat-gateway-address \
--nat-gateway-id <NAT-GW-ID> \
--association-ids <ASSOCIATION-ID> \
--region ap-northeast-1
結果:
| 状態 | EC2-1 (AZ-a) | EC2-2 (AZ-d) |
|---|---|---|
| 正常時 | AZ-a の EIP | AZ-d の EIP |
| AZ-a EIP 解除後 | AZ-d の EIP ← フォールバック | AZ-d の EIP(影響なし) |
片 AZ の EIP 解除後、残存 AZ の NAT GW にフォールバックして通信が継続しました。
フォールバックの仕組み(検証結果からの推測)
検証結果から、AZ-a の NAT GW リソース(EIP)が消失すると、リージョナル NAT GW が内部的に正常な AZ-d のリソースへクロス AZ でトラフィックを転送していると考えられます。リージョナル NAT GW はサブネットに紐付かない VPC レベルのリソースであるため、このような挙動が可能と推測されます。
なお、disassociate-nat-gateway-address 実行後に disassociating 状態を経てからフォールバックに切り替わる挙動も確認しており、グレースフルドレインが行われていました。
ENI 指定ワークアラウンドとの比較
| 項目 | ENI 指定(2025/11) | TGW 直接指定(2026/05) |
|---|---|---|
| 正常時 | 全トラフィックが 1AZ に集中 | 各 AZ で独立処理 |
| 片 AZ 障害時 | ❌ 全 AZ 通信不能(SPOF) | ✅ 残存 AZ にフォールバック |
| クロス AZ 料金 | ⚠️ 常時発生($0.02/GB) | ✅ 正常時は発生しない |
まとめ
リージョナル NAT Gateway のエッジルートテーブルに Transit Gateway を直接指定できるようになり、のんピさんの記事で指摘されていた SPOF リスクを回避できる構成が組めるようになりました。CFn テンプレートだけで環境構築が完結し、正常時は AZ アフィニティが維持され、EIP 解除による簡易検証ながら片 AZ 障害時のフォールバックも確認できました。
リージョナル NAT Gateway + TGW の組み合わせを安心して採用できる構成になっています。アウトバウンド通信の集約を検討中の方はぜひ試してみてください。








