docker stop や docker-compose downが遅いとき

docker-compose downがなんか遅いなーと思っていたら、entrypoint.sh の書き方がよくなかった。 exec your-command としないと、 SIGTERM シグナルがプロセスに届かず、タイムアウト(10秒)を待って SIGKILL される。

確認

例としてflaskアプリをgunicornで動かしてみる。

ファイルの準備

Dockerfile

from python:3.9-slim
RUN pip install flask gunicorn
COPY app.py /

app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def konnnichiha():
        return "<p>Konnnichiha!</p>"

entrypoint.sh

#!/bin/bash
gunicorn -w 1 -b 0.0.0.0:5000 app:app

entrypoint-exec.sh

#!/bin/bash
exec gunicorn -w 1 -b 0.0.0.0:5000 app:app

テスト

コンテナを実行する。

$ chmod 755 entrypoint*
$ docker build -t flask-test .


$ docker run -d --name test-flask\
   -v $(pwd)/entrypoint.sh:/entrypoint.sh\
   --entrypoint '/entrypoint.sh'\
   test-flask
$ docker run -d --name test-flask-exec\
   -v $(pwd)/entrypoint-exec.sh:/entrypoint.sh\
   --entrypoint '/entrypoint.sh'\
   test-flask

exec版はentrypoint.shがいなくなっている。

$ docker top test-flask
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                935376              935355              0                   23:26               ?                   00:00:00            /bin/bash /entrypoint.sh
root                935446              935376              0                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app
root                935448              935446              0                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app

$ docker top test-flask-exec
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                935610              935591              2                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app
root                935658              935610              1                   23:26               ?                   00:00:00            /usr/local/bin/python /usr/local/bin/gunicorn -w 1 -b 0.0.0.0:5000 app:app

終了してみると、execなしはタイムアウト待機分の10秒余計にかかる。

$ time docker stop test-flask
test-flask

real    0m10.228s
user    0m0.019s
sys     0m0.019s

$ time docker stop test-flask-exec
test-flask-exec

real    0m0.323s
user    0m0.020s
sys     0m0.020s

execありはシグナルを受け取ってgracefulに終了している。

$ docker logs test-flask
[2021-05-20 03:26:16 +0000] [7] [INFO] Starting gunicorn 20.1.0
[2021-05-20 03:26:16 +0000] [7] [INFO] Listening at: http://0.0.0.0:5000 (7)
[2021-05-20 03:26:16 +0000] [7] [INFO] Using worker: sync
[2021-05-20 03:26:16 +0000] [8] [INFO] Booting worker with pid: 8

$ docker logs test-flask-exec
[2021-05-20 03:26:22 +0000] [1] [INFO] Starting gunicorn 20.1.0
[2021-05-20 03:26:22 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
[2021-05-20 03:26:22 +0000] [1] [INFO] Using worker: sync
[2021-05-20 03:26:22 +0000] [8] [INFO] Booting worker with pid: 8
[2021-05-20 03:27:02 +0000] [1] [INFO] Handling signal: term
[2021-05-20 03:27:02 +0000] [8] [INFO] Worker exiting (pid: 8)
[2021-05-20 03:27:02 +0000] [1] [INFO] Shutting down: Master

掃除。

$ docker rm test-flask test-flask-exec

しくみ

docker stopやdocker-compose downは、SIGTERM を送ってデフォルトの10秒 (ref https://docs.docker.com/engine/reference/commandline/stop/) たっても終了しない場合は、 SIGKILL が送信される。

PID 1が bash entrypoint.sh だと、シグナルが子プロセスに届かない。 exec commandcommand を実行すると command が PID 1になるので、シグナルを受け取れる。

exec commandcommand がシェルを置き換える。man bashより:

       exec [-cl] [-a name] [command [arguments]]
              If command is specified, it replaces the shell.  No new process  is
              created.  The arguments become the arguments to command.  (略)

なお、シグナルを受け取った後でシェルで他の処理をしたいときは、exec ではなく trap する

ref: https://docs.docker.com/engine/reference/builder/#entrypoint

他にもコンテナの終了が遅いときは、CMD/ENTRYPOINT の書き方とか、そもそも SIGTERM では終了しないプロセスとか、いくつか原因になりそうな事がある。 ref: https://docs.docker.com/compose/faq/#why-do-my-services-take-10-seconds-to-recreate-or-stop