亡霊が巣食うエフェメラルポート:TIME_WAIT枯渇との終わらない戦い
深夜2時、アラートが鳴り響く。監視ダッシュボードには、Webフロントエンドのレスポンスタイムが異常なスパイクを描いている。現場の若手エンジニアは「負荷が集中したせいだ」と安易にスケールアウトを提案するが、私は画面越しの ss の出力を見て、静かに首を振る。
「違う。これは負荷ではない。TCPの残滓(ざんし)が、システムを窒息させているんだ」
今回は、現代の高トラフィック環境における真の敵、TIME_WAIT 状態の蓄積と、それをカーネルレベルでいかにねじ伏せるかという、泥臭くもエレガントな戦いについて語ろう。
—
なぜTIME_WAITは「悪」なのか
TCPのステートマシンにおいて、TIME_WAIT は決して欠陥ではない。むしろ、通信の整合性を保つための「賢明な猶予期間」だ。コネクションをクローズした側(アクティブクローズ側)が、ネットワーク上に漂う遅延パケットを確実に破棄し、新しいコネクションと古いコネクションが混線しないようにするための安全装置である。
問題は、この猶予期間が「エフェメラルポート(一時ポート)」を占有し続けることにある。
短命なTCP接続を繰り返す高密度なマイクロサービスやAPIゲートウェイでは、ポートの供給が需要に追いつかなくなる。ポートが枯渇すれば、OSは新しい connect() を受け付けない。パケットは送信される前に EADDRNOTAVAIL という冷徹な例外を叩きつけられる。
現状把握:ssコマンドという名の解剖用メス
まずは、自分の戦場を正確に把握しよう。netstat という老兵はもう引退だ。カーネルのネットワーク統計をダイレクトに叩く ss コマンドを使う。
# 現在のTIME_WAIT状態のソケット数をカウントする(即座に異常の予兆を掴む)
ss -tan state time-wait | wc -l
# 特定の宛先ポートに対する通信状況を深掘りする
ss -tan 'sport = :80 or sport = :443'
ここで重要なのは、単に「数が多い」ことよりも、その推移だ。秒間の接続数がエフェメラルポートの範囲(通常 net.ipv4.ip_local_port_range で定義)を MSL(Maximum Segment Lifetime)の2倍の期間で割った値を上回れば、いずれ破滅が訪れる。
カーネルパラメータによる「外科手術」
教科書では net.ipv4.tcp_tw_recycle の有効化が紹介されることがあるが、今すぐ忘れろ。これはNAT環境下でパケットのタイムスタンプが狂い、通信が全滅するリスクを孕んだ「禁断の果実」だ。
我々が採用すべきは、より安全でモダンな net.ipv4.tcp_tw_reuse である。
1. 安全な再利用の許可
/etc/sysctl.conf に以下を追記し、カーネルに「タイムスタンプが有効なら、TIME_WAIT状態のソケットを新しいコネクションに再利用してもよい」と教え込む。
# TIME_WAIT状態のソケットを、安全な条件(タイムスタンプの比較)下で再利用する
net.ipv4.tcp_tw_reuse = 1
# エフェメラルポートの範囲を拡張し、許容される「打席数」を増やす
net.ipv4.ip_local_port_range = 1024 65535
2. TCPバッファの最適化(RTT削減の副次的効果)
TIME_WAITの蓄積は、往々にしてTCPのクローズシーケンスが滞ることに起因する。バッファサイズを適切に管理することで、パケットの滞留を減らす。
# TCPウィンドウの自動調整(高スループット環境の必須設定)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
—
本質的な解決策:プロトコルとアーキテクチャの最適化
カーネルチューニングはあくまで「延命措置」に過ぎない。インフラアーキテクトとして目指すべきは、TIME_WAITを発生させない設計だ。
Keep-Aliveの徹底(HTTP/1.1からHTTP/2への転換)
短命接続が問題なら、接続を殺さなければいい。Keep-Alive を有効にし、コネクションプールを適切に管理せよ。特に HTTP/2 や gRPC は、単一のTCPコネクション上で多重化(Multiplexing)を行うため、この問題を根本から解決できる。
TLSハンドシェイクのオーバーヘッド削減
TLS 1.3への移行は、RTT(Round Trip Time)を劇的に減らす。0-RTT(Early Data)を利用すれば、ハンドシェイクを待たずにデータを送信可能だが、リプレイアタックのリスクがある。このバランスをどう取るか。ここがセキュリティ専門家の腕の見せ所だ。
ヘッダー圧縮の活用
HPACK や QPACK を活用し、ペーロードではなくヘッダーの肥大化によるパケット断片化を防ぐ。小さなパケットが大量に飛び交う環境は、TCPの輻輳制御アルゴリズム(BBR や Cubic)にとって最も判断に困る状況だ。
—
最後に:ネットワークを「飼い慣らす」ということ
「ネットワークが重い」という報告に対し、漫然と ping を打って帰ってくるようなエンジニアであってはならない。
カーネルがソケットをどう管理し、パケットがどのスタックで止められ、なぜそのポートが解放されないのか。その挙動が脳内でイメージできて初めて、真のトラブルシューターと言える。
TIME_WAITの枯渇は、システムが成長したという証拠でもある。しかし、それに押し潰されるか、あるいは設定を研ぎ澄ませて乗り越えるかは、貴方の知見次第だ。さあ、今夜も ss でパケットの鼓動を聴こうではないか。
コメント