Carpe Diem

備忘録

Dockerの--initフラグについて

概要

dockerのコンテナは指定したコマンドがPID 1で起動されており、使い方によってはシグナルハンドリングできないことがありますよ、という話です。
それによってプロセスをGracefulに終了できなかったりリソースリークが起きたりするので注意する必要があります。

環境

  • docker v18.09.0

どんな問題が起きる?

こちらでとてもわかり易く説明されてます。

Unix プロセスと Docker の罠 - けちゃぶろぐ

Dockerケースを要約すると、親、子、孫の3プロセスが起動している状態

ケース 何が起きる 問題点
親が死ぬ 子も孫も強制的に死ぬ 処理中リクエストを
ハンドリングできない
子が死ぬ 孫は親に紐付けられるが、
親はそれを知らないので孫はゾンビになる
リソースリーク

という問題が起きます。
親、子だけのケースであれば前者が起きます。

こんな使い方の時は注意

特に以下のケースではこの問題が起きている可能性が高いので注意が必要です。

どんな対応をすればいい?

直接バイナリを実行する場合はシグナルがそのバイナリに送られるので問題ないですが、そうでない場合は以下のような対応が必要になってきます。

docker runの場合

docker 1.13以降で--initをつけるとPID 1/dev/initという軽量init processを仕込んでくれます。

$ docker run --init --name test init:latest

コンテナ内部でpsしてみると、こんな感じになります。

--initなし

bash-4.4# ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 {run.sh} /bin/bash ./run.sh
    9 root      0:00 /main
   16 root      0:00 /bin/bash
   23 root      0:00 ps aux

--initあり

以下のように/dev/initが挟まって適切にシグナルをハンドリングしてくれます。

bash-4.4# ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 /dev/init -- ./run.sh
    8 root      0:00 {run.sh} /bin/bash ./run.sh
    9 root      0:00 /main
   16 root      0:00 /bin/bash
   23 root      0:00 ps aux

docker-composeの場合

docker-compose 1.13以降で、docker-compose.ymlが

  • v2なら2.2以上
  • v3なら3.7以上

において以下のようにinit: trueを設定します。

version: '2.2'
services:
  web:
    image: alpine:latest
    init: true

Compose file version 2 reference | Docker Documentation

ECSの場合

Task DefinitionにinitProcessEnabledをつけます。

タスク定義パラメーター - Amazon Elastic Container Service

Kubernetesの場合

stackoverflow.com

にあるように、Kubernetesオーケストレーションツールであってコンテナランタイム側の対応をするつもりはないようです。

なのでDockerfileで

などを導入してそれらをENTRYPOINTにする必要があります。

その他

dumb-initとか対応してるdocker imageで--initをつけると?

/ # ps aux
PID   USER     TIME   COMMAND
    1 root       0:00 /dev/init -- docker-entrypoint.sh agent -retry-join consul-server-bootstrap -client 127.0.0.1
    8 root       0:00 {docker-entrypoi} /usr/bin/dumb-init /bin/sh /usr/local/bin/docker-entrypoint.sh agent -retry-join consul-server-bootstrap -client 127.0.0.1
    9 consul     0:00 consul agent -data-dir=/consul/data -config-dir=/consul/config -retry-join consul-server-bootstrap -client 127.0.0.1
   27 root       0:00 /bin/ash
   34 root       0:00 ps aux

こんな感じで

dumb-init -> consul
↓
/dev/init -> dumb-init -> consul

となるようです。動作としてはシグナルは伝播されて問題ありませんでした。