docker stopをしてもNode.jsは終了しない
Dockerで起動したプロセスはPID 1として動くが、Node.jsはPID 1として動く場合、docker stopでSIGTERMを送っても終了しない。
サンプルのExpressサーバーをDockerで動かしてみる。
mkdir nodetest
cd nodetest
cat << 'EOF' > Dockerfile
FROM node:24
WORKDIR /app
RUN npm install express
COPY . .
ENTRYPOINT ["node", "server.js"]
EOF
cat << 'EOF' > server.js
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!\n');
});
app.listen(port, () => {
console.log(`listening at http://localhost:${port}`);
});
EOF
docker build -t nodetest .
docker run --name nodetest -d --rm -p3000:3000 nodetest
curl localhost:3000
docker stop nodetest
docker stopをしても10秒間待たされる。これはDockerがstopできず、デフォルトのタイムアウト10秒が過ぎたため、DockerがSIGKILL(docker kill)を送信して強制終了しているため。
DockerでなければSIGTERMで終了できる
Dockerではなく普通にNode.jsを起動した場合、問題なく終了できる。
cd ..
mkdir nodetest2
cd nodetest2
cp ../nodetest/server.js .
npm install express
node server.js
同じコードでNode.jsを起動しても、以下のようにSIGTERMで即座に終了できる。
kill -s TERM $(ps -o pid,args | grep 'node server.j[s]' | awk '{print $1}')
Dockerでも終了するようにgraceful shutdownのコードを実装する
Docker用の対処として、graceful shutdownのコードを実装する。
cd ../nodetest
vim server.js
server.jsのコードを以下に書き換える。
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!\n');
});
const server = app.listen(port, () => {
console.log(`listening at http://localhost:${port}`);
});
const gracefulShutdown = (signal) => {
console.log(`${signal} received.`);
server.close((err) => {
if (err) process.exit(1);
process.exit(0);
});
server.closeIdleConnections();
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
再度docker buildして起動する。コンソールを確認しやすいように-dはつけない。
docker build -t nodetest .
docker run --name nodetest --rm -p3000:3000 nodetest
別のターミナルからdocker stopしたり、Ctrl CでSIGINTを送ると、即座に終了することがわかる。