概要
[これ]の日本語版のようなもの
[これ]: https://gist.github.com/hiroaki-yamamoto/4e27167dd3cb74700ce86e678ff987cc
問題
複数のコマンドを扱うDockerを使うとき、こんな感じのスクリプトになりませんか?
#!/bin/sh -e
pip install --upgrade poetry
poetry config settings.virtualenvs.create false
poetry install
python ./manage.py runserver 0.0.0.0:8000
from python:3.6
env PYTHONUNBUFFERED 1
run mkdir /code
workdir /code
version: '3'
services:
db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: fingine
POSTGRES_USER: fingine
POSTGRES_DB: fingine
backend:
build:
context: ./docker
dockerfile: backend.dockerfile
command: bash -c ./run.sh
stop_signal: SIGINT
env_file: ./docker/dev.env
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
これ、docker-compose up
した後にdocker-compose stop
すると、fingine_backend_1 exited with code 137
と、エラーコード137、つまりSIGKILL
が返された上で終了する。つまり、終了まで10秒ほど待たされる上に強制終了を食らう。
で、調べるとやれ、メモリが足りないだのtrap
コマンドを仕掛けろだのと的外れな回答ばかりで嫌気が差した。だから書いてやった。
解決法
注目すべき点はexec
ビルトインユーティリティコマンドにあります。通常、shell芸で書かれたコマンドは新しいプロセスを生成してそこでコマンドを実行します。この新しく生成されたプロセスでは、後述する問題によってシグナルを受信することができません。
しかし、exec
にコマンドライン引数としてコマンドとその引数を指定すると、コマンドのプロセスを生成せずに、シェルのプロセスとそのコマンドのプロセスを置き換えます。つまり、先程のrun.sh
を次のように書き換えると解決するのでは・・・と考えますよね?
#!/bin/sh -e
pip install --upgrade poetry
poetry config settings.virtualenvs.create false
poetry install
exec python ./manage.py runserver 0.0.0.0:8000 # <-- これ。
実はこれだと不十分っす。 というのも、Dockerの終了シグナルはコンテナ内のプロセスIDが1のプロセスに対してのみ発火します。さあ、docker-compose.yml
を見てみましょう。
version: '3'
services:
db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: fingine
POSTGRES_USER: fingine
POSTGRES_DB: fingine
backend:
build:
context: ./docker
dockerfile: backend.dockerfile
command: bash -c ./run.sh # <-- これ
stop_signal: SIGINT
env_file: ./docker/dev.env
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
要するに、bash
コマンドをrun.sh
を引数にとって実行している、つまり、run.shの実行には新しいプロセスが生成される、と。 そういうことですね。
というわけで、このdocker-compose.yml
に記述されているrun.sh
の呼び出しをbash
ではなく、exec
を使って呼び出されるようにする必要があります。
幸いなことに、dockerには手軽にexec
を使ったコマンドを記述する方法があり、今回はこれを用いることにします。
version: '3'
services:
db:
image: postgres:alpine
environment:
POSTGRES_PASSWORD: fingine
POSTGRES_USER: fingine
POSTGRES_DB: fingine
backend:
build:
context: ./docker
dockerfile: backend.dockerfile
command: ['./run.sh'] # <-- これ。配列として書き直すとexecに続くコマンドとして認識される。
stop_signal: SIGINT
env_file: ./docker/dev.env
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
さあこれでdocker-compose up
してdocker-compose stop
してみましょう。コンテナを10秒も待つことなく、fingine_backend_1 exited with code 0
、つまりコード0、正常終了させる事ができます。素晴らしい!!
最後に
Schlawackさんの記事によって筆者のdockerの問題が解決した事に謝辞を申し上げます。
参考記事
Hynek Schlawack, Why Your Dockerized Application Isn’t Receiving Signals, 19 June 2017