亡霊となったコネクション:NATゲートウェイの「沈黙」とTCPセッションの死線
クラウドネイティブなインフラの設計において、NATゲートウェイ(NAT GW)は単なる「外への出口」ではない。それはステートフルな変換器であり、同時に数百万のTCPセッションを監視する非情な番人でもある。
多くのエンジニアが、「なぜか突然通信が切れる」「接続確立時にSYNパケットがドロップされる」という不可解な事象に直面する。その犯人の多くは、TCPのタイムアウト仕様とNAT GWの追跡テーブルの乖離にある。今日は、パケットレベルの挙動から、この「沈黙の断絶」をどう制御すべきか、深淵を覗いてみよう。
—
1. NATゲートウェイの「忘却」メカニズム
AWSのNAT GatewayやGCPのCloud NATは、内部で広大な接続追跡(Connection Tracking)テーブルを維持している。しかし、リソースは有限だ。そのため、一定時間通信のない「アイドル状態」のTCPコネクションに対して、NAT GWは容赦なく送信元ポート(Source Port)を解放する。
- AWS NAT Gateway: アイドルタイムアウトは固定で「350秒」。
- GCP Cloud NAT: デフォルトは「1200秒(20分)」だが、設定で変更可能。
この「350秒」という時間は、アプリケーション層から見れば「死の宣告」だ。NAT GWがポートを解放した後、クライアントがFINパケットを送信しても、NAT GWはそれを「未登録のコネクション」として破棄する。結果として、クライアント側には RST パケットが戻ることもなく、単にリクエストがタイムアウトするだけの「ブラックホール」が完成する。
—
2. キープアライブの「質」が運命を分ける
この断絶を防ぐための定石は、TCP Keepalive だ。しかし、単に有効にするだけでは不十分だ。カーネルパラメータのデフォルト値(多くの場合、最初のプローブまで2時間)では、NAT GWの350秒の壁を突破できない。
以下の sysctl 設定を確認してほしい。LinuxカーネルのTCPスタックを、NAT環境に適応させるための最適解だ。
# /etc/sysctl.conf への追加設定例
# 最初のキープアライブプローブを送るまでのアイドル時間(秒)
# NAT GWのタイムアウト(350秒)より短く設定する必要がある
net.ipv4.tcp_keepalive_time = 60
# プローブを再送する間隔(秒)
net.ipv4.tcp_keepalive_intvl = 10
# 接続を強制切断するまでのプローブ回数
net.ipv4.tcp_keepalive_probes = 6
これで、コネクションは60秒ごとにパケットを送信し、NAT GWの「タイマー」を強制的にリセットし続ける。だが、注意してほしい。アプリケーションレベル(HTTP/2やgRPC)での keepalive 設定が優先される場合も多い。特にTLSハンドシェイクが絡む場合、このレイヤーでの制御が重要になる。
—
3. トランスポート層の最適化:RTTとバッファの調律
ネットワークを駆け巡るパケットの往復時間(RTT)を最小化することは、単なる速度改善ではなく、コネクションの維持確率を高めることにも直結する。
TCPウィンドウサイズのチューニング
Cloud NAT越しに広帯域通信を行う場合、デフォルトのバッファサイズでは帯域幅遅延積(BDP)を埋めきれない。
# 高速ネットワーク環境向けの推奨設定
# 読み取りバッファの最大値(16MB)
net.core.rmem_max = 16777216
# 書き込みバッファの最大値(16MB)
net.core.wmem_max = 16777216
# TCPの自動チューニング範囲
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
ヘッダー圧縮とTLSハンドシェイク
もしあなたがgRPCを使用しているなら、HTTP/2 の HPACK アルゴリズムによるヘッダー圧縮を最大限活用すべきだ。さらに、TLS 1.3の「0-RTT(Zero Round-Trip Time)」を導入すれば、ハンドシェイクの遅延を劇的に削減できる。ただし、0-RTTにはリプレイ攻撃のリスクが伴うため、冪等性のあるリクエストのみに制限する「防衛的なアーキテクチャ」が必須だ。
—
4. 現場で直面する「ポート枯渇」という絶望
NAT GWが「350秒」で切断するもう一つの理由は、送信元ポートの枯渇(SNATポート枯渇)だ。高負荷なWeb APIサーバーが数万のコネクションを維持しようとすれば、NAT GWのポート数は瞬く間に尽きる。
- 解決策:
1. コネクションプーリング: アプリケーション層でコネクションを使い回す。
2. ターゲットグループの分散: NAT GWを複数配置し、トラフィックをAZ(Availability Zone)ごとに分離してポートの割り当てを論理的に増やす。
3. PrivateLinkの検討: AWSであれば、通信先がVPCエンドポイントをサポートしている場合、NAT GWを介さずにAWS内部ネットワークへ直接接続する。これが「通信を物理的に通さない」という最強のセキュリティとパフォーマンスの最適解だ。
—
最後に:ネットワークは「生き物」である
クラウドのネットワークを設計するということは、単にルーティングテーブルを書くことではない。パケットがいつ生まれ、いつ死ぬのか。そのライフサイクルを、物理レイヤーからアプリケーションレイヤーまで一気通貫で可視化することだ。
NAT GWのタイムアウトに翻弄される日々は、今日で終わりにしよう。tcpdump でパケットのシーケンス番号を追い、カーネルの netstat でソケットの状態を監視する。その泥臭い作業の先にこそ、真に堅牢なクラウドインフラの姿があるはずだ。
貴方のシステムを流れるパケットに、幸あらんことを。
コメント