【実務・中級編】 インターネットゲートウェイ経由の通信におけるMTU(Maximum Transmission Unit)制限 – クラウドインフラと仮想化ネットワーク実践ガイド

パケットが泣く日:AWSのMTU 1500制限とIGWの罠から学ぶ、ネットワークの深層

クラウドのインフラを設計していると、すべてが抽象化されて魔法のように動くように錯覚しがちだ。しかし、夜中の3時に「特定の外部APIに対するPOSTリクエストだけがタイムアウトする」「マイクロサービス間のデータ同期が突如としてスローダウンした」といった現場の修羅場に直面したとき、私たちが引き戻されるのは、いつの時代も変わらない「物理とプロトコルの現実」である。

今回は、AWS VPCの基本中の基本でありながら、実務の現場で数々のエンジニアをハマらせてきた「MTU(Maximum Transmission Unit)」、特にインターネットゲートウェイ(IGW)を跨ぐ通信におけるパケットの断片化と制約について、シニアエンジニアの視点から徹底的に紐解いていこう。

—

1. なぜMTU 1500なのか? イーサネットの歴史とAWSのリアル

私たちが日常的に使っているインターネットの標準的なイーサネットフレームのMTUは 1500 バイトだ。これは、イーサネットヘッダー(14バイト)とFCS(4バイト)を除いた、ネットワーク層(IP層)が一度に運べるペイロードの最大サイズを指す。

+-----------------------------------+--------------------+
| Ethernet Header (14B) + FCS (4B)  | IP Payload (1500B) |
+-----------------------------------+--------------------+
|<--------- 標準的なMTU 1500バイト --------------------->|

AWSのEC2インスタンス(VPC内)は、デフォルトでこの 1500 バイトのMTUを採用している。しかし、モダンなAWS環境では、同一VPC内やVPCピアリング、Transit Gatewayを介した通信において、最大 9001 バイトのジャンボフレーム(Jumbo Frames)が利用できる。

「おっ、じゃあスループットを上げるために全部 9001 にしちゃえばいいじゃないか」――そう考えた若き日のエンジニアが踏む地雷が、インターネットゲートウェイ(IGW)を介した外部ネットワークとの通信である。

IGWの向こう側は「普通のインターネット」であるという事実

AWSのVPC内では 9001 バイトの巨大なパケットを快適に飛ばせていても、そのパケットがIGWを通過してパブリックインターネットに出た瞬間、状況は一変する。インターネット上の一般的なルーターやISPの回線規格は、いまだに 1500 バイト(あるいはPPPoEなどの影響でさらに小さい値)を基本としている。

もしあなたが 9001 バイトのパケットをそのままIGWへ送り出そうものなら、何が起きるか?
ルーターは「おいおい、こんなデカい荷物はうちのトラックに乗らないよ」と悲鳴を上げる。これが、ネットワークの世界における MTU制限とパケット断片化(Fragmentation)のドラマ の始まりだ。

—

2. パケットの悲劇:断片化(Fragmentation)とPMTUDのメカニズム

パケットが経路上の最小MTU(Path MTU)を超えるサイズである場合、ルーターは二つの選択肢のどちらかを選ぶ。

1. パケットを分割(フラグメント化)して転送する。
2. パケットを破棄し、送信元に「ICMP Destination Unreachable (Fragmentation Needed)」を返す。

ここで厄介なのが、現代のセキュリティ意識の高いクラウド環境やファイアウォールだ。セキュリティグループやネットワークACL、あるいは途中のキャリアのセキュリティポリシーによって、ICMPパケットが容赦なくドロップ(ブラックホール化)されるケースが後を絶たない。

これが有名な 「PMTUD(Path MTU Discovery)ブラックホール問題」 である。
送信元は応答(ICMP)を受け取れないため、「相手が受け取っているはずだ」と信じ込み、ひたすら巨大なパケットを再送し続ける。結果として、TCPのコネクションは確立するものの、データ転送フェーズに入った瞬間にピタリと通信が止まるという、最もデバッグが困難な現象が発生する。

—

3. 実務で遭遇するトラブルとデバッグの作法

では、実際にこのようなトラブルに直面したとき、現場のSREはどう動くべきか。具体的なデバッグ手順を見ていこう。

ステップ1: インスタンス上のMTU設定を確認する

まずは、踏み台や該当のEC2インスタンスにログインし、現在のネットワークインターフェイス(ENI)のMTUを確認する。

# Linux環境でのインターフェイス確認
ip link show

出力結果の mtu 9001 や mtu 1500 という記述に注目してほしい。もしインターフェイス自体が 9001 に設定されている場合、アプリケーション層が意識しないところでジャンボフレームが生成されている可能性がある。

ステップ2: ping によるPath MTUの実測(Don’t Fragmentフラグの活用)

パケットが途中で断片化されずにどこまで通るかを調べるには、ping コマンドの Do not fragment (DFフラグ)オプションを使用するのが定石だ。

# Linux環境で、パケットサイズ1472バイト(IPヘッダー20B + ICMPヘッダー8B = 合計1500B)を指定し、DFフラグを立てて送信
ping -M do -s 1472 8.8.8.8

もし途中の経路でMTU 1500 を超える制約があり、かつICMPが正しく返ってくる環境であれば、frag needed のメッセージを確認できる。逆に、何も返ってこずにタイムアウトする場合は、PMTUDが途中でブロックされている(ブラックホール化している)シグナルだ。

—

4. アプリケーション・Web API設計における実務的な対策

インフラ層の挙動を理解したところで、これをWeb APIの設計やクライアント側の実装にどう落とし込むべきか。

A. MSS(Maximum Segment Size)クランプの活用

ルーターやNATゲートウェイ(AWSのNAT GatewayやIGW)は、TCPのSYNパケットに含まれるMSSを書き換えることで、あらかじめ適切なサイズ(通常はMTUからIP/TCPヘッダーを引いた 1460 など)に制限する機能を備えている。しかし、UDPベースの通信(HTTP/3やgRPC over HTTP/2の一部など)ではこの手法が使えないため、アプリケーション側での配慮が必要になる。

B. Python(requests / httpx)や cURLでの挙動検証

外部の巨大なペイロードを持つWeb APIを叩くスクリプトを書く際、タイムアウトやコネクションの切断に悩まされたら、まずはcurlで詳細なトレースを取ってみる。

# 詳細な通信ログとヘッダーを出力しながらAPIを叩く
curl -v -X POST "https://api.example.com/v1/data" \
  -H "Content-Type: application/json" \
  -d '{"payload": "..."}'

また、Pythonの requests ライブラリ等でカスタムアダプターを使い、タイムアウトやリトライを適切にハンドリングすることは基本中の基本だが、根本的なネットワーク層の原因を見落としていると、いくらリトライ回数を増やしても無駄骨に終わる。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_robust_session():
    """
    ネットワークの微小な揺らぎやMTU起因の一時的なパケットロスに備え、
    適切なリトライ戦略を持ったrequestsセッションを構築する。
    """
    session = requests.Session()
    
    # リトライポリシーの設定
    retries = Retry(
        total=3,
        backoff_factor=1,
        status_forcelist=[500, 502, 503, 504],
        raise_on_status=False
    )
    
    adapter = HTTPAdapter(max_retries=retries)
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    
    return session

# 使用例
if __name__ == "__main__":
    client = create_robust_session()
    try:
        response = client.post(
            "https://api.example.com/v1/data",
            json={"status": "check"},
            timeout=10
        )
        print(f"Status Code: {response.status_code}")
    except requests.exceptions.RequestException as e:
        print(f"通信エラーが発生しました: {e}")

—

5. シニアからの提言:設計段階でのベストプラクティス

最後に、AWS環境でネットワークを設計・運用する上での黄金律をいくつか残しておこう。

1. インターネット向けインスタンスのMTUは原則 1500 を維持する
内部通信のパフォーマンス(ビッグデータ処理やNFSマウントなど)を上げるためにジャンボフレーム(9001)を採用するのは大いに結構だが、パブリックIPを持つ、あるいはIGWを頻繁に経由するWeb/APIサーバーのENIは、標準の 1500 に据え置くか、ルーティングの設計を厳密に行うこと。
2. ICMPを完全にブロックしない
セキュリティの観点から「すべてのICMPを遮断する」というポリシーを敷く組織があるが、Destination Unreachable などの制御メッセージまで塞いでしまうと、PMTUDが機能不全に陥り、遠隔のクライアントから「繋がらない謎の現象」を引き起こす最大の原因になる。最低限必要なICMPタイプは許可するべきだ。
3. パケットキャプチャ(tcpdump)を恐れない
迷ったら、ENI上で tcpdump -i eth0 -nnvvS "tcp[tcpflags] & (tcp-syn) != 0" などのコマンドを叩き、実際にやり取りされているMSSの値を自分の目で確認しよう。生のパケットは決して嘘をつかない。

クラウドの抽象化の裏側で、パケットは今日も無数のルーターをくぐり抜け、宛先を目指して旅をしている。その旅路のルール(MTU)を正しく理解し、リスペクトすることこそが、障害に強い堅牢なシステムを作り上げる唯一の近道なのである。

コメント

タイトルとURLをコピーしました