基本的な実装の仕方と、実装した場合 / しなかった場合、で実際にどういう動作をするか〜、について書きます。
Linux, Node.js 12.13.0, での話だけをします。
Graceful shutdown ?
Express (Node.js) に限りませんが、Web サーバーを停止する際、クライアントから接続中のリクエスト (リクエスト受付してまだレスポンスしていない接続) はどうなるでしょうか?
Graceful shutdown とは一般的に以下の停止を指します。
- 停止指示後に、新しい接続を受付しない
- 残った処理中の接続が完了するのを待ってから、プロセスを安全に停止する
SIGNALs
そもそも Web サーバープロセスはどうやって停止するかというと、 SIGNAL を用いて停止します。
具体的には下表の通り、コマンド等によって SIGNAL を送信できます。
SIGNAL | kill command | Linux Terminal | Kubernetes | 実装依存 | 説明 |
---|---|---|---|---|---|
SIGHUP | kill -SIGHUP {pid} | -- | -- | Yes | プロセスが端末から切断された際のシグナル |
SIGINT | kill -SIGINT {pid} | CTRL + C | -- | Yes | 割り込み |
SIGKILL | kill -SIGKILL {pid} | -- | SIGTERM 送信から30秒後に送信 | NO | プロセスの実装に依存しないOSからの強制終了 |
SIGTERM | kill {pid} | -- | 最初に送信 | Yes | プロセスの終了 |
最近では、プロセスを Docker コンテナ化して、 Kubernetes 等のプラットフォーム上で動かす事が多いと思います。
例えば Kubernetes では、プロセス (Docker コンテナ) を終了する際は、まず
SIGTERM
が送信され、30秒待ってもプロセスが終了しない場合、最終的にSIGKILL
が送信されます。
実装例
URL /sleep
と、 SIGNAL SIGTERM
での Graceful shutdown を Express で実装しました。
const express = require('express');
const SLEEP_MSEC = 30 * 1000;
const app = express();
app.get('/sleep', (req, res) => {
setTimeout(() => res.send('OK'), SLEEP_MSEC);
});
const server = app.listen(3000, () => console.log('Example app listening on port 3000!'));
process.on('SIGTERM', () => {
server.close(() => {
console.log('Process terminated.')
})
});
実行します。
$ node index.js
Example app listening on port 3000!
ブラウザで以下の URL にアクセスすると、30秒待たされた後 OK
と表示されます。
[未実装で] 実際に停止してみる
まずは 未実装の状態で どう動作するか検証します。
Express サーバーを起動した後、ブラウザで http://localhost:3000/sleep にアクセスします。
まだリクエストの処理中に SIGNAL を送信します。
(1) SIGHUP
シグナルを送信でプロセスが即座に停止。
kill -SIGHUP {pid}
Hangup: 1
(2) SIGINT
シグナルを送信でプロセスが即座に停止。
kill -SIGINT {pid}
(terminal 上の出力はなし)
(3) SIGKILL
シグナルを送信でプロセスが即座に停止。
kill -SIGKILL {pid}
Killed: 9
(4) SIGTERM
シグナルを送信でプロセスが即座に停止。
kill {pid}
Terminated: 15
[SIGTERM 実装で] 実際に停止してみる
実装していない
SIGTERM
以外のシグナルは省略。
(5) SIGTERM
ブラウザで http://localhost:3000/sleep にアクセスする。
応答がすぐには帰ってこず、ローディング状態になります。
シグナルを送信します。
kill {pid}
プロセスは実行されたまま、無反応。
(terminal 上の出力はなし)
ブラウザから新しいタブを開いて http://localhost:3000/sleep に追加でアクセスする。
TCP 接続が拒否され、ブラウザ上に「正常に接続できませんでした」と表示される。
1度目のブラウザからのリクエストが 30 秒後に正常に応答が返り、プロセスが終了する。
Process terminated.