Transit Gateway本番3年で学んだ、VPC統合で踏んだ地雷と実装パターン

VPCピアリングの限界を感じてTransit Gatewayに移行したら、想像以上に複雑でした。3年の試行錯誤から見えた、ルーティング・コスト・スケーリングの失敗パターンと解決法を実体験から共有します。

Transit Gateway最適化設計|本番3年で学んだVPC統合の地雷と実装パターン

先日、新しく入ったエンジニアが「うちのVPCネットワーク構成図、複雑すぎません?」と質問してきたんですよ。その一言で、Transit Gatewayを導入して3年の試行錯誤が全部思い出された。最初は「複数のVPCを繋ぐだけだからシンプルじゃん」と思ってたけど、蓋を開けたら地雷だらけ。今日はその失敗から学んだ、本当に運用できるTransit Gateway設計について書きます。

「VPCピアリングが限界」に気づくまで

3年前、うちのチームはVPCピアリングで8個のVPCを繋いでた。当時は「これで十分だ」って思ってたんですけど、半年経つと新しいプロジェクトが立ち上がるたびに、既存のすべてのVPCピアリング接続を新規VPCと繋ぎ直す必要が出てくる。8個のVPCがあれば、新しいVPCを追加するたびに新たに7個のピアリング接続を作る。これはn × (n-1) / 2の地獄ですよ。

そこでTransit Gatewayに移行したんですけど、正直ここからが本当の戦いでした。

最初の失敗:「繋いだら終わり」の罠

Transit Gatewayを立ち上げて、全VPCのAttachmentを作成。Routeテーブルも設定。本番環境と開発環境の疎通確認をしたから「よし、完成」と思いました。

ところが翌週、営業VPCから顧客管理VPCへのトラフィックが突然増えて、本番APIの応答が1000msを超え始めたんです。当時、何が起きてるのか全然分かりませんでした。

後になって気づいたのは、単純な接続性だけでなく、トラフィックフローの可視化・ルーティング優先度・帯域制御が全く設計されてなかったってこと。VPCピアリングと違って、Transit Gatewayは複数の経路を自動判定するから、意図しないルーティングが起きやすいんですよ。

ルーティング設計の失敗パターン

Transit Gatewayを本番で3年運用してて、何度も引っかかったのが「デフォルトルートの扱い」だ。

# 最初、こうしてた
resource "aws_ec2_route" "tgw_default" {
  route_table_id            = aws_route_table.app_vpc.id
  destination_cidr_block    = "0.0.0.0/0"
  transit_gateway_id        = aws_ec2_transit_gateway.main.id
}

これ、全トラフィックをTransit Gatewayに投げちゃう設定です。結果として、本来ローカルVPCで完結すべきトラフィックまでTGWを経由しちゃう。TGWはネットワークスループットに応じて課金されるので、この設定で月の通信費が3倍になってたんですよ。

正しくは、明示的な宛先CIDRだけをTGWに向けるべきなんだ。

# 改善後
resource "aws_ec2_route" "tgw_specific" {
  for_each = toset([
    "10.1.0.0/16",  # 営業VPC
    "10.2.0.0/16",  # 開発VPC
    "10.3.0.0/16",  # 顧客管理VPC
  ])

  route_table_id            = aws_route_table.app_vpc.id
  destination_cidr_block    = each.value
  transit_gateway_id        = aws_ec2_transit_gateway.main.id
}

これなら、明示的に必要な接続だけがTGWを通る。地味だけど、本当に重要な違いです。

トランジットゲートウェイアタッチメント設計

うちが本番で絶対に失敗したくなかったのが、Attachmentの管理なんだ。最初の設計は「全VPCを全サブネットでAttachする」だったんですけど、これもハマりました。

複数AZ・複数サブネット構成での地雷

# 悪い例:すべてのプライベートサブネットをAttach
resource "aws_ec2_transit_gateway_vpc_attachment" "multi_az" {
  transit_gateway_id      = aws_ec2_transit_gateway.main.id
  vpc_id                  = aws_vpc.app_vpc.id
  subnet_ids              = aws_subnet.private[*].id  # 全サブネット
}

これだと、VPC内のトラフィック分散が複雑になって、ネットワークの応答ばらつきが出る。特にAZ間の通信がコスト高いから、意図しない経路選択で課金が増える。

本番で実装したのはこれだ:

# 改善:各AZで代表サブネットのみAttach
resource "aws_ec2_transit_gateway_vpc_attachment" "per_az" {
  transit_gateway_id      = aws_ec2_transit_gateway.main.id
  vpc_id                  = aws_vpc.app_vpc.id
  subnet_ids              = [
    aws_subnet.private_az1.id,
    aws_subnet.private_az2.id,
  ]
  tags = {
    Name = "app-vpc-attachment"
  }
}

各AZに1つのサブネットだけをAttachするんですよね。こうすると、Transit Gateway側で各AZのルーティングを明確に制御できます。

マルチアカウント構成での運用

うちは3つのAWSアカウント(本番・ステージング・開発)を運用してるんですが、最初はそれぞれのアカウントに独立したTransit Gatewayを立てちゃいました。その結果、アカウント間通信のたびにVPNゲートウェイが必要になって、ネットワーク構成が複雑化。

で、Transit Gateway Route TableとTransit Gateway Peeringで統合することにしたんです。

graph TB
    subgraph prod["本番アカウント (111111111111)"]
        tgw_main["Transit Gateway (Main)"]
        app_vpc["App VPC<br/>10.0.0/16"]
        web_vpc["Web VPC<br/>10.1.0/16"]
        db_vpc["DB VPC<br/>10.2.0/16"]
        
        tgw_main --> app_vpc
        tgw_main --> web_vpc
        tgw_main --> db_vpc
    end
    
    subgraph dev["開発アカウント (222222222222)"]
        tgw_dev["Transit Gateway (Dev)"]
        dev_vpc["Dev VPC<br/>10.100.0/16"]
        
        tgw_dev --> dev_vpc
    end
    
    tgw_main <-->|TGW Peering| tgw_dev

これで、開発環境が本番DBに必要な部分だけアクセスできるようにしました。Route Tableで制御します。

帯域制御とルートテーブル設計

Transit Gatewayのもう一つの強みが、Route Tableを使ったルーティング制御だ。VPCピアリングの時は「繋ぐか繋がないか」の2択だったけど、TGWはより細かく制御できます。

本番で実装してる構成:

resource "aws_ec2_transit_gateway_route_table" "app_routes" {
  transit_gateway_id = aws_ec2_transit_gateway.main.id
  tags = {
    Name = "app-vpc-routes"
  }
}

resource "aws_ec2_transit_gateway_route_table_association" "app_vpc" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.app_vpc.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.app_routes.id
}

# AppVPCから他のVPCへのルート明示
resource "aws_ec2_transit_gateway_route" "app_to_web" {
  destination_cidr_block          = "10.1.0.0/16"  # Web VPC
  transit_gateway_attachment_id   = aws_ec2_transit_gateway_vpc_attachment.web_vpc.id
  transit_gateway_route_table_id  = aws_ec2_transit_gateway_route_table.app_routes.id
}

resource "aws_ec2_transit_gateway_route" "app_to_db" {
  destination_cidr_block          = "10.2.0.0/16"  # DB VPC
  transit_gateway_attachment_id   = aws_ec2_transit_gateway_vpc_attachment.db_vpc.id
  transit_gateway_route_table_id  = aws_ec2_transit_gateway_route_table.app_routes.id
}

こうすると、AppVPCが意図した接続先だけに通信できる。セキュリティグループ+ルートテーブルのレイヤーで、ネットワークセグメンテーションが実現できるんですよね。

コスト最適化

xychart-beta
    title Transit Gateway 月間コスト推移(実データ)
    x-axis [1月, 2月, 3月, 4月, 5月, 6月]
    line [45, 48, 52, 38, 35, 32]

Route Table整備とルーティング最適化後、TGWの月額コストが45万円から32万円に下がったんですよ。理由は、不要なトラフィックがTGWを通らなくなったから。

実際に効いた施策はこれだ:

  • 明示的なルート設定:デフォルトルート廃止で無駄な通信を削減
  • リージョン内通信の優先化:同一AZ内通信はTGWを迂回
  • VPCエンドポイント活用:S3・DynamoDBのAWS内部通信を外す
  • ローカルトラフィック分類:VPC内完結する通信をRoute Tableで除外

本番で失敗した監視・可視化

最初の1年は「繋がってるから OK」という、超危機的な運用をしてました。結果、Network Firewallでブロックされてることに数ヶ月気づかなかったんです。正直、冷や汗ものでしたね。

今は、VPCフローログ + CloudWatch Insightsで監視してます:

fields @timestamp, srcaddr, dstaddr, dstport, action, bytes
| stats sum(bytes) as total_bytes by dstaddr, action
| sort total_bytes desc

これで、各VPC間の通信パターンが可視化できる。上司から「月の通信費が多い理由は?」と聞かれても、今は即座に回答できますよ。

マルチリージョン構成への拡張

オンプレとのハイブリッドクラウド化で、最近はマルチリージョンも検討中だ。Transit Gateway Peeringをリージョン間で構築する設計をテスト中ですけど、正直まだ本番化してないので自信ない。でも構成図としてはこんな感じになると思う:

graph LR
    subgraph ap["ap-northeast-1"]
        tgw1["TGW Main"]
    end
    
    subgraph us["us-east-1"]
        tgw2["TGW Primary"]
    end
    
    tgw1 <-->|Regional Peering| tgw2

ここからが本当の複雑さだと思ってるんですよね。リージョン間ルーティングで、アジアとアメリカのVPCをどう分離するか、いま試行錯誤中です。

まとめ

Transit Gatewayは確かに便利ですけど、使い方を間違えると本当に厄介になる。3年の運用を振り返って、大事なのはこれだ:

  • デフォルトルートは絶対に使わない:明示的な宛先CIDRだけをRoute Tableで制御する
  • 各AZで代表サブネット1つだけAttach:複数Attachは複雑化の原因になる
  • Route Tableで接続性を明示的に定義:セキュリティとコストの両面で効果あり
  • VPCフローログで可視化必須:ブラックボックス化を防ぐ
  • マルチアカウント時はTGW Peeringも検討:NATゲートウェイより通信費が安い場合が多い

正直、インフラエンジニアの中でもTransit Gatewayの運用で失敗してるチーム、結構多いと思う。特にVPCピアリングから移行する時は、「シンプルから複雑へ」のマインドセット転換が必須なんですよね。もし今、Transit Gatewayの設計を考えてるなら、最初は「余分に制限するくらい」の気持ちでRoute Tableを設計することをお勧めします。我々の失敗が参考になれば幸いです。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事