aws

Transit Gatewayクロスアカウント構築手順|TerraformとRAM共有

Transit Gatewayクロスアカウント構築手順|TerraformとRAM共有

AWS Transit Gateway(TGW)をコンソールで作る手順は多くの記事が書いています。一方、複数アカウントにまたがる環境をコードで管理しようとすると別の壁に当たります。アタッチメントを作るアカウントと承諾するアカウントが違う。ルートテーブルはオーナーしか触れない。既定のままでは全VPCが相互に通信できてしまう。この記事ではTerraformとAWS CLIのコードを示しながら、単一アカウントのTGW作成からRAM共有・アタッチメント承諾・ルートテーブル分割・疎通しないときの切り分けまでを組み立てます。引数の既定値と数値はAWS公式ドキュメントとTerraform AWS Providerで確認した2026年9月時点の値です。仕組みとVPC Peeringとの料金比較はAWS Transit Gatewayとは|仕組み・VPC Peeringとの違い・料金を解説にまとめてあります。

まとめ:クロスアカウントTGWの構築でつまずく2点と対処の結論

クロスアカウントのTGW構築が止まる原因は、ほぼ2か所に集約されます。1つは、スポーク側で作った aws_ec2_transit_gateway_vpc_attachment が pendingAcceptance のまま止まること。オーナー側に aws_ec2_transit_gateway_vpc_attachment_accepter を置き、プロバイダを別エイリアスに分けて適用すれば解けます。もう1つは、ルートテーブルの関連付けと伝播をスポーク側で設定して権限エラーになること。これはオーナーアカウントの責務なので、accepter 側で指定します。

設計の結論も先に出します。TGWは既定で「全アタッチメントが既定ルートテーブルに関連付けられ、全経路が伝播する」状態で立ち上がります。本番と検証を分けたいなら、ルートテーブルを2枚以上作り、既定への関連付けを明示的に false にしてから割り当ててください。後回しにすると、VPCを足すたびに意図しない到達性が増えます。VPCが2本で単一アカウント、かつ低遅延が要件なら、TGWを入れずVPC Peeringで足ります。

Transit Gatewayの構成要素とルート伝播の既定動作を押さえる

コードを書く前に、TGWが持つオブジェクトの粒度を押さえます。ここが曖昧だと、どのリソースがどのアカウントに属するのかで迷います。

アタッチメント5種とTGWあたり5,000本というクォータの上限

TGWにぶら下げられるのは、VPC・VPN接続・VPN Concentrator・Direct Connect ゲートウェイ・Transit Gateway Connect・TGWピアリングです。1つのTGWに作れるアタッチメントの既定上限は5,000本、ルートテーブルは20枚、全ルートテーブル合計のルート数は10,000本、ピアリングアタッチメントは50本。いずれもAWS Transit Gateway のクォータに載っている既定値です。帯域はVPCアタッチメントあたりアベイラビリティーゾーン単位で各方向100Gbpsまで。かつて50Gbpsとして語られていた値は現行ドキュメントで書き換わっているので、古い見積り資料は差し替えてください。MTUはVPC間・Direct Connect・ピアリング間が8500バイト、VPN経由が1500バイトです。

associationとpropagationの既定値がもたらす全通信状態

TGWを作ると既定ルートテーブルが1枚付いてきます。これは「既定の関連付け先」と「既定の伝播先」を兼ねるものです。VPCをアタッチすると、そのアタッチメントは既定ルートテーブルに関連付けられ、VPCのCIDRブロックが同じルートテーブルへ伝播します。3つのVPCを足した時点で3本の経路が相互に入り、どのVPCからどのVPCへもパケットが通ります。

関連付けと伝播の両方を無効にした場合だけ、AWSは既定ルートテーブルを作りません。どちらか一方でも有効なら作られる、という挙動がAWS Transit Gateway のトランジットゲートウェイに明記されています。分離が要件なら、既定への関連付けをオフにする設計を最初の日に決めておくほうが安全です。

Terraformで単一アカウントのTGWとVPCアタッチメントを作成する

まず1アカウント内で動かします。クロスアカウントはこの構成にRAM共有とaccepterを足したものです。

aws_ec2_transit_gatewayの既定値を明示して意図を固定する

aws_ec2_transit_gateway の引数は既定値が入っているため、書かなくても作れます。ただ到達性に関わるものは明示しておきます。aws_ec2_transit_gateway のドキュメントによると、default_route_table_associationdefault_route_table_propagation はどちらも既定が enable、auto_accept_shared_attachmentsencryption_support は disable、amazon_side_asn は 64512 です。ASNは16ビットなら64512〜65534、32ビットなら4200000000〜4294967294の範囲で、オンプレ側のBGPと衝突しない番号を選びます。

resource "aws_ec2_transit_gateway" "hub" {
  description                     = "shared-network-hub"
  amazon_side_asn                 = 64512
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"
  dns_support                     = "enable"
  vpn_ecmp_support                = "enable"
  auto_accept_shared_attachments  = "disable"

  tags = {
    Name = "tgw-shared-hub"
  }
}

resource "aws_ec2_transit_gateway_vpc_attachment" "app" {
  transit_gateway_id = aws_ec2_transit_gateway.hub.id
  vpc_id             = aws_vpc.app.id
  subnet_ids = [
    aws_subnet.app_tgw_1a.id,
    aws_subnet.app_tgw_1c.id,
  ]

  appliance_mode_support = "disable"
  dns_support            = "enable"
  ipv6_support           = "disable"

  transit_gateway_default_route_table_association = false
  transit_gateway_default_route_table_propagation = false

  tags = {
    Name = "tgw-attach-app"
  }
}

subnet_idsのAZ単位の挙動とVPC側ルートテーブルへの追記

subnet_ids には、アベイラビリティーゾーンごとにちょうど1つのサブネットを指定します。TGWはそのサブネットにネットワークインターフェースを1つ置き、IPアドレスを1つ消費します。有効化の単位はサブネットではなくゾーンです。ap-northeast-1a のサブネットを1本指定すれば、1a にある他のサブネットからもTGWへ到達できます。逆に 1c を指定していなければ、1c のリソースはTGWへ出られません。TGW専用に小さなサブネットを各ゾーンへ1本ずつ切る構成が扱いやすいです。

忘れやすいのがVPC側です。TGWのルートテーブルに経路が載っていても、サブネットのルートテーブルにTGW宛の経路が無ければパケットは届きません。2種類のルートテーブルを触る、と作業手順に書いてください。

resource "aws_route" "app_private_to_tgw" {
  route_table_id         = aws_route_table.app_private.id
  destination_cidr_block = "10.0.0.0/8"
  transit_gateway_id     = aws_ec2_transit_gateway.hub.id

  depends_on = [aws_ec2_transit_gateway_vpc_attachment.app]
}

depends_on は、アタッチメントが available になる前に経路を作って失敗するのを防ぐ目的で追加する指定です。宛先を 0.0.0.0/0 にしてインターネット向け通信ごと寄せる構成も取れますが、集約したNATの費用が乗ります。費用構造はNAT ゲートウェイと NAT インスタンスの違い・料金で整理しています。

RAM共有とaccepterで別アカウントのVPCをTGWへ接続する

TGWはネットワーク用アカウントに1つ置き、各アプリケーションアカウントのVPCをぶら下げる形が実務では一般的です。

aws_ram_resource_shareとプリンシパル関連付けの3リソース

共有はAWS Resource Access Manager(RAM)で行います。必要なリソースは、共有そのものを表す aws_ram_resource_share、載せるリソースを指す aws_ram_resource_association、共有先を指す aws_ram_principal_association の3つです。AWS RAM の共有可能なリソースによると、ec2:TransitGateway は組織外の任意のAWSアカウントとも共有でき、AWS Organizations は前提条件ではありません。IAMユーザーやロール単位の共有には非対応で、共有先はアカウント単位です。共有を作る側には ram:CreateResourceShare に加えて ec2:DescribeTransitGateway が必要です。

resource "aws_ram_resource_share" "tgw" {
  name                      = "tgw-shared-hub"
  allow_external_principals = false
}

resource "aws_ram_resource_association" "tgw" {
  resource_arn       = aws_ec2_transit_gateway.hub.arn
  resource_share_arn = aws_ram_resource_share.tgw.arn
}

resource "aws_ram_principal_association" "spoke" {
  principal          = var.spoke_account_id
  resource_share_arn = aws_ram_resource_share.tgw.arn
}

同一組織内なら allow_external_principals = false のままで、principal に組織のARNを渡せば招待の承諾も不要です。組織外へ渡すときだけ true にします。

attachmentとaccepterをプロバイダ別に分けて適用する順序

共有が済むと、スポーク側で自分のVPCをアタッチできます。このときアタッチメントは pendingAcceptance で止まり、オーナーが承諾するまで通信は始まりません。Terraformでは aws_ec2_transit_gateway_vpc_attachment をスポーク用プロバイダで、aws_ec2_transit_gateway_vpc_attachment_accepter をオーナー用プロバイダで宣言します。aws_ec2_transit_gateway_vpc_attachment のドキュメントにも、TGWを共有して第2のアカウントのVPCをアタッチする場合は両リソースを使うよう明記されています。

provider "aws" {
  alias  = "spoke"
  region = "ap-northeast-1"
  assume_role {
    role_arn = "arn:aws:iam::${var.spoke_account_id}:role/TerraformExecution"
  }
}

# スポークアカウント側:アタッチメントを作る
resource "aws_ec2_transit_gateway_vpc_attachment" "spoke" {
  provider           = aws.spoke
  transit_gateway_id = aws_ec2_transit_gateway.hub.id
  vpc_id             = aws_vpc.spoke.id
  subnet_ids         = [aws_subnet.spoke_tgw_1a.id]

  tags = {
    Name = "tgw-attach-spoke-prod"
  }

  depends_on = [aws_ram_principal_association.spoke]
}

# オーナーアカウント側:承諾と同時に割り当てを決める
resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "spoke" {
  transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.spoke.id

  transit_gateway_default_route_table_association = false
  transit_gateway_default_route_table_propagation = false

  tags = {
    Name = "tgw-attach-spoke-prod"
  }
}

スポーク側の depends_on が無いと、共有が届く前にアタッチメントを作ろうとして TransitGatewayID not found で落ちます。動く構成一式はTerraform AWS Providerのtransit-gateway-cross-account-vpc-attachment のexampleにあるので、プロバイダの分け方はそれに合わせると手戻りが少ないです。実行基盤を検討中ならHCP Terraformとは?旧Terraform Cloud(名称変更)の機能・料金も参照してください。

参加者アカウントがTGWルートテーブルを操作できない制約と回避策

共有されたTGWで参加者アカウントができるのは、自分のVPCをアタッチすることと、自分が作ったアタッチメントを削除することだけです。ルートテーブルを作る、関連付けを変える、伝播を設定する操作はオーナー側にしかありません。スポーク側のコードに aws_ec2_transit_gateway_route_table_association を書くと権限エラーになります。

先のコードで関連付けと伝播を accepter 側に置いたのはこのためです。両引数は attachment にも accepter にもありますが、クロスアカウントではオーナー側で決めます。どちらも既定値が true なので、分離設計なら明示的に false を書いてください。書き忘れると、承諾した瞬間にスポークVPCが既定ルートテーブルへ入ります。

TGWルートテーブルの分割でVPC間通信を遮断する設計と実装

ルートテーブルを複数持てることが、TGWをPeeringのメッシュと分ける実質的な価値です。1つのTGWの中に、従来機器のVRFに近い独立した経路空間を作れます。

本番と検証を分けるルートテーブル2枚の関連付けと伝播の書き方

本番VPC群と検証VPC群を遮断し、共有サービスVPCだけ双方から使える構成を考えます。関連付けは1アタッチメントに1枚だけ、伝播は複数枚に向けられる、という非対称なルールがここで効きます。

resource "aws_ec2_transit_gateway_route_table" "prod" {
  transit_gateway_id = aws_ec2_transit_gateway.hub.id
  tags               = { Name = "tgw-rtb-prod" }
}

resource "aws_ec2_transit_gateway_route_table" "stg" {
  transit_gateway_id = aws_ec2_transit_gateway.hub.id
  tags               = { Name = "tgw-rtb-stg" }
}

# 本番VPCは本番ルートテーブルに関連付ける
resource "aws_ec2_transit_gateway_route_table_association" "prod_app" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.app.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
}

# 共有サービスVPCの経路は本番・検証の両方へ伝播させる
resource "aws_ec2_transit_gateway_route_table_propagation" "shared_to_prod" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.shared.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
}

resource "aws_ec2_transit_gateway_route_table_propagation" "shared_to_stg" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.shared.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.stg.id
}

共有サービスVPCは関連付け先として専用のルートテーブルを1枚持ち、そこへ本番・検証各VPCの経路を伝播させます。共有サービス側からは両方へ出られ、本番と検証の間は経路が無いので届きません。

blackholeルートで特定CIDR宛の通信だけを落とす実装

伝播で自動的に載る経路のうち、特定のCIDRだけ止めたい場面があります。TGWのルートテーブルにはblackholeルートを置けるので、宛先を指定して破棄できます。

resource "aws_ec2_transit_gateway_route" "drop_legacy" {
  destination_cidr_block         = "10.200.0.0/16"
  blackhole                      = true
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
}

TGWのルート評価は、宛先が最も具体的な経路が勝ち、同一CIDRなら静的ルートが伝播ルートより優先されます。この順序はAWS Transit Gateway の仕組みに一覧があり、静的ルートを消すと隠れていた伝播ルートが再び現れます。ただしblackholeは経路を消すだけで、セキュリティグループやネットワークACLのような拒否の記録は残りません。恒久的な分離はルートテーブルを分ける、一時的な遮断はblackhole、と使い分けます。接続方式そのものの比較はAWSのネットワーク接続方式まとめ|VPC内・VPC間・オンプレ・インターネットの選び分けが出発点になります。

疎通できないときの切り分け:AZとサブネットとappliance mode

TGWのトラブルは、経路表が正しいのにパケットが落ちる形で出ます。原因はゾーンの非対称かステートフル機器に寄っています。

該当AZにサブネットが無いアタッチメントで起きる片側不通の判別

TGWは、宛先アタッチメントが存在しないゾーンから来た通信を、アタッチメントがあるゾーンのどれかへ内部的に転送します。このゾーン越えの転送にTGWの追加料金はかかりません。問題は、アタッチメント作成時に指定したゾーンが送信側と受信側で食い違う場合です。1a と 1c の両方にサブネットを持つVPCで、アタッチメントに 1a だけを指定していると、1c のインスタンスはTGWへ出られません。症状は「VPC Bの一部のインスタンスだけ届かない」形で現れます。届かないインスタンスのゾーンが、アタッチメントのサブネット一覧に含まれているかを見るのが最短です。

ステートフル機器を挟むときのappliance_mode_support有効化

検査用VPCにファイアウォールやIDSのようなステートフル機器を置く構成では、appliance mode を有効にしないと戻りの通信が落ちます。無効時、TGWは送信元のゾーンを維持して転送しようとします。VPC Aの1aから出た要求は検査VPCの1aの機器へ届き、VPC Bの1cから返った応答は検査VPCの1cの機器へ届く。1cの機器は元の要求を知らないので、パケットを捨てます。

有効にすると、TGWはフローハッシュで選んだ1つのネットワークインターフェースをそのフローが続く間ずっと使い、戻りも同じインターフェースへ流します。設定するのは機器が置かれているVPCのアタッチメント側です。フローの固定を保証するには検査VPCに接続するTGWをちょうど1つにする必要があり、複数つなぐとフロー状態が共有されず固定が崩れます。既存アタッチメントで有効化すると、それまでゾーン内に留まっていた通信が全ゾーンへ広がるため、変更は業務時間外に寄せてください。

# 新規作成時に有効化する
aws ec2 create-transit-gateway-vpc-attachment \
  --transit-gateway-id tgw-0123456789abcdef0 \
  --vpc-id vpc-0fedcba9876543210 \
  --subnet-ids subnet-0aaa111 subnet-0bbb222 \
  --options ApplianceModeSupport=enable

# 既存アタッチメントを変更する
aws ec2 modify-transit-gateway-vpc-attachment \
  --transit-gateway-attachment-id tgw-attach-0123456789abcdef0 \
  --options ApplianceModeSupport=enable

search-transit-gateway-routesで実際の経路を確認する

TGWのルートテーブルは、優先された経路だけを表示します。Direct Connect と Site-to-Site VPN で同じCIDRを広報していると、コンソールにはDirect Connect側だけが出て、VPN側は待機経路として隠れます。「バックアップ経路が入っているはずなのに見えない」という相談の多くはこれです。実際に入っている経路はCLIで検索すると、伝播か静的か、blackholeかまで一覧で確認できます。

aws ec2 search-transit-gateway-routes \
  --transit-gateway-route-table-id tgw-rtb-0123456789abcdef0 \
  --filters "Name=state,Values=active,blackhole" \
  --query 'Routes[].{cidr:DestinationCidrBlock,type:Type,state:State}' \
  --output table

Encryption Supportと専用アタッチメントの採用判断と見送り条件

2026年時点のTGWには以前は無かった選択肢が2つあります。どちらも採用条件が限定されます。

Encryption Support有効化に最大14日かかる前提での移行計画

Encryption Support は、TGW配下のVPC間通信に転送中の暗号化を強制する機能です。有効化は modify-transit-gateway--options EncryptionSupport=enable で行い、キー名の一覧はmodify-transit-gateway のリファレンスにあります。

判断を分ける条件は3つです。第一に、TGWの作成時には指定できず、既存のTGWにしか追加できません。第二に、enabling の状態が最大14日続きます。第三に、Connect・ピアリング・Network Firewall・VPN Concentrator・Client VPN のアタッチメント、セキュリティグループ参照、マルチキャストのいずれかを使うTGWでは有効化できず、use1-az3 のサブネットを使うVPCアタッチメントも対象外です。制約はAWS Transit Gateway の Encryption Supportに載っています。準拠要件で転送中の暗号化を求められる案件なら14日を工程表に載せて着手する。そうでなければピアリングやマルチキャストを捨てる判断が先に来るので、見送りが妥当です。

TGWを採用しない条件:VPC2本・単一アカウント・低遅延要求

VPCが2本、アカウントも1つ、オンプレ接続も無いなら、TGWは過剰です。アタッチメントの時間課金とデータ処理課金が両方かかる一方、VPC Peeringは接続自体に時間課金がありません。ミリ秒未満の遅延を削る要件があるなら、1ホップ挟む構成は不利です。料金の内訳とPeeringとの比較はAWS Transit Gatewayとは|仕組み・VPC Peeringとの違い・料金を解説で数値を出しています。

逆に、VPCが4本を超える見通しがある、アカウントを分けて運用する方針が決まっている、オンプレ接続やVPNを束ねる、この3つのうち2つ以上に当てはまるならTGWへ寄せてください。Peeringのメッシュは接続数がVPC数の二乗に比例し、VPCを1本足すたびに既存の全VPCのルートテーブルを触る作業が出ます。なお Network Firewall はTGWへ直接アタッチでき、検査用VPCを自前で作る必要がない構成です。ただしTGW側は静的ルーティングのみでサードパーティ製ファイアウォールは非対応なので、機器の選定が済んだ案件では従来の検査VPC構成を続ける判断になります。マルチアカウントのネットワーク設計とコード化の相談はインフラ構築(AWS・Google Cloud・Azure)で受けています。

よくある質問

TGWのクロスアカウント構築で、実際に問い合わせが多い5点をまとめました。

Transit Gatewayは同一アカウント内だけでも使えますか?

使えます。RAM共有は必須ではなく、1つのアカウント内で複数VPCを束ねる用途でも同じように動きます。その場合は aws_ec2_transit_gateway_vpc_attachment だけで完結し、accepter リソースは不要です。後からアカウントを分ける方針になっても、TGW側は共有を足すだけで、既存のアタッチメントを作り直す必要はありません。

クロスアカウントのTGWアタッチメントは自動承諾にできますか?

できます。auto_accept_shared_attachmentsenable にすると、共有先アカウントが作ったアタッチメントは承諾を待たずに available になります。既定値は disable です。ただし自動承諾は、共有したアカウントが任意のタイミングで自分のVPCをぶら下げられる状態を意味します。既定への関連付けと伝播も有効のままだと意図しない到達性が生まれるので、関連付けを無効にする設計と組み合わせてください。

1つのTGWでルートテーブルは何枚まで作成でき、上限は変更できますか?

既定で20枚で、Service Quotasから引き上げを申請できる調整可能なクォータです。一方、全ルートテーブル合計のルート数10,000本は、ソリューションアーキテクトかテクニカルアカウントマネージャーへの相談が必要な区分になります。ポリシーテーブルは20枚で調整不可、顧客管理のエントリは200件で調整可能です。分離の単位をVPCごとに切ると枚数が増えるので、本番・検証・共有サービス・オンプレ接続のように役割で束ねる設計が枠に収まります。

Encryption Supportは作成済みTGWにも後から付けられますか?

作成済みのTGWにしか付けられません。公式ドキュメントに、既存のTGWにのみ暗号化サポートを追加でき、作成時には指定できないと明記されています。有効化してもTGWとアタッチメントにダウンタイムは発生せず、パケットが落ちることもないとされています。ただし enabling の状態が最大14日続くため、期日のある移行では工程に織り込んでください。ピアリングやマルチキャストを併用するTGWでは有効化できません。

appliance modeは全アタッチメントに設定が必要ですか?

ステートフル機器が置かれているVPCのアタッチメントだけで足ります。通信の送信元や宛先となるVPCには設定しません。設定の単位はTGW全体ではなくVPCアタッチメントごとなので、検査用VPCのアタッチメントに ApplianceModeSupport=enable を付けるのが正しい置き方です。なお Network Firewall をTGWへ直接アタッチする構成では、作成時に appliance mode が自動で有効になります。

関連記事

資料請求

RELATED POSTS 関連記事