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

# 概要
[これ]の日本語版のようなもの
[これ]: https://gist.github.com/hiroaki-yamamoto/4e27167dd3cb74700ce86e678ff987cc

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

```bash: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
```

```dockerfile:backend.dockerfile
from python:3.6
env PYTHONUNBUFFERED 1
run mkdir /code
workdir /code
```

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

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

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

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

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

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

```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: ['./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の問題が解決した事に謝辞を申し上げます。

[//]: # (あと気になるのが、こんな基本的な事がQiitaや他のブログ記事などに上がっていないのは、皆々様が優秀だからなのか、それとも記事を書く時間がなかったのか、MacでDockerする事に夢中になっててこの問題に気付いていないのか、僕がそもそも日本語の文献を全く読まないからそういった記事がある事に気づかなかったのか・・・)

# 参考記事
[Hynek Schlawack, Why Your Dockerized Application Isn’t Receiving Signals, 19 June 2017](https://hynek.me/articles/docker-signals/)