現場で泣かないためのTCPステータス解読術:ssコマンドと「接続の死に体」を追う
深夜3時、アラートメールの着信音で叩き起こされる。ダッシュボードは真っ赤。Web APIのレイテンシは跳ね上がり、ELB(ロードバランサー)のヘルスチェックはタイムアウトを連発している。こんな時、君が真っ先に叩くべきコマンドは何だ?
そう、ss(または古き良きnetstat)だ。
教科書には「TCPの3ウェイ・ハンドシェイク」が美しいシーケンス図で載っているが、現場のサーバーで見かけるのは、そんな優等生な通信ばかりじゃない。今回は、TCPの各ステータスが何を示し、なぜそれがシステムを蝕む「負債」となるのか、現場の視点から紐解いていこう。
—
1. 現場で観測されるTCPステータス:その「意味」を血肉にする
サーバーの接続状態を確認する際、ss -ant を叩く。ここに出るステータスを単なる文字列としてではなく、パケットの「呼吸」として捉える必要がある。
LISTEN: 門番。bindされたポートがクライアントのノックを待っている状態。ここが埋まると、そもそも接続すら受け付けない。SYN_RECV: 門番が「入るか?」と問われ、「いいぜ」と答えた直後。ACKが帰ってくるのを待っている状態。ここが異常に多いなら、典型的なSYN Flood攻撃か、バックログの限界だ。ESTABLISHED: 宴の真っ最中。通信の主役。TIME_WAIT: 最も厄介な「余韻」。通信終了後、ネットワーク上に残留するパケットを消し去るために2MSL(Maximum Segment Lifetime)待機している状態。
特に開発者が苦しめられるのは、この TIME_WAIT の山だ。
—
2. なぜ TIME_WAIT は居座り続けるのか?
「コネクションを閉じたはずなのに、ssで見ると数百のTIME_WAITが消えない……」。これは、君がAPIクライアントとして短期間に大量のリクエストを送り出し、接続を使い捨てている証拠だ。
TCPの仕様上、クローズ時に先にFINパケットを送った側が TIME_WAIT になる。Web APIのクライアント(Pythonのrequestsやcurl)で毎回接続を切断していると、サーバー側ではなく「クライアント側のOS」でこのステータスが積み上がり、やがてエフェメラルポート(一時的なポート)を使い果たして「接続不可」になる。
トラブルシューティング:TIME_WAIT の調査手順
まずは、どのプロセスがどの程度の接続を掴んでいるか、泥臭く追いかける。
# 接続ステータスごとにカウントを取る。これで何が起きているか一目瞭然だ
ss -ant | awk '{print $1}' | sort | uniq -c | sort -nr
# 特定ポート(例えば8080)でTIME_WAITが異常に多いなら、そのコネクション元を特定する
ss -ant state time-wait sport = :8080
もし君がPythonで requests を使っているなら、Session オブジェクトを使って接続を再利用(Keep-Alive)していないのが原因だ。
import requests
# 悪い例:リクエストのたびに新しい接続を開く。TIME_WAITの温床になる
# for _ in range(100):
# requests.get('http://api.example.com')
# 良い例:Sessionを使ってコネクションプールを再利用する
session = requests.Session()
for _ in range(100):
session.get('http://api.example.com') # TCPコネクションを使い回すためTIME_WAITが発生しない
—
3. 現場で使える「攻め」のカーネルチューニング
どうしても短期間に大量の接続が発生するインフラの場合、OS側の設定で「ゴミ掃除」を加速させる必要がある。/etc/sysctl.conf をいじる準備はいいか?
以下の設定は、現場で多くの障害を回避してきた「お守り」のようなものだ。
# /etc/sysctl.conf に追記
# 1. TIME_WAIT状態のソケットを再利用可能にする(クライアント側で有効)
net.ipv4.tcp_tw_reuse = 1
# 2. TCPのバックログを増やす(高負荷時に接続拒否を防ぐ)
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 3. エフェメラルポートの範囲を広げる(接続枯渇対策)
net.ipv4.ip_local_port_range = 1024 65535
設定後は sysctl -p を叩くのを忘れるな。ただし、これらは「対処療法」だ。根本解決は、アプリケーション層でのコネクション管理(コネクションプーリング)にあることを忘れないでほしい。
—
4. 最後に:トラブルシューティングは「仮説」の積み重ね
障害対応の現場で最も重要なのは、「コマンドを叩くこと」そのものじゃない。「どのステータスが、どの通信フローのどの段階で止まっているのか」を想像する力だ。
ssコマンドの出力に SYN_RECV が並んでいれば「相手からのACKが届いていない、あるいはルーターのファイアウォールでブロックされているのか?」と考え、ESTABLISHED が全く減らないなら「アプリケーションがデータを読み出せずにブロックしているのではないか?」と疑う。
教科書通りの手順をなぞるだけでは、本質的な障害は解決しない。パケットの行方を頭の中で描き、コマンドの出力からシステムの断末魔を感じ取れるようになってこそ、一人前のエンジニアだ。
さあ、次のアラートが鳴る前に、君のサーバーの ss を眺めてみてくれ。そこには、君のコードとネットワークのリアルな会話が記録されているはずだ。
コメント