VGWからTransit Gatewayへ — VPNのECMP冗長化

Site-to-Site VPNの終端をVGWからTransit Gatewayに置き換え、2本のトンネルを同時に使うECMP構成を検証します。VGWのアクティブ/スタンバイとの挙動差、非対称ルーティングの注意点まで。

連載: VyOSで作るAWSハイブリッド接続(全4回)

  1. VyOSでつくるAWS Site-to-Site VPN — IPsec+BGPの基礎
  2. AWS Site-to-Site VPNの冗長化とBGP経路制御
  3. VGWからTransit Gatewayへ — VPNのECMP冗長化(この記事)
  4. マルチリージョン接続 — DX-GWの限界とTransit Gatewayピアリング

0. この回のテーマ

第2回では、VPNの終端に VGW(仮想プライベートゲートウェイ) を使い、2本のトンネルをBGPでアクティブ/スタンバイに制御しました。

今回はこの終端を Transit Gateway(TGW) に置き換えます。狙いは2つです。

  1. 2本のトンネルを同時に使う(ECMP / ロードバランス) 構成にする
  2. 将来の拡張(複数VPC・マルチリージョン)の土台を作る

VGWとTGWは役割の格が違う

ネットワーク機器の感覚で対比すると分かりやすいです。

VGWTransit Gateway
立ち位置1つのVPC専用のVPN終端装置リージョン規模のハブルータ
束ねられるものそのVPCとのVPN/DXだけ複数VPC・複数VPN・DX・別リージョンのTGWまで
例えるなら拠点ルータのVPNモジュールデータセンターのコアスイッチ/ルータ

VGWが「VPCに後付けするVPN口」なら、TGWは「VPCもVPNもDXも全部ぶら下げる中央ルータ」です。今はVPN1本をつなぐだけですが、第4回でこのハブ性がマルチリージョン接続の鍵になります。

そしてもう一つ、TGWはECMP(Equal-Cost Multi-Path)に対応しています。これが今回の挙動差の本体です。

1. VyOS側: ECMPを成立させる2つの操作

ECMPは「両トンネルを等コストにして同時に使う」状態です。VyOS側では ①偏りを外す②並列本数を許可する の2操作が要ります。片方だけでは効きません(特に②は忘れやすく、これが本記事一番の落とし穴です)。

① 経路制御を「外す」

第2回では、2本を意図的に偏らせるためにWeightとAS-Path Prependを入れました。実はこれ、ECMPにとっては邪魔になります。

BGPが複数経路を「等コスト」とみなして並べるには、以下の属性がすべて一致している必要があります(FRR/VyOSの判定基準。VyOS公式ドキュメントの maximum-paths の項に明記されています)。

Weight / Local Preference / AS-Path(AS番号もパス長も)/ Origin / MED / IGPメトリック

第2回のWeight(片側に300)とAS-Path Prepend(片側のパスを長くする)は、まさにこの「Weightを変える」「AS-Path長を変える」操作そのものでした。つまり冗長化のために入れた偏りが、そっくりそのままECMPの不成立条件になっているわけです。だから今回は削除します。

// VyOS  AWS の偏り(Weight)を削除
delete protocols bgp neighbor 169.254.234.49 address-family ipv4-unicast weight '300'

// AWS  VyOS の偏り(AS-Path Prepend)を削除
delete protocols bgp neighbor 169.254.61.113 address-family ipv4-unicast route-map export 'VPC-Tunnel-2-OUT'

② 並列本数を許可する — maximum-paths

ここが見落としやすいポイントです。BGPはデフォルトで、等コストの経路が何本あっても「ベスト1本」しか転送に使いません。 偏りを外して2本が等コストになっても、それだけでは片方は予備のまま。AWSがいくら 100 / 100 の等コストで広告してきても、VyOS側が勝手に1本へ絞ってしまいます。

「等コスト経路を最大何本まで同時に使うか」を明示するのが maximum-paths です。VyOS(AS65000)とAWS(AS64512)はASNが別=eBGPなので、ebgp 側を指定します。

set protocols bgp address-family ipv4-unicast maximum-paths ebgp 2
  • maximum-paths ebgp 2: eBGPの等コスト経路を最大2本まで転送テーブル(FIB)へ載せる。これで初めて2本のトンネルに同時にトラフィックが流れます。
  • iBGP同士の構成なら ibgp を使います(今回はeBGPなので不要)。

覚えておくと得をする切り分け: 「BGPテーブルには2本見えているのに、片方しか使われない」はECMPで最も多いハマり方です。原因はほぼこの maximum-paths 漏れ。経路が「見えている(RIB)」と「実際に使われている(FIB)」は別物、と分けて考えると一発で当たります。AWSのルーティングというよりVyOS/FRR側の都合なので、AWS側をいくら見直しても直りません。

結果を読む — show ip bgp のマーカー

①②を入れた後のBGPテーブルです。2本が等コストになり、*=(multipath)のマークが付きます。

vyos@vyos:~$ show ip bgp
   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.2.0/24      0.0.0.0                  0         32768 i
*> 10.99.0.0/16     169.254.234.49         100             0 64512 i
*=                  169.254.61.113         100             0 64512 i   ## = はmultipath

各行頭のマーカーはこう読みます。

マーカー意味
*有効な経路(valid)
>ベストパス(best。これが単独なら、転送に使われるのはこの1本だけ)
=multipath(ECMP対象)。> と一緒に転送に使われる

*>*= の両方が転送に使われている、これがECMPの成立状態です。逆に = がどこにも付かなければ、①の偏りが残っているか、②の maximum-paths が入っていないかのどちらかです。

2. AWS側: VGWを外しTGWを作る(Terraform)

ここから初めてTerraformで組みます。やることは「TGWを作る」「ネットワーク用サブネットにアタッチする」「VPN接続をTGWに紐づける」の3点です。

// Transit Gateway本体
resource "aws_ec2_transit_gateway" "tgw" {
  auto_accept_shared_attachments = "enable"
  tags = { Name = "satoshi-tgw" }
}

// TGWをネットワーク用サブネットにアタッチ
resource "aws_ec2_transit_gateway_vpc_attachment" "tgw" {
  subnet_ids         = [aws_subnet.network.id]
  transit_gateway_id = aws_ec2_transit_gateway.tgw.id
  vpc_id             = var.vpc.id
  tags = { Name = "tgw-attach-shared-vpc" }
}

// Publicサブネットからオンプレ(10.0.2.0/24)宛をTGWへ向けるルート
resource "aws_route" "public_tgw" {
  route_table_id         = aws_route_table.pub.id
  destination_cidr_block = "10.0.2.0/24"
  transit_gateway_id     = aws_ec2_transit_gateway.tgw.id
}

// Site-to-Site VPNの接続先を VGW ではなく TGW にする
resource "aws_vpn_connection" "vpn" {
  transit_gateway_id  = aws_ec2_transit_gateway.tgw.id   //  ここがVGWからの変更点
  customer_gateway_id = aws_customer_gateway.cgw.id
  type                = "ipsec.1"
  static_routes_only  = false   // 動的(BGP)

  tunnel1_log_options {
    cloudwatch_log_options {
      log_enabled       = true
      log_group_arn     = aws_cloudwatch_log_group.vpn.arn
      log_output_format = "json"
    }
  }
  tags = { Name = "${var.name}-vpn-01" }
}

このTerraformで補足しておきたい2点:

  • tunnel1_log_options は、トンネルのIKE/IPsecネゴシエーションのログをCloudWatch Logsに吐く設定です。トンネルが上がらないとき、GUIで延々pingするより、ここのログでネゴシエーションのどの段階で失敗したかを見るほうが圧倒的に速い。検証段階でも入れておくことを勧めます。
  • TGWには vpn_ecmp_support(VPNのECMPを有効にするか)という属性があり、デフォルトで有効です。だから明示しなくても2本がECMP候補になります。裏を返せば、ここを無効にすればTGWでもアクティブ/スタンバイ寄りの挙動にできる、ということでもあります。

TGWアタッチで踏みやすい3つの注意点

  • AZの一致: あるサブネットからTGW経由で通信したいなら、そのサブネットと同じAZにTGWのアタッチメント(ENI)が存在している必要があります。AZをまたぐと無言で届きません。
  • 専用サブネット推奨: TGWアタッチ用に小さな専用サブネットを切るのがベストプラクティス。EC2を置くサブネットと混ぜない。
  • ルートテーブルへの明示: VPCのルートテーブルに、オンプレ宛(10.0.2.0/24)をTGW向きで登録する必要があります。VGWの「ルート伝搬」のようには自動で入りません。

3. 検証: VGWとの挙動差(MEDとECMP)

ここが第2回との一番面白い対比です。VPNテーブルを見ると、両トンネルともMEDが100で通知されています。

vyos@vyos:~$ show ip bgp
   Network          Next Hop            Metric LocPrf Weight Path
*> 10.0.2.0/24      0.0.0.0                  0         32768 i
*> 10.99.0.0/16     169.254.234.49         100             0 64512 i
*                   169.254.61.113         100             0 64512 i

第2回のVGWは 100 / 200 を付けてアクティブ/スタンバイに誘導してきました。TGWは 100 / 100 の等コスト。これがTGWのデフォルトが**ECMP(両系アクティブ)**である証拠です。終端を替えただけで、AWS側のBGP広告の性格が変わる、というのは押さえどころです。

VGW(第2回)TGW(今回)
AWSが付けるMED100 / 200100 / 100
デフォルト挙動アクティブ/スタンバイECMP(両系同時利用)

非対称ルーティングに注意

ECMPの実機検証で、実際にこういう現象が出ます。AWS側からpingを打つと、行きと戻りで別々のトンネルを通ることがあります。

aws ping: asymmetric

VyOS側から打つと、両方向とも同じ経路(.6系)に乗りました。

vyos ping

これがECMPの本質的な副作用、非対称ルーティングです。

仕組みを補足すると、ECMPの経路選択はパケット1個ごとのランダムではなく、フロー単位のハッシュで決まります。送信元/宛先IP・ポート・プロトコルの組(5タプル)からハッシュ値を計算し、それでトンネルを選ぶ。だから同じTCPコネクションのパケットは必ず同じトンネルを通る(順序が入れ替わらない)。問題は、行き(A→B)と戻り(B→A)は5タプルの並びが逆なので別のフローとして扱われ、別々にハッシュされる点です。結果、行きと戻りで違うトンネルに乗ることがある——これが非対称の正体です。

ICMPなら非対称でも普通に返ります。ですが、経路上にステートフルな機器(FW・NAT・IDS等)があると致命的になり得ます。これらは「行きのパケットを見て接続状態を記憶し、その記憶に合う戻りだけ通す」動きをするため、戻りが別経路(=別の機器、状態の記憶がない)を通ると、正規の応答パケットが「身に覚えのない通信」と判断されて破棄されるからです。iptables の conntrack(ステートフルNAT/FW)を思い浮かべると分かりやすい挙動です。

実運用でECMPを使うなら、経路上のステートフル機器の有無を必ず確認してください。「とにかく帯域を増やしたい」だけなら、行き戻りが揃う第2回のアクティブ/スタンバイのほうが安全、という判断も十分あり得ます。ECMP=常に正解、ではありません。

4. 完成形

VPNの終端がVGWからTransit Gatewayに替わり、2本のトンネルがECMPで同時に使われる構成です。

tgw ecmp final

5. うまくいかないときの確認ポイント

ECMPは「トンネルは2本とも上がっているのに、片方しか使われない」という静かな失敗をしがちです。RIB(経路が見えているか)とFIB(実際に転送に使われているか)を分けて切り分けます。

症状確認コマンド / 見るところ
2本見えているのに片方しか使われないshow ip bgp=(multipath)が付いているか。付いていなければ maximum-paths ebgp 漏れ を最優先で疑う
= が付かない(best 1本のまま)第2回のWeight/AS-Path Prependが残っていないか。show ip bgp 10.99.0.0/16 で2本のWeight・AS-Path長・MEDが一致しているか確認
BGPテーブルは正しいのに転送が片寄るshow ip route 10.99.0.0/16 でFIBに2本入っているか。1本ならBGP→RIB→FIBのどこで落ちたか(≒maximum-paths)を確認
通信が不安定・特定通信だけ落ちる非対称ルーティングを疑う。経路上のステートフル機器(FW/NAT/IDS)の有無を確認(§3)
そもそも片トンネルが上がらない第1回の確認ポイントへ。show vpn ipsec sa でSA状態、CloudWatch Logs(§2で有効化)でネゴシエーション段階を確認

まとめ

  • VGWは1VPC専用のVPN口、TGWはリージョンのハブルータ。格が違う
  • 終端をTGWにすると、AWSのMEDが 100/100の等コストになり、デフォルトで**ECMP(両系アクティブ)**になる(VGWは100/200のアクティブ/スタンバイ)
  • ただしAWS側が等コストで広告するだけでは不十分。VyOS側に maximum-paths ebgp 2 を入れて初めて2本が転送に使われる(BGPはデフォルトでベスト1本しか使わない)
  • ECMPには非対称ルーティングの副作用がある。経路はフロー単位のハッシュで選ばれ、行きと戻りが別トンネルになり得る。経路上にステートフル機器があると危険
  • TGWアタッチは AZ一致・専用サブネット・ルートの明示登録 が踏みどころ
  • TGWというハブを手に入れたことで、次回別リージョンとの接続に進めます。ここで「DX-GWではリージョン間がつながらない」という壁にぶつかり、TGWピアリングで超えます。