markdown

はじめに (対象読者・この記事でわかること)

この記事は「Node.js で Web サービスを開発・運用しているが、プロセスが意図せず残ってしまいポート競争やメモリ圧迫が起きる」という悩みを持つエンジニア向けです。
本記事を読むと、kill/pkill の基本から PM2・systemd による本番運用まで、プロセスを「見つけて」「止めて」「再発防止する」一連の手法が身につきます。開発環境でも本番サーバーでも、同じトラブルを二度と起こさないための実践的なテクニックをお伝えします。

前提知識

  • Linux/macOS のターミナルで ps, grep, kill コマンドを使ったことがある
  • Node.js で HTTP サーバを書いた経験(npm startnode app.js を実行したことがある)
  • package.json やポート番号の概念をある程度理解している

なぜ Node プロセスが「ゾンビ化」するのか

Node.js はシングルスレッド イベントループのため、コード上で明示的に server.close() しない限り、Ctrl+C を押してもすぐに終了しないことがあります。さらに:

  • 開発中にターミナルを閉じてしまった
  • 本番で forever/PM2 を使わず nohup node … & で起動した
  • プロセスが子孫を fork してデタッチ化している

これらが原因で、ポート 3000 や 8080 が「既に使われている」と怒られ、新しいプロセスが立ち上がらないという現象に遭遇します。以下では「見つける→止める→再発防止する」を段階的に説明します。

プロセスを見つけて確実に停止する実践ガイド

Step1: プロセスを特定する(ps, lsof, ss コマンド)

  1. ポート番号で調べる(8080 番を例に)
    bash sudo lsof -i :8080 -t # 出力例 12345 -t を付けると PID のみが出力され、後続の処理に連結しやすくなります。

  2. プロセス名で絞る
    bash ps -ef | grep node # kousukei 12345 107 0 09:30 pts/0 00:00:01 node app.js

  3. さらに確実に親子関係を見たい場合
    bash pstree -p 12345

Step2: 安全に終了させる(kill, fkill, PM2, systemd)

2-A kill / killall / pkill でシグナルを送る

  • まずは通常の終了(SIGTERM)を試す
    bash kill -15 12345
  • 10 秒程度待っても終了しない場合、強制終了(SIGKILL)
    bash kill -9 12345
  • プロセス名で一括削除したい場合
    bash pkill -f "node app.js"

2-B 対話的に選んで一発 kill(fkill-cli)

npm モジュールの fkill を使えば、TUI でプロセスを選んで即死できます。

Bash
npm i -g fkill-cli fkill

矢印キーでプロセスを選び Enter を押すだけで、内部で適切なシグナルを送ってくれます。

2-C PM2 を使っている場合

PM2 は守護プロセスなので、単に kill しても再度復活することがあります。

Bash
pm2 list # アプリ名確認 pm2 stop app_name # 停止 pm2 delete app_name # リストからも削除

pm2 kill と打つと PM2 自身も含めすべて終了します。

2-D systemd service として動かしている場合

Bash
sudo systemctl stop myapp.service sudo systemctl disable myapp.service # 自動起動も外す

stop のみでは再起動時に復活するので、必要に応じて disable も忘れずに。

ハマった点とトラブルシューティング

  1. kill -9 してもプロセスが残る
    → 子プロセスがデタッチしている可能性。
    pkill -P 12345 で子を探し、再帰的に kill するか、cgroup や systemd スライスを使って一括で潰す。

  2. 「ポートが使用中」が解消しない
    → TCP の TIME_WAIT 状態を誤認している場合がある。
    ss -tan | grep 8080 で状態を確認。実プロセスがいなければ数十秒待てば自動的に開放される。

  3. PM2 でクラスタモードにした際、ワーカだけが残る
    pm2 reloadLogs のあと pm2 stop ecosystem.config.js で設定ファイルごと止める。
    ワーカが孤児化した場合は pm2 delete all で全消去後、再度 pm2 start する。

再発防止のための設定テンプレート

開発環境
- nodemon --signal SIGTERM を使い、Ctrl+C で即終了できるようにする
- package.json の scripts に "dev": "nodemon app.js" を明記

本番環境
- PM2 の ecosystem ファイルで kill_timeout : 5000 を設定し、graceful shutdown を実装
- systemd なら TimeoutStopSec=10 以下にし、ExecStop で node -e "process.kill(process.pid, 'SIGTERM')" を呼ぶ

どちらもログローテーションと自動起動を設定しておけば、サーバ再起動後もゾンビ化しにくくなります。

まとめ

本記事では、Node.js プロセスが意図せず残ってしまうメカニズムと、それを「見つけて」「止めて」「再発防止する」ための手法を紹介しました。

  • lsof -i :ポート で PID を特定し、kill -15 → -9 で段階的に終了
  • PM2/systemd を使っている場合は、各ツールの停止コマンドを正しく使う
  • プロセスの孤児化を防ぐため、graceful shutdown と再帰的 kill を実装しておく

これで開発中のポート競争や、本番サーバのメモリ圧迫トラブルを大幅に減らせるはずです。
次回は「Zero Downtime 再起動を実現する Node.js アプリの作り方」について掘り下げていきます。

参考資料