
Dockerでアプリケーションを構築する場合に発生するメジャーな問題である「The PID 1 Problem」とその解決策を紹介します。
はじめに
アプリケーションをDocker化するにあたり、プロダクション用のDockerfileでは開発用のDockerfile以上にいくつか気をつける点があります。その中の一つが今回紹介する「The PID 1 Problem」の対応です。Dockerをプロダクションに適用する場合は、理解しておく必要があります。それでは、問題の内容と解決策を見ていきましょう。
The PID 1 Problemとは?
「The PID 1 Problem」とは、この記事で指摘されているDockerコンテナのPID 1に関する問題です。一般的にPID 1は「initプロセス」と呼ばれており、システムが起動した際に最初に起動するプロセスです。このinitプロセスの役割は、ゾンビプロセスの除去とサブプロセスへのシグナルの伝搬です。何の対策もせずにアプリケーションをDocker上で起動した場合、そのアプリケーションの起動プロセスがコンテナ上の最初のプロセスになってしまうため、ゾンビプロセスが残ったり、シグナルが正しく処理されないという問題が発生します。開発者として明確に困る点は、Docker化したアプリケーションが正しく停止しないことです。
tiniとは?
「tini」とは、コンテナ向けに作られたシンプルなinitプログラムです。Dockerコンテナの起動コマンドで使用することで、PID 1の本来の役割を果たし、「The PID 1 Problem」を回避できます。
解決策
それではNodeJSの簡単なDockerアプリケーションを作って、「The PID 1 Problem」を解決してみましょう。
サンプルNodeJSアプリケーションの作成
単にメッセージを返すだけのアプリケーションを作ります。
$ mkdir dockerized-app-fixed-pid1
$ cd dockerized-app-fixed-pid1/
$ yarn init -y
$ yarn add express
$ touch app.js
$ touch Dockerfile
$ touch .dockerignore
コードは以下のようにします。
app.js
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hey, dockerized app!');
});
app.listen(3000, () => {
console.log('Dockerized app is up...');
});
Dockerfile
FROM node:12.2-alpine
ENV NODE_ENV=production
WORKDIR /node
COPY package.json yarn.lock ./
RUN mkdir app && chown -R node:node .
USER node
RUN yarn install && yarn cache clean --force
WORKDIR /node/app
COPY --chown=node:node . .
EXPOSE 3000
CMD ["node", "app.js"]
.dockerignore
node_modules/
試しにビルドして実行してみましょう。
$ docker build -t dockerized-app .
$ docker run -p 3000:3000 dockerized-app
Dockerized app is up...
動作確認してから停止します。
$ curl localhost:3000/
Hey, dockerized app!
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3b8c29a79c6 dockerized-app "node app.js" About a minute ago Up 59 seconds 0.0.0.0:3000->3000/tcp quizzical_heyrovsky
$ docker top a3b
PID USER TIME COMMAND
55419 1000 0:00 node app.js
$ docker stop a3b
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a3b8c29a79c6 dockerized-app "node app.js" About a minute ago Exited (137) 4 seconds ago quizzical_heyrovsky
$ docker rm a3b
注目点として、このコンテナはControl+Cやdocker stopで停止しようとしてもすぐに停止することはなく、正しい停止処理で失敗して強制終了する形で停止しています。
補足として、NodeJSにおけるExit Codeの見方ですが、公式サイトのExit Codeのページを見ると書かれています。つまり、NodeJSのSignal ExitはUnixのSignal Numberに128を足した数字になります。上記の場合は、Exit Codeは「137」なので、128+9ということであり、「9」は「SIGKILL」のシグナルを示しているので、強制終了されていることが分かります。
--initによる対応(一時的な起動)
Dockerの「--init」オプションと使うことでDockerに同封されているinitプロセスを有効にできます。開発環境など一時的にこの問題を解決したい場合に適しています。
それではこのオプションを付けて起動します。
$ docker run --init -p 3000:3000 dockerized-app
Dockerized app is up...
停止します。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAM
ES
200687bc9992 dockerized-app "node app.js" 38 seconds ago Up 37 seconds 0.0.0.0:3000->3000/tcp vig
ilant_hofstadter
$ docker top 200
PID USER TIME COMMAND
55642 1000 0:00 /dev/init -- node app.js
55677 1000 0:00 node app.js
$ docker stop 200
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
200687bc9992 dockerized-app "node app.js" 54 seconds ago Exited (143) 7 seconds ago vigilant_hofstadter
$ docker rm 200
停止はすばやく実行され、Exit Code「143」(128+15なので「SIGTERM」)で正常に終了しています。
tiniによる対応(永続的な起動)
「tini」をDockerfile上に追加することでプロダクション用にこの問題を解決できます。今回はtiniの「-e」オプションを使い、Exit Code「143」を「0」にマップすることにします。
Dockerfile
FROM node:12.2-alpine
ENV NODE_ENV=production
RUN apk add --no-cache tini
WORKDIR /node
COPY package.json yarn.lock ./
RUN mkdir app && chown -R node:node .
USER node
RUN yarn install && yarn cache clean --force
WORKDIR /node/app
COPY --chown=node:node . .
EXPOSE 3000
ENTRYPOINT ["/sbin/tini", "-e", "143", "--"]
CMD ["node", "app.js"]
それでは変更したDockerfileでビルドして起動します。
$ docker build -t dockerized-app .
$ docker run -p 3000:3000 dockerized-app
Dockerized app is up...
停止します。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
c7ebb0d93ce7 dockerized-app "/sbin/tini -e 143 -…" 10 seconds ago Up 9 seconds 0.0.0.0:3000->3000/tcp
cocky_haslett
$ docker top c7e
PID USER TIME COMMAND
55904 1000 0:00 /sbin/tini -e 143 -- node app.js
55938 1000 0:00 node app.js
$ docker stop c7e
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c7ebb0d93ce7 dockerized-app "/sbin/tini -e 143 -…" 36 seconds ago Exited (0) 5 seconds ago cocky_haslett
Exit Code「0」で想定通りに終了しています。
最後に
いかがでしたか?これでDockerコンテナのメジャーな問題である「The PID 1 Problem」を解決できるようになったと思います。プロダクションでDockerを適用する際は注意しましょう。それでは。
環境
- Docker: 18.09.2
- NodeJS: v12.2.0
- Yarn: 1.16.0