はじめに:なぜVPCの「最長一致」で夜中に呼び出されるのか
インフラエンジニアとしてキャリアを積んでいると、一度は「なぜか意図したターゲットにパケットが流れない」という怪奇現象に直面します。本番環境のリリース直前、あるいは静かな夜中に突然鳴り響くアラート。「APIサーバーから特定の外部エンドポイントへ繋がらない」「VPCピアリング越しの通信が、なぜかインターネットゲートウェイ(IGW)に吸い込まれていく」。
こうしたネットワークの迷宮に入り込んだとき、原因の多くはAWS VPCのルートテーブルにおけるルーティング競合、そして最長一致ルーティング(Longest Prefix Match)の評価ミスにあります。
教科書には「プレフィックス長が最も長いルートが優先される」と一言で書かれています。しかし、AWSというブラックボックスの向こう側で、分散ルーターが毎秒数百万ものパケットをどのように処理し、どのルートを選択しているのか。そのリアルな挙動を理解しているかどうかで、障害シューティングのスピードは文字通り10倍変わります。
今回は、数々の修羅場を潜り抜けてきたシニアSREの視点から、AWS VPCのルートテーブルにおける最長一致のメカニズムを、パケットの旅路とともに徹底的に紐解いていきましょう。
—
1. ルート評価の基本原則:RFCとAWS実装のリアル
最長一致ルーティング(Longest Prefix Match)とは何か
IPネットワークの世界では、パケットの宛先IPアドレスに対して、ルーターのルーティングテーブルに登録されているプレフィックス(ネットワーク部)のビット長が最も長いものを優先して選択するという大原則があります。これはRFC 1812などのインターネット標準に根ざした動作であり、AWSのVPCデータプレーンも例外ではありません。
例えば、あなたのVPCのルートテーブルに以下の2つのルートが鎮座しているとします。
1. デフォルトルート:0.0.0.0/0 (プレフィックス長: 0ビット)
2. 特定のCIDR:10.0.0.0/16 (プレフィックス長: 16ビット)
ここで、宛先IPアドレスが 10.0.5.15 のパケットがVPC内のインスタンスから送出されたとしましょう。
AWSの分散ルーターは、この宛先IPを二進数に変換し、ルートテーブルのエントリと上から順に比較する…わけではありません。ハードウェアベースの高速なTCAM(Ternary Content-Addressable Memory)やそれに類するカスタムASICを用いて、一瞬にして最もビット一致数(マスク長)が大きいエントリを特定します。
10.0.5.15と0.0.0.0/0の一致:0ビット一致10.0.5.15と10.0.0.0/16の一致:16ビット一致
当然、プレフィックス長が長い 10.0.0.0/16 が勝者となり、パケットはローカルVPC内へルーティングされます。もしこの特定のルートがなければ、0.0.0.0/16 にもヒットしないため、デフォルトルートである 0.0.0.0/0(インターネットゲートウェイ等)へと吸い込まれていくことになります。
AWS特有の「重なり」のルールと注意点
AWSのVPCルートテーブルでは、CIDRの範囲が部分的に重複する設定が許可されています。しかし、完全に同一のプレフィックスを持つルートを複数登録することはできません。
実務でやりがちなミスが、既存の広範囲なルート(例: 10.0.0.0/8)が存在する環境に、より狭い範囲のルート(例: 10.100.0.0/16)を後から追加し、意図したトラフィックを別のNATゲートウェイや仮想アプライアンス(Transit Gatewayやサードパーティ製FW)に強制的に曲げようとするケースです。最長一致の原則を知っていれば「10.100.0.0/16 宛てのパケットは狭い方が優先される」と確信できますが、プレフィックスの計算を誤ると、予期せぬルーティングループやブラックホールを生み出す原因になります。
—
2. パケットの通信フロー:分散ルーターの頭の中
では、AWSのインフラストラクチャ層でパケットがどのようにルーティングされているのか、そのシーケンスを覗いてみましょう。
[EC2インスタンス / Lambda]
│
▼ (パケット送出: 宛先 10.0.5.15)
[AWS分散ルーター(ハイパーバイザー/Nitroカード)]
│
├─► ルートテーブル評価ロジック起動
│ ├─ 0.0.0.0/0 (Target: igx-xxxxxxxx) -> 一致長 0
│ ├─ 10.0.0.0/8 (Target: TGW-xxxxxxx) -> 一致長 8
│ └─ 10.0.0.0/16 (Target: local) -> 一致長 16 ★WINNER!
│
▼ (最長一致によりターゲット確定)
[ローカルVPCのENI / サブネットへ転送]
Nitro Systemを採用した近年のAWS環境では、これらのルーティング処理はホスト側のハードウェアアクセラレーター(Nitroカード)で超高速に処理されています。CPUのオーバーヘッドなしにミリ秒以下のレイテンシーで最長一致評価が行われ、パケットは次のホップへと送り出されます。
—
3. 実践:Terraformによるルート設計と競合回避のコード
ここからは、実務でそのまま使えるコード例を見ていきます。
安全かつ予測可能なネットワークを構築するため、Terraformを用いて意図的な最長一致ルートを構成する例を挙げます。
以下の設定では、広範囲なトラフィックをTransit Gateway(TGW)に流しつつ、特定の社内レンジやプライベートIP帯だけをローカルまたは別のルート(VPCピアリング等)に逃がす典型的なパターンを実装しています。
# メインのVPCリソース
resource "aws_vpc" "main" {
cidr_block = "10.100.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "production-vpc"
}
}
# ルートテーブルの作成
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
# 1. デフォルトルート: すべてのインターネット向けトラフィックをNATゲートウェイへ
# プレフィックス長: 0 / 最も広範囲だが、他にいなければこれが選ばれる
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat.gw.id
}
# 2. 広範囲なプライベートネットワーク向けルート(例: 社内オンプレミス網など)
# プレフィックス長: 12
route {
cidr_block = "10.0.0.0/12"
transit_gateway_id = aws_tgw.example.id
}
# 3. 特定のサブネット/セグメント向けルート(最長一致の主役)
# プレフィックス長: 16 / 上記の 10.0.0.0/12 よりもプレフィックス長が長いため、
# 10.100.0.0/16 宛てのパケットはこちらが優先されて 'local'(VPC内)に流れる。
route {
cidr_block = "10.100.0.0/16"
gateway_id = "local"
}
tags = {
Name = "production-private-rt"
}
}
この設定において、宛先が 10.100.50.10 のパケットが飛んできた場合、ルーターは 10.0.0.0/12(12ビット一致)と 10.100.0.0/16(16ビット一致)を比較し、より長いプレフィックスを持つ 10.100.0.0/16(local)を勝者として選択します。これにより、オンプレミス側へ向かうべきではないVPC内部のトラフィックが正しくローカルに留まることになります。
—
4. デバッグと検証:本当にそのルートを通っているか?
「設定は書いた。しかし、本当に意図したルートを通っているのか?」
これを検証するのが、現場のSREの腕の見せ所です。AWS環境では、ブラックボックス化されたパケットの挙動を暴くための強力なツールが用意されています。
1. VPC Reachability Analyzerの活用
AWSコンソール、またはAWS CLIから使える VPC Reachability Analyzer は、指定した送信元(例: プライベートサブネットのEC2)と宛先(例: 外部APIサーバーや別VPCのリソース)の間で、どのルートテーブルがどのように評価され、どのホップを経由したかを静的解析してくれる神ツールです。
以下のAWS CLIコマンドで、ルーティングのシミュレーションを実行できます。
# パス分析の作成(送信元ENIから宛先IPへのルーティングパスを確認)
aws ec2 create-network-insights-path \
--source "eni-0123456789abcdef0" \
--destination "10.200.5.10" \
--destination-port 443 \
--protocol tcp
# 返却された Path ID を使って分析を実行
aws ec2 start-network-insights-analysis \
--network-insights-path-id "nip-0123456789abcdef0"
分析結果が succeeded になり、途中のルートテーブル評価ステップでどのプレフィックスがヒットしたかが視覚的に確認できます。もし意図しないルート(例: NAT Gatewayなど)にヒットしている場合は、最長一致のプレフィックス長が設計通りになっていない証拠です。
2. アプリケーション層(Python)からの死活・経路確認
実務のWeb API設計やインフラ検証では、APIクライアント側から実際にリクエストを飛ばし、タイムアウトやルーティング異常を検知するスクリプトを走らせることも有効です。Pythonの requests ライブラリを用いて、特定の宛先への疎通とレイテンシーを測定するスニペットを以下に示します。
import requests
from requests.exceptions import RequestException
def verify_api_routing(endpoint_url: str, timeout_sec: int = 3) -> None:
"""
指定されたAPIエンドポイントに対してリクエストを送り、
ルーティング障害やタイムアウトが発生していないかを検証する。
"""
print(f"Verifying connectivity to: {endpoint_url}")
try:
# 実際にHTTPSリクエストを送信
response = requests.get(endpoint_url, timeout=timeout_sec)
# ステータスコードのチェック
if response.status_code == 200:
print(f"[SUCCESS] Route is healthy. Status: {response.status_code}")
else:
print(f"[WARNING] Reached endpoint, but returned status: {response.status_code}")
except RequestException as e:
# ネットワークタイムアウトやルーティングエラー(No Route to Host等)の捕捉
print(f"[ERROR] Routing or Network failure detected: {e}")
# ここでSlackやDatadogへのアラート通知フックを記述するのも実用的です
if __name__ == "__main__":
# 検証対象の社内APIエンドポイント
target_api = "https://internal-api.example.com/healthcheck"
verify_api_routing(target_api)
もしこのスクリプトが突然 ConnectTimeoutError や Max retries exceeded を吐き出し始めたら、単なるアプリケーションのバグではなく、VPCルートテーブルの変更による最長一致の崩壊(パケットがブラックホールや誤ったゲートウェイにルーティングされている状態)を疑うべきです。
—
おわりに:シニアSREからの実務的アドバイス
AWS VPCのルートテーブルにおける最長一致ルーティングは、シンプルであるがゆえに、一度ハマると原因究明に時間がかかるポイントです。
大規模なマルチVPC環境やTransit Gatewayを組み合わせた複雑なネットワーク設計を行う際は、以下の鉄則を忘れないでください。
1. プレフィックス長を常に意識する: 「とりあえず広い範囲をカバーするルート」を書くときは、それよりも狭い(ビット長が長い)特異的なルートが他に影響を与えないか必ず図解して確認する。
2. 検証を怠らない: 変更を加えたら、本番トラフィックを流す前に必ず VPC Reachability Analyzer やテスト用インスタンスからの traceroute / curl で期待通りのパスを通っているか検証する。
ネットワークの基礎をなすこの「最長一致」の哲学を体に叩き込んでおけば、どんなに複雑なクラウドインフラの迷宮に迷い込んでも、必ず出口への最短経路(最長一致のパス)を見つけ出すことができるはずです。あなたの構築するインフラが、今日も安定したパケットの奔流に包まれますように。
コメント