開発サーバーを立ち上げようとして、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 になれば、ポートは解放されています。
シグナルの指定や強制終了まわりは、こちらの記事が詳しいです。
fuser という三番目の選択肢
PID だけ即座に知りたいなら fuser がいちばん短く書けます。
$ fuser 8099/tcp
8099/tcp: 7
psmisc パッケージに含まれるコマンドなので、コンテナの最小イメージだと未導入のことも多いです。
使い分けの目安をひとことで言うなら、一覧を眺めたいなら ss、1つを深く見たいなら lsof、PID だけなら fuser。
迷ったら ss を覚えておけば、たいていのディストリビューションでそのまま通ります。
ポートの状態が変わる瞬間を見張りたいときは、watch と組み合わせると手が空きます。
最後に
ポートの奪い合いは、原因がはっきりしているわりに毎回あわてる類のトラブルです。
ss で見て、ps で確かめて、kill する。
この3手だけ体に入れておけば、次に Errno 98 を見ても落ち着いて対処できるはず。
以上です。











コメントを残す