深夜3時、静まり返ったNOCのフロアに、監視モニターの警告音が冷たく響き渡る。
「またか……。踏み台サーバーから外部APIへのコネクションが枯渇している」
Webアプリケーションのパフォーマンス低下や、マイクロサービス間の通信エラー。こうしたトラブルに直面したとき、君たちは真っ先に何を疑うだろうか?アプリケーションのログ、それともロードバランサーのメトリクス?
もちろんそれらも重要だ。だが、最終的にパケットが流れるOSのトランスポート層、その「足元」を見ずしてネットワークの真実には辿り着けない。
今回は、モダンな監視ツール全盛の今であっても、トラブルシューティングの現場で絶対に外すことのできないレガシーにして最強のツール、netstatコマンドについて語ろう。
—
1. なぜ今、netstatなのか?(レガシーツールの真価)
Linuxの世界では、後継であるssコマンドへの移行が進んで久しい。だが、長年数々の修羅場をくぐってきたエンジニアたちが、今でも脊髄反射のように叩くのはnetstatだ。
なぜなら、古くから存在するどのOS(Linuxはもちろん、AIXやSolaris、果てはWindowsまで)にも標準で備わっており、環境を選ばない普遍性があるからだ。
ネットワーク統計情報の収集において、netstatは単なる「接続確認ツール」ではない。カーネルが管理するソケットの生死、ルーティングの迷い、そしてインターフェースが泣き叫ぶパケットドロップの歴史を暴く、いわば「OSの健康診断書」なのだ。
RFCに裏打ちされたTCPの状態遷移
netstatが吐き出す出力の根底には、IETFが定めるTCPの仕様、すなわち RFC 793(Transmission Control Protocol)で定義された状態機械(State Machine)が存在する。
クライアントが SYN を送り、サーバーが SYN-ACK で返し、ESTABLISHED へと至るハンドシェイクのシーケンス。あるいは、コネクション切断時の TIME_WAIT や CLOSE_WAIT の滞留。
現場でよくある「APIサーバーが突然応答しなくなった」というインシデントの多くは、コードのバグによってソケットが適切に解放されず、CLOSE_WAIT が無限に増殖しているケースがほとんどだ。これを暴くために、僕らはnetstatの扉を叩く。
—
2. 実務で使うnetstat:基本コマンドとパラメーターの解剖
まずは、実務の現場で秒速で打つべき基本コマンドと、そのオプションの意味を整理しよう。
余計な名前解決(DNS逆引き)を省き、数千行の出力から真実を炙り出すための黄金の組み合わせだ。
# 全てのTCP/UDPソケットを、数値表現(IPとポート)かつプロセス名付きで一覧表示する
netstat -tulpn
このコマンドで使用している主要なパラメーターの意味は以下の通りだ。実務ではこの組み合わせを体に覚え込ませておいてほしい。
-t(--tcp): TCPコネクションのみを対象にする。-u(--udp): UDPソケットのみを対象にする。-l(--listening): 接続待ち(Listen状態)のサーバーソケットを表示する。-p(--program): そのソケットを開いているPID(プロセスID)とプログラム名を併記する(要root権限)。-n(--numeric): IPアドレスやポート番号を名前解決せず、数値のまま高速に表示する(DNSの逆引き遅延を防ぐため、現場では必須)。
—
3. 現場のユースケース:トラブルシューティングの実践
では、実際のWeb API開発やインフラ運用において、どのようにnetstatを活用するのか。具体的なシナリオを見ていこう。
シナリオA:APIクライアントからの「接続拒否(Connection Refused)」
自社のバックエンドAPIサーバーに対して、フロントエンドのNode.jsやPythonアプリからリクエストを送ったところ、突然 ECONNREFUSED が返るようになった。
サーバーが死んでいるのか、あるいはポートが空いていないのかを確かめる。
# 特定のポート(例: 8080番)でプロセスが正しくListenしているか確認する
netstat -an | grep 8080
もしここで何も出力されなければ、アプリケーションプロセスがクラッシュしているか、起動に失敗している。
逆に、LISTEN 状態であっても、接続元が限られている場合は注意が必要だ。ローカルループバック (127.0.0.1) だけでListenしていて、Dockerコンテナや別ホストからの通信を弾いているケース(バインドミスの典型)を、このコマンドが一発で見抜いてくれる。
シナリオB:TIME_WAIT 嵐とエフェメラルポートの枯渇
大量のHTTPリクエストを外部API(Web APIや外部SaaS)へ高速に投げ続けるバッチ処理を実装した際、突然処理が重くなり、次のようなエラーに直面したことはないだろうか?
「Cannot assign requested address」
これは、クライアント側から接続を切断した際に発生する TIME_WAIT 状態のソケットがOS内に溢れ返り、外向きの通信に使えるエフェメラルポート(一時ポート)が完全に枯渇している状態だ。現場では「TIME_WAIT 祭り」と呼んでいる。
現在のコネクションの偏りを統計的に確認するには、以下のコマンドが有効だ。
# TCPコネクションの状態ごとの件数を集計して降順でソートする
netstat -an | grep tcp | awk '{print $6}' | sort | uniq -c | sort -nr
出力例:
1245 ESTABLISHED
892 TIME_WAIT
12 LISTEN
4 CLOSE_WAIT
この状態を見たら、アプリケーション側の設計を見直す必要がある。
HTTP/1.1であれば Keep-Alive を有効にしてコネクションを使い回すべきだし、Pythonの requests や Node.js の axios を使っているなら、セッション(コネクションプーリング)を張る実装に修正しなければならない。
以下に、Pythonの requests ライブラリでコネクションプールを適切に維持し、無駄な TIME_WAIT を発生させない実装例を示す。
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_robust_api_client():
"""
コネクションプールとリトライロジックを備えたセッションを生成する。
無駄なソケットの生成・破棄を防ぎ、TIME_WAITの枯渇を防ぐ実用的なパターン。
"""
session = requests.Session()
# リトライ戦略の設定(500番台エラーやネットワーク一時断に対応)
retries = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504]
)
# HTTPAdapterでプールサイズ(pool_maxsize)を明示的に定義
adapter = HTTPAdapter(
pool_maxsize=50, # 同時接続プールの最大数
pool_block=True, # プールが枯渇した際にブロックして待機する
max_retries=retries
)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
# 使用例
if __name__ == "__main__":
client = create_robust_api_client()
try:
# 同一セッションを使い回すことでTCPハンドシェイクのオーバーヘッドを削減
response = client.get("https://api.example.com/v1/data", timeout=5.0)
response.raise_for_status()
print(f"API Response: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"API通信エラーが発生しました: {e}", file=sys.stderr)
finally:
client.close()
—
4. インターフェース統計情報の深掘り(パケットロッジの検知)
ソケットの状態だけでなく、netstatはネットワークカード(NIC)レベルの統計情報も教えてくれる。ハードウェアの故障、ケーブルの劣化、あるいはクラウド環境での帯域制限(スロットリング)に起因するパケットロスを見抜くには、-i オプションを使う。
# インターフェースごとの送受信パケット数、エラー、ドロップ数を一覧表示する
netstat -i
注目すべきは、出力結果の中にある RX-ERR, TX-ERR(受信/送信エラー)や、RX-DROP, TX-DROP(ドロップ数)の数値だ。
もし、正常に稼働しているはずのサーバーで RX-DROP が猛烈な勢いでカウントアップされていたら、それはOSのカーネルバッファ(受信キュー)が溢れ、パケットを処理しきれずに捨てている証拠だ。
このような現場に直面した場合、一時的な対策としてカーネルパラメータ(/etc/sysctl.conf)のチューニングが必要になる。
# /etc/sysctl.conf の設定例
# 受信キューの最大バックログ(未処理の接続要求を保持するキュー)を拡大する
net.core.somaxconn = 1024
# TIME_WAIT ソケットの早期再利用を有効にする(※環境により慎重に判断すること)
net.ipv4.tcp_tw_reuse = 1
設定を変更した後は、以下のコマンドで即座に反映させることを忘れないでほしい。
# sysctlの設定を即時反映させる
sudo sysctl -p
—
5. シニアエンジニアからの教訓
ネットワークのトラブルシューティングにおいて、最も恐ろしいのは「見えないものを見ようとせず、思い込みで設定ファイルを書き換えること」だ。
「APIが繋がらない」「重い」と感じたとき、感情的にコードをいじる前に、まずは落ち着いて netstat -tulpn を叩いてほしい。
今、何個のソケットがどこを向いて生きているのか。どのプロセスが通信を握りしめているのか。
OSは嘘をつかない。すべての真実は、パケットとソケットの統計情報という名のログに刻まれている。
レガシーと言われようとも、netstatはいつの時代も、泥臭く現場を支えるエンジニアの最も頼れる相棒なのだ。次回の障害対応の夜も、こいつの出番がないことを祈りつつ、万全の準備をしておこう。
コメント