使用中ポートを特定する|ss・lsof・fuserの使い分け

使用中ポートを特定する|ss・lsof・fuserの使い分け

開発サーバーを立ち上げようとして、Address already in use に出迎えられる日があります。

前に落とし損ねたプロセスが、まだそのポートを握ったまま生きているパターンですね。

犯人を特定して止めるまでの手順は、実はコマンド2つで足ります。

今回は ss と lsof と fuser の使い分けを、実際に詰まった状態を再現しながら整理しました。

検証環境は Ubuntu 22.04.5・iproute2 5.15.0・lsof 4.93.2 です。

まずは症状の再現から

説明用に、同じポートで2回サーバーを起動してみます。

$ python3 -m http.server 8099 &
$ python3 -m http.server 8099
  File "/usr/lib/python3.10/socketserver.py", line 466, in server_bind
    self.socket.bind(self.server_address)
OSError: [Errno 98] Address already in use

Errno 98 は、要求したアドレスとポートの組がすでに使われているという意味です。

ここで再起動やリブートに走る前に、誰が握っているのかを1コマンドで確かめるのが近道になります。

ss なら listen 中のポートを一望できる

いまの Linux で第一候補になるのは ss です。

netstat の後継にあたるコマンドで、iproute2 パッケージに入っています。

$ ss -tulnp
Netid State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp   LISTEN 0      5            0.0.0.0:8099      0.0.0.0:*   users:(("python3",pid=7,fd=3))
tcp   LISTEN 0      5            0.0.0.0:1080      0.0.0.0:*   users:(("socat",pid=4,fd=5))

オプションは4文字ぶんの意味があり、それぞれ t が TCP、u が UDP、l が listen 中のみ、n が名前解決なしです。

p を付けるとプロセス名と PID まで出るので、この1行で犯人がわかります

n を外すとポート番号がサービス名に化けるため、番号で追いたいときは付けたままにしましょう。

$ ss -tlp
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN 0      5            0.0.0.0:socks      0.0.0.0:*   users:(("socat",pid=4,fd=5))

1080 番が socks と表示されてしまい、探している番号と目で突き合わせにくくなりました。

ポートを絞り込むフィルタ構文

行数が増えてくると、grep より ss 自身のフィルタのほうが正確です。

$ ss -ltnp "sport = :8099"
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      5            0.0.0.0:8099      0.0.0.0:*   users:(("python3",pid=7,fd=3))

sport は送信元ポート、つまり listen 側のポートを指します。

接続先を絞りたい場合は dport に置き換えれば同じように書けますね。

状態で絞る指定も用意されていて、確立済みの接続だけを見たいときに便利でした。

$ ss -tn state established

lsof はファイルディスクリプタ側から見る

lsof は「開いているファイル」を列挙するコマンドで、ソケットもファイルの一種として扱われます。

$ lsof -i :8099
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python3   7 ubuntu 3u  IPv4  28634      0t0  TCP *:8099 (LISTEN)

出力が横に整っているぶん、ss より読みやすいと感じる人も多いはずです。

ただし lsof は標準で入っていない環境がそれなりにあるので、そこだけ注意しておきます。

スクリプトに組み込むなら、PID だけを返す -t が圧倒的に扱いやすいでしょう。

$ lsof -t -i :8099
7

PID を取り出して止めるまで

ss から PID だけ抜くなら、grep の -o と -P を組み合わせるのが手軽です。

$ PID=$(ss -ltnp "sport = :8099" | grep -oP 'pid=\K[0-9]+')
$ echo $PID
7

止める前に、本当に消していいプロセスかを ps で確認しておきます。

$ ps -p $PID -o pid,user,etime,cmd
    PID USER     ELAPSED CMD
      7 ubuntu     00:02 python3 -m http.server 8099

起動からの経過時間とコマンドラインが見えるので、消し間違いをかなり防げます。

確認してから kill、が事故を減らす唯一のコツでしょうか。

$ kill $PID
$ ss -ltn "sport = :8099" | tail -n +2 | wc -l
0

行数が 0 になれば、ポートは解放されています。

シグナルの指定や強制終了まわりは、こちらの記事が詳しいです。

ps・kill入門|動いているプロセスを調べて止める基本

fuser という三番目の選択肢

PID だけ即座に知りたいなら fuser がいちばん短く書けます。

$ fuser 8099/tcp
8099/tcp:                7

psmisc パッケージに含まれるコマンドなので、コンテナの最小イメージだと未導入のことも多いです。

使い分けの目安をひとことで言うなら、一覧を眺めたいなら ss、1つを深く見たいなら lsof、PID だけなら fuser

迷ったら ss を覚えておけば、たいていのディストリビューションでそのまま通ります。

ポートの状態が変わる瞬間を見張りたいときは、watch と組み合わせると手が空きます。

watchコマンド入門|繰り返し実行で変化を見張る

最後に

ポートの奪い合いは、原因がはっきりしているわりに毎回あわてる類のトラブルです。

ss で見て、ps で確かめて、kill する。

この3手だけ体に入れておけば、次に Errno 98 を見ても落ち着いて対処できるはず

以上です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA