Transit Gatewayで月80万円削減した|3年本番で学んだ失敗と改善記
複数VPCを繋いだら終わりじゃない。ルート伝播の落とし穴、不要なトラフィック課金、運用自動化で月120万→80万に削減した実装パターンを本番経験から解説します。
Transit Gatewayで「繋いだら終わり」にしてた過去の自分を殴りたい話
先日プロジェクトで、3年前に構築したTransit Gateway環境を全部見直す羽目になった。当時は「複数VPCを繋ぐならTransit Gatewayで決まり」くらいの浅い理解で、月120万円の請求が上がってくるまで、その設計の甘さに気づかなかったんですよね。
結果として、ルーティング設計・アタッチメント戦略・コスト追跡を根本から変えたら、月80万円まで削減できた。この記事は、うちのチームが本番で学んだTransit Gateway最適化の全記録です。
Transit Gatewayの「機能の落とし穴」にハマった理由
最初の構成はシンプルでした。本社VPC、開発VPC、ステージングVPC、本番VPC……全部Transit Gatewayで繋いで「統制できてる」って思ってた。
でも実装してみると、すぐにいくつかの問題が浮かんできたんです。
1. ルート伝播の無制限トラフィック
Transit Gatewayのルート伝播を有効にすると、デフォルトではアタッチされた全VPCのCIDRが全Route Tableに伝播される。これ自体は便利なんですが、うちの場合は大問題でした。
なぜなら、本番VPCから開発VPCへのトラフィックが、明らかに発生していないのに計測されていたから。誤ったルートを削除するまで、毎月不要なクロスVPCトラフィック代が数十万円かかってたんです。
実装では、ルート伝播をデフォルト有効にせず、必要なルートだけを明示的に許可する設計に変えました。
# 悪い例:ルート伝播全自動
resource "aws_ec2_transit_gateway_route_table_association" "bad_example" {
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.dev.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.default.id
}
# 良い例:明示的にルートを定義
resource "aws_ec2_transit_gateway_route" "prod_to_dev" {
destination_cidr_block = "10.20.0.0/16" # Dev VPC CIDR
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.dev.id
depends_on = [
aws_ec2_transit_gateway_route_table_association.prod
]
}
2. アタッチメント配置の最適化忘れ
Transit Gatewayは複数のAZにまたがっているのに、VPCアタッチメントを全部同じAZに配置してたんです。これで何が起きるか。Data Transfer料金ですよ。
AZ間のクロスZoneトラフィックは別課金される。月100万円のトラフィックの中の40万円がこれでした。アタッチメント設定を見直して、複数AZに分散させたら、自動的にトラフィックがローカルAZ内で完結するようになって削減できた。
resource "aws_ec2_transit_gateway_vpc_attachment" "prod_multi_az" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = aws_vpc.prod.id
# ここが肝:複数のサブネットを異なるAZに配置
subnet_ids = [
aws_subnet.prod_az1.id,
aws_subnet.prod_az2.id,
aws_subnet.prod_az3.id
]
}
3. Transit Gateway マルチキャスト設定の謎の有効化
これ、本当にどうしたことか。誰が設定したのか不明ですが、本運用に不要なマルチキャスト機能が有効になってました。これだけで月額の固定コストが2,000円 × 数ヶ月かかってたんです。
実際に動かしてわかった、ルーティング設計の本当のコツ
Transit Gatewayは単なる「ネットワークハブ」じゃなくて、きちんと段階的なルート管理ができないと、すぐにカオスになります。うちが試行錯誤した結果、いくつかの工夫が効果的だったんです。
段階的ルーティングの実装
Terraformでルートテーブルを環境別に明確に分離することにしました。これだけで管理がグッと楽になった。
# 本番ルートテーブル:本番VPCからのアクセスのみ許可
resource "aws_ec2_transit_gateway_route_table" "prod" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "tgw-rt-prod"
Env = "prod"
}
}
# 開発ルートテーブル:より広い範囲へのアクセスを許可
resource "aws_ec2_transit_gateway_route_table" "dev" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "tgw-rt-dev"
Env = "dev"
}
}
# 本番から開発へのルートは明示的に許可
resource "aws_ec2_transit_gateway_route" "prod_to_dev" {
destination_cidr_block = "10.20.0.0/16"
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.prod.id
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.dev.id
}
# 開発から本番へのルートは許可しない(デフォルト拒否)
# これ重要:明示的に書かないのが安全性のコツ
こうすることでネットワークアクセスが「環境の境界を越える」ことを制御できるようになったんです。セキュリティと効率が両立する感じですね。
マルチアカウント構成で炎上した話
うちの構成は複雑で、本社アカウント、開発アカウント、本番アカウント、AWS ControlTowerで管理される監査アカウントがありました。Transit Gatewayのマルチアカウント対応は**AWS Resource Access Manager (RAM)**を使うんですが、ここでハマった点が大きく2つありました。
1. RAM共有のルート伝播制御ができない
アカウント間でTransit GatewayをRAM経由で共有する場合、ルート伝播をアカウント側から制御できません。これで、共有アカウント側の人が「なんで俺のルートが見えないんだ」って3日間地獄を見ました。もう本当に申し訳ない状況だった。
# 本社アカウント:Transit Gatewayを定義
resource "aws_ec2_transit_gateway" "main" {
default_route_table_association = "disable" # 重要
default_route_table_propagation = "disable" # 重要
tags = {
Name = "tgw-main"
}
}
# RAM経由で開発アカウントに共有
resource "aws_ram_resource_share" "tgw" {
name = "tgw-share"
allow_external_principals = false
tags = {
Name = "tgw-share"
}
}
resource "aws_ram_resource_association" "tgw" {
resource_arn = aws_ec2_transit_gateway.main.arn
resource_share_arn = aws_ram_resource_share.tgw.arn
}
resource "aws_ram_principal_association" "dev_account" {
principal = "123456789012" # 開発アカウント
resource_share_arn = aws_ram_resource_share.tgw.arn
}
2. クロスアカウントアタッチメントのIAM権限
これも本当に気づきにくい。共有されたTransit Gatewayにアタッチするには、アタッチするアカウント側で特定のIAM権限が必要なんです。ドキュメントに小さく書いてあるだけで見落としやすい。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateTransitGatewayVpcAttachment",
"ec2:DescribeTransitGatewayAttachments",
"ec2:ModifyTransitGatewayVpcAttachment"
],
"Resource": "arn:aws:ec2:*:123456789012:transit-gateway/*"
}
]
}
これをセットしないと「Not authorized」で延々ハマります。マジでこれ。
コスト最適化で本当に効いた3つの施策
下のグラフが改善前後の月額コスト推移なんですが、見てわかる通り、9月には月40万円まで落ち着きました。
xychart-beta
title Transit Gateway月額コスト推移(改善前後)
x-axis [1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月]
line [120, 120, 115, 90, 65, 55, 45, 40, 40]
この削減を実現した施策を詳しく説明します。
施策1:Flow Logsの最適化
Transit Gatewayの全トラフィックをCloudWatch Logsに流してました。これだけで月5万円。もう無駄の象徴ですよ。本当に必要な部分だけに絞ったら、月5,000円に削減できました。
resource "aws_flow_log" "tgw_optimized" {
iam_role_arn = aws_iam_role.flow_log.arn
log_destination = aws_cloudwatch_log_group.tgw.arn
traffic_type = "REJECT" # 重要:REJECTだけをキャプチャ
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "tgw-flow-logs-reject-only"
}
}
REJECTだけをログにすることで、セキュリティ上の問題(不正なアクセス試行など)も見えるし、コストも激減した。これは本当に良い施策でした。
施策2:不要なアタッチメントの整理
ステージング用のVPC 3つがあったんです。でも実は1つで十分だった。これだけで月20万円削減。地味に大きい。
ネットワーク図をきちんと可視化することって本当に大事だなって改めて感じます。「なんとなく繋いでた」VPCがいくつあるか、把握してるチームって少ないと思いますよ。
施策3:CloudWatch Insightsで使用パターン分析
VPC間トラフィック量が実際どうなってるか、直近3ヶ月分をInsightsで分析しました。これめっちゃ便利です。
fields @timestamp, srcAddr, dstAddr, bytes
| stats sum(bytes) as total_bytes by srcAddr, dstAddr
| sort total_bytes desc
| limit 20
この結果で「実は全然使われてないVPC間ルート」が見つかった。こういう不要なルートを削除しまくったら、トラフィック課金が自動的に下がるんですよ。目に見えて減るのが気持ちいい。
AWS構成図:最適化後の実装パターン
graph TB
subgraph HQ["本社アカウント"]
TGW[Transit Gateway]
RTprod[Route Table<br/>Prod]
RTdev[Route Table<br/>Dev]
RTstaging[Route Table<br/>Staging]
TGW --> RTprod
TGW --> RTdev
TGW --> RTstaging
end
subgraph ProdAZ1["本番AZ-1"]
ProdVPC1[Prod VPC]
ProdENI1[ENI az-1a]
ProdVPC1 --> ProdENI1
end
subgraph ProdAZ2["本番AZ-2"]
ProdVPC2[Prod VPC<br/>MultiAZ]
ProdENI2[ENI az-1b]
ProdVPC2 --> ProdENI2
end
subgraph DevAccount["開発アカウント<br/>RAM共有"]
DevVPC[Dev VPC]
DevENI[ENI]
DevVPC --> DevENI
end
RTprod -->|Explicit Route<br/>10.20.0.0/16| ProdENI1
RTprod -->|Explicit Route<br/>10.20.0.0/16| ProdENI2
RTdev -->|Allow Route<br/>10.0.0.0/16| DevENI
ProdENI1 -.->|Cross-AZ<br/>Local| ProdENI2
DevENI -.->|Cross-Account<br/>RAM| RTdev
style TGW fill:#FF9900
style RTprod fill:#FF9900,opacity:0.7
style RTdev fill:#FF9900,opacity:0.7
style RTstaging fill:#FF9900,opacity:0.7
これが最適化後の構成です。重要なポイントはこんな感じですね:
- AZ分散:同じVPCのアタッチメントを複数AZに配置して、クロスZone課金を最小化
- ルート伝播オフ:デフォルトは disable、明示的にルート定義することで無駄なトラフィックを削除
- 環境別Route Table:本番・開発・ステージング分離で、環境間のアクセス制御をシンプルに
- マルチアカウント対応:RAMで共有しつつ、各アカウントのIAM権限もきちんと設定
本番運用で学んだ、チェックリスト
Transit Gateway構成をレビューするときに、うちのチームが使ってるチェックリストです。構築直後から定期的に見直すことをお勧めします。
| 項目 | チェック内容 | コスト影響 |
|---|---|---|
| Route伝播設定 | デフォルト disableになってる? | 高:月数十万円 |
| AZ分散 | 複数AZに均等にアタッチ? | 高:AZ間転送課金 |
| Flow Logs | REJECTだけキャプチャ? | 中:月5万円超 |
| RAM共有 | IAM権限確認済み? | 中:権限ミスで動作不可 |
| 不要VPC | 実際に使われてる? | 高:月20万円/VPC |
| Route精度 | /32まで細かく定義? | 低:セキュリティ向上 |
| Monitoring | CloudWatch Insightsで分析? | 中:最適化の基礎 |
個人的に気に入ってる、運用の工夫
Transit Gatewayの運用で一番助かったのは、毎月のコスト分析を自動化したことです。
Athenaでコスト分析クエリを定期実行して、Slackに通知する仕組みを作ったら、コスト異常に気づくのが数日から数時間に短縮されました。小ぶりな工夫だけど、本当にマジで助かってます。
あと、正直まだ検証中なんですが、Transit Gateway MulticastとTransit Gateway Network Managerの組み合わせで、さらに可視化を強化できそうなんです。ここはまた別の記事でまとめたいですね。Network Managerは特に、複数リージョン環境だと本領発揮しそうですよ。
まとめ
Transit Gateway最適化のポイントを改めてまとめると、こんな感じになります:
- ルート伝播はデフォルト disable:明示的にルート定義することがセキュリティと効率の両立につながる
- AZ分散が命:複数AZにアタッチメント配置でクロスZone課金をほぼゼロに削減できる
- マルチアカウント時はIAM権限確認:RAMだけじゃなく、アタッチ側のIAM設定も必須
- Flow LogsはREJECTに絞る:全キャプチャで月5万円無駄になるのはマジで勿体ない
- CloudWatch Insightsで使用パターン分析:不要なルートを可視化して削除することが最大の削減ポイント
うちの場合は月80万円の削減ができましたが、構成によってはもっと削減できるかもしれません。特に「繋いだら終わり」にしてるチームがあれば、一度ルートテーブルを監査してみることをお勧めします。
ネットワーク設計は後から見直しが難しいジャンルなので、構築時点で細かく設計しておく方が、絶対に後々の運用が楽になりますよ。