Help us understand the problem. What is going on with this article?

Docker Containerの終了時にExit Code 137 (SIGKILL)が出る時の対処法

概要

これの日本語版のようなもの

問題

複数のコマンドを扱うDockerを使うとき、こんな感じのスクリプトになりませんか?

run.sh
#!/bin/sh -e

pip install --upgrade poetry
poetry config settings.virtualenvs.create false
poetry install
python ./manage.py runserver 0.0.0.0:8000
backend.dockerfile
from python:3.6
env PYTHONUNBUFFERED 1
run mkdir /code
workdir /code
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

これ、docker-compose upした後にdocker-compose stopすると、fingine_backend_1 exited with code 137と、エラーコード137、つまりSIGKILLが返された上で終了する。つまり、終了まで10秒ほど待たされる上に強制終了を食らう

で、調べるとやれ、メモリが足りないだのtrapコマンドを仕掛けろだのと的外れな回答ばかりで嫌気が差した。だから書いてやった。

解決法

注目すべき点はexecビルトインユーティリティコマンドにあります。通常、shell芸で書かれたコマンドは新しいプロセスを生成してそこでコマンドを実行します。この新しく生成されたプロセスでは、後述する問題によってシグナルを受信することができません。

しかし、execにコマンドライン引数としてコマンドとその引数を指定すると、コマンドのプロセスを生成せずに、シェルのプロセスとそのコマンドのプロセスを置き換えます。つまり、先程のrun.shを次のように書き換えると解決するのでは・・・と考えますよね?

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を見てみましょう。

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を使ったコマンドを記述する方法があり、今回はこれを用いることにします。

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: ['./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

hyamatan
SCP-████-Aは東洋系黄色人種の外見を持ち、自身を「Hiroaki Yamamoto」と呼称するヒト型実体です。SCP-████-Aは出生前に事案█████による████の発現と暴露によって思考と行動、及び感情が一致しません。また、SCP-████-Aを目撃した実体の██%がSCP-████-Aの有する特異性と同様の特異性を発現するようになります。
https://www.hysoftware.net/
zeals-inc
チャットボットでネットにおもてなし革命を起こす、チャットコマース『Zeals』の開発・運営をやっています。
https://zeals.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away