【実務・中級編】 ssコマンドを用いたTCPタイマー状態(Retransmit, Keep-Alive等)の詳細分析 – トラブルシューティング&ネットワーク運用監視実践ガイド

夜間、データセンターの冷気が肌を刺す静寂の中、監視モニタの赤いアラートが突如として点灯する。
「Web APIの応答速度が急激に悪化、一部クライアントでタイムアウト発生」

こういう修羅場をくぐり抜けてきた我々NOCエンジニアが、真っ先に叩き込むコマンドは何だと思う? ping か? いや、甘い。パケットが生きているかを確認するだけの ping では、アプリケーション層とトランスポート層の深い闇で見え隠れする「パケットロス」や「コネクションの膠着(こうちゃく)」までは見えてこない。

現代のインフラ運用やWeb APIの設計において、真に信頼すべき相棒は ss コマンドだ。かつて一世を風靡した netstat はすでに歴史の舞台から退き、今やLinuxカーネルの深部からミリ秒単位のソケット状態を抉り出す ss こが、現代のネットワーク診断における最強のメスとなる。

今回は、この ss コマンドを用いて、TCPの心臓部である「タイマー状態(再送タイマー、キープアライブなど)」を徹底的に暴き、障害の芽を摘み取る実務的アプローチを伝授しよう。

—

1. なぜ netstat を捨てて ss を使うべきなのか

古い世代のエンジニアは、いまだに netstat -an と叩きたがる。しかし、考えてみてほしい。何万ものアクティブセッションを抱えるモダンなクラウドネイティブ環境や高負荷なAPIサーバーにおいて、netstat は /proc/net/tcp をテキストベースで舐め回すため、カーネル空間からユーザー空間へのデータコピーでCPUを無駄に食いつぶし、挙句の果てにはタイムアウトを起こす。

一方、ss はカーネルの netlinkソケット を直接叩き、高速にソケット情報を吸い上げる。さらに、TCPの内部ステートマシンにおける「タイマー(Timer)」の稼働状況や、輻輳制御(Congestion Control)のアルゴリズム、RTO(Retransmission Timeout)の値をダイレクトに暴き出すことができるのだ。

現場で使える基本の構文をまず確認しておこう。

# すべてのTCPソケットを、タイマー情報付きで詳細表示する
$ ss -t -a -i

この -i (--info)オプションこそが、今回の主役であるTCPタイマーの内部状態を引き出す魔法の鍵となる。

—

2. TCPタイマーの正体とRFCが定める挙動

TCPは信頼性のある通信を担保するため、いくつかのタイマーを裏で巧みに回している。代表的なものを3つ、現場の視点で押さえておこう。

1. Retransmit Timer(再送タイマー / RFC 6298)

  • 送出したセグメントに対するACKが返ってこない場合に発火する。RTO(Retransmission Timeout)に基づき、パケットが途中のルーターのバッファ溢れや回線断でロストしたと判断して再送を行う。

2. Keep-Alive Timer(キープアライブタイマー / RFC 1122)

  • アイドル状態のコネクションが生きているかを確認する。ファイアウォールやNATのセッションタイムアウトによる意図せぬ切断を防ぐために非常に重要。

3. Delayed ACK Timer(遅延ACKタイマー)

  • ACKを即座に返さず、データ送信のついでに相乗り(ピギーバック)させるために少しだけ(通常最大200ms)待つタイマー。

これらが今、サーバー内部でどう動いているのか。ss コマンドの出力から読み解いてみよう。

—

3. ss -ti が暴くリアルな出力とパラメータの読み方

実際にWeb APIサーバーで遅延が発生している最中に、以下のコマンドを実行したとする。

# 特定のポート(例: 443)に絞り込み、TCP内部情報を詳細に出力
$ ss -tin '( sport = :443 )'

出力される羅列の中から、我々が注目すべき重要メトリクスを抜粋して解説しよう。

State      Recv-Q Send-Q  Local Address:Port   Peer Address:Port  Process
ESTAB      0      128     192.168.10.50:443    203.0.113.15:58422 users:(("nginx",pid=12345,fd=15))
          ino:987654 sk:ffff8801a2b3c4d5
          ts kive.min:600000 mss:1460 rtt:145.2/35.1 rto:380 ato:40 qack:1 
          ssthresh:10 cwnd:10 retrans:0/3 app_limited

この呪文のような一行(特に2行目以降のTCPインフォ)に、障害解決のヒントのすべてが詰まっている。

各パラメータの実務的解釈

  • Send-Q: 128
  • 送信キューに128バイト(またはパケット数)が溜まっている。クライアント側の受信処理が追いついていないか、ネットワーク帯域が飽和してACKが返ってきていない状態を示唆する。ここが常に数千に張り付いている場合、バックエンドの詰まりが疑われる。
  • rto:380
  • 現在の再送タイムアウト値(ミリ秒)。RTT(往復遅延)の揺らぎに応じてカーネルが動的に計算する。この値が異常に跳ね上がっている(例: 数千〜数万ms)場合、経路上のパケットロスが激しい証拠だ。
  • cwnd:10
  • 輻輳ウィンドウサイズ(Congestion Window)。一度にACKを待たずに送信できるセグメント数。これが極端に小さい場合、スロースタートの最中か、輻輳によってウィンドウが絞られている。
  • retrans:0/3
  • これが一番重要だ。左側は「現在の接続で再送された累計パケット数」、右側は「現在リトライ中の試行回数」。ここが不自然に増加している場合、アプリケーションのバグではなく、物理層・データリンク層、あるいはクラウドのVPCルーターレベルでの障害を疑うべきだ。

—

4. 実戦:Pythonとcurlを用いた意図的なタイマー発火と診断

百聞は一見にしかず。実際に手を動かして、ss でタイマーの挙動をキャッチしてみよう。
ここでは、あえてレスポンスを遅延させるWeb API(Python/Flask製)を用意し、クライアントからリクエストを投げて ss でその挙動を監視する。

サーバー側コード(Python / Flask)

以下のスクリプトは、意図的にレスポンスを3秒間スリープさせ、キープアライブや遅延の挙動を引き起こす。

# slow_api.py
from flask import Flask
import time

app = Flask(__name__)

@app.route('/api/v1/heavy-query', methods=['GET'])
def heavy_query():
    # 意図的に3秒間処理をブロック(DBのデッドロックや重いクエリを模倣)
    time.sleep(3.0)
    return {"status": "success", "message": "Heavy process completed."}, 200

if __name__ == '__main__':
    # 0.0.0.0のポート8080で起動
    app.run(host='0.0.0.0', port=8080)

クライアント側(curl)

別の端末から、このAPIに対してリクエストを投げる。

# 3秒間のレスポンス待ちを発生させる
$ curl -i http://<サーバーのIP>:8080/api/v1/heavy-query

この通信が走っているまさにその瞬間に、サーバー側で以下のコマンドを叩いてみよう。

# ポート8080のソケット状態を継続的に監視
$ watch -n 0.5 "ss -tin '( sport = :8080 )'"

画面には、クライアントからのリクエストを受信し、ESTAB 状態でデータ処理(スリープ)を待っている間のソケットの様子がリアルタイムに表示される。
もしここでクライアント側が途中で接続を切断(Ctrl+Cなど)した場合、サーバー側の Send-Q や Retransmit の数値がどう変化するかを観察してほしい。FINパケットのロストに伴い、FIN_WAIT状態や再送タイマーが火を噴く瞬間が生々しく確認できるはずだ。

—

5. 現場で役立つ!TCPキープアライブのチューニング手法

APIサーバーの運用で最も頭を悩ませるのが、「クライアントとサーバーの間にあるロードバランサー(ALBやNginx等)やファイアウォールが、無言のコネクションを勝手に切断してしまう」という問題だ。

デフォルトのLinuxカーネルは、TCPキープアライブを発動させるまでに2時間ものアイドル時間を必要とする。これではクラウド時代のWeb APIとしては使い物にならない。ロードバランサーが3分でセッションを忘れてしまうなら、Linux側もそれに合わせる必要がある。

以下のカーネルパラメータを /etc/sysctl.conf に設定し、sysctl -p で反映させよう。

# /etc/sysctl.conf の設定例
# アイドル状態からキープアライブプローブを送り始めるまでの時間(秒)
net.ipv4.tcp_keepalive_time = 60

# プローブを再送する間隔(秒)
net.ipv4.tcp_keepalive_intvl = 10

# 応答がない場合に、コネクション切断と見なすまでの最大再送回数
net.ipv4.tcp_keepalive_probes = 5

設定を反映させた後、実際にアプリケーション側(またはソケットオプション)でキープアライブが有効になっているかを ss で確認する。

# timer=(timer-name,expire-time,retrans) の形式でタイマー情報を出力させる
$ ss -to '( sport = :8080 )'

出力結果の中に、timer:(on,39sec,0) や timer:(keepalive,58min,0) のような表示が現れる。ここを見ることで、正しくタイマーがセットされ、カウントダウンが始まっていることが一目で確認できるのだ。

—

6. シニアからの教訓:障害対応の極意

障害現場でパニックになるエンジニアの共通点は、「目に見える現象(エラーログなど)だけに囚われ、パケットの生態系を見ようとしないこと」だ。

アプリケーションのログが「タイムアウトしました」と言っていても、それは結果に過ぎない。原因はアプリにあるのか、OSのバッファにあるのか、それとも途中のネットワーク機器のセッション枯渇にあるのか。
その境界線を鮮やかに見極めるためのメスが、今回解説した ss コマンドによるTCPタイマー分析だ。

「なぜ今、この再送タイマーが回っているのか?」
「なぜ cwnd が絞られているのか?」

この問いを常に自分に投げかけられるようになれば、どんな難解なネットワーク障害も、必ず論理的な紐解きができる。明日からの実務で、まずは ss -ti を叩く習慣をつけてみてほしい。世界の見え方が変わるはずだ。

コメント

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