【実務・中級編】 TCPタイムアウト設定の最適化とアイドルコネクションの維持 – クラウド&コンテナネットワーク実践ガイド

なぜ、あなたのロングコネクションは突然「沈黙」するのか?―NATゲートウェイとTCPアイドルタイムアウトの深淵

夜中の2時、原因不明の「504 Gateway Timeout」や「Connection Reset by Peer」に叩き起こされた経験はありますか?

AWSのNATゲートウェイやGCPのCloud NATを使っていると、ふとした瞬間に通信が途切れる現象に遭遇します。特に、WebSocketやストリーミング配信、あるいは長時間待機するバックエンド処理など、いわゆる「ロングコネクション」を扱うシステムにおいて、この「謎の切断」は避けて通れない登竜門のようなものです。

今日は、教科書には載っていない、現場の泥臭いネットワークトラブルの正体と、その解決策についてお話ししましょう。

—

犯人は「350秒の沈黙」:NATゲートウェイの冷徹な仕様

まず、大前提として理解すべきは、クラウドのマネージドNATゲートウェイは「ステートフルなファイアウォール」として振る舞っているという点です。

パケットがNATゲートウェイを通過するたび、ゲートウェイはコネクション情報を追跡(トラッキング)します。しかし、メモリリソースには限りがあるため、長期間通信がない(アイドル状態の)コネクションをいつまでも保持するわけにはいきません。

AWSのNATゲートウェイの場合、350秒間(5分50秒)の通信がないコネクションは、容赦なく「破棄」されます。しかも、この破棄の通知(FINパケットなど)は送られません。ある瞬間、パケットがブラックホールに消えるだけです。これが、クライアント側が「突然通信が切れた」と感じる正体です。

—

TCP Keep-Aliveによる「生存証明」の技術

この強制切断を防ぐ唯一の現実的な解は、「こちらから定期的にパケットを投げ、コネクションが生きていることをNATゲートウェイに教え続ける」ことです。これが TCP Keep-Alive です。

1. TCPレベルでの設定(OS/ライブラリ)

アプリケーションコードに手を入れる前に、OSレベルでのチューニングが基本です。Linuxであれば、カーネルパラメータで調整が可能です。

# sysctl.conf での設定例
# 最後にパケットを送信してからプローブを開始するまでの時間(秒)
net.ipv4.tcp_keepalive_time = 60
# プローブを送信する間隔(秒)
net.ipv4.tcp_keepalive_intvl = 10
# 接続を切断とみなすまでのプローブ回数
net.ipv4.tcp_keepalive_probes = 3

これらを適切に設定することで、通信が途絶えてから1分ほどでKeep-Aliveパケットが自動的に送信され、NATゲートウェイのタイマーがリセットされます。

2. Python (requests/urllib3) での制御

PythonでHTTPリクエストを投げる際も、Keep-Aliveを明示的に有効化するのが鉄則です。

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

# セッションを生成し、Keep-Aliveを維持する
session = requests.Session()

# 接続の再利用設定
adapter = HTTPAdapter(
    pool_connections=10,
    pool_maxsize=100
)
session.mount('https://', adapter)

# これにより、コネクションがプールされ、Keep-Aliveが有効になる
response = session.get('https://api.example.com', timeout=60)

3. Web API設計時の注意点:HTTPレベルのKeep-Alive

インフラ側の設定だけでなく、アプリケーション層での「ハートビート」も重要です。例えば、gRPCやWebSocketを使用している場合、プロトコルレベルでping/pongメッセージを定期的にやり取りするように設計してください。

特に注意すべきは、Nginx や ALB が間に入っている場合です。これらもまた「独自のタイムアウト値」を持っています。NATゲートウェイの350秒よりも短いタイムアウト値が設定されている場合、NATゲートウェイに届く前に上流で切断されてしまいます。

—

現場で役立つ「デバッグの鉄則」

もしトラブルが発生したら、まず tcpdump でパケットを追いかけてください。

# NATゲートウェイを通るパケットを監視
sudo tcpdump -ni eth0 tcp port 443 -v

ここで注目すべきは、「相手からのFIN/RSTパケットが帰ってきているか」、あるいは「自分から送ったはずのパケットが相手に届いていない(再送が繰り返されている)か」です。

  • FIN/RSTが来ている場合: アプリケーション層やロードバランサーのタイムアウトが原因です。
  • パケットが送られても応答がない場合: NATゲートウェイのアイドルタイムアウトでコネクション情報が破棄された可能性が高いです。

—

最後に:完璧なネットワークなど存在しない

シニアエンジニアとして皆さんに伝えたいのは、「ネットワークは信頼できないもの」という前提で設計することの重要性です。

どんなに素晴らしいインフラでも、クラウドの仕様変更やメンテナンスでコネクションは切れます。だからこそ、アプリケーション側で「切断されたら再接続する(Exponential Backoff付きのRetryロジック)」という堅牢なリカバリ処理を実装することが、結局のところ最もコストパフォーマンスの高い解決策になります。

NATゲートウェイの350秒は変えられませんが、私たちが書くコードの柔軟性は無限大です。ぜひ、今日からコネクションの寿命を意識した設計を取り入れてみてください。

それでは、良いデバッグライフを!

コメント

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