def yasuharu519(self):

日々の妄想

Docker と --init とコンテナオーケストレーションでの設定について

SRE 2 Advent Calendar 2018 (https://qiita.com/advent-calendar/2018/sre2) 12/9 の記事です。

今回は Docker コンテナを起動する際の --init オプションと PID 1 問題について調べていくうちに、各コンテナオーケストレーションサービスでの対応について気になり、それについて調べてみました。

Docker の init process

Docker の init process については上記記事で紹介されていますが、 Docker の場合、特に考慮せずプロセスを実行させるとエントリーポイントとして設定したプロセスが PID 1 で動作します。 Linux では起動時に最初に起動する init プロセスに PID 1 が割り振られ、PID 1 のプロセスについては特殊な考慮がされ、デフォルトでシグナルを無視する設定となっているようです。 そのため、 docker stop コマンドなどで SIGTERM シグナルを送ってもプロセスが停止されず、一定時間後にタイムアウトして SIGKILL で強制終了します。

ちなみに SIGTERM から SIGKILL に切り替わるタイムアウト値については、 -t オプションで変更することが可能で、docker stop -t 30 <コンテナID> 等でタイムアウト値の変更をすることができます。 https://docs.docker.com/engine/reference/commandline/stop/

ローカルで開発している場合などは大きな問題にならないかと思いますが、大規模なアプリケーションをコンテナで動かそうとしている場合は、余計な時間がかかったり graceful にシャットダウンされないなど問題があります。

そういった問題に対応するため、Docker で使用できて、シグナルを適切に処理する init 用プロセスがいくつか作成されています。

例えば datadog agent では tini が使われていますし、(https://github.com/DataDog/docker-dd-agent/blob/master/Dockerfile-alpine) consul では dumb-init が使われています (https://github.com/hashicorp/docker-consul/blob/master/0.X/docker-entrypoint.sh#L1)。

Docker 1.13 からは --init オプションが追加されました。 これは、実行時にオプションを設定することで、tini (https://github.com/krallin/tini) と同等の処理が実行されるようになるものです。 --init オプションのありなしで init プロセスがラップされ、SIGTERM が正しく処理されるようになります。

コンテナオーケストレーションでの対応について

AWS ECS, Kubernetes のコンテナオーケストレーションサービスでも上記の問題については対応を考える必要があります。 すべてのコンテナで tini, dumb-init 等の init process についての対応をできればいいですが、その場合コンテナイメージを作成するたびにそのための対応をする必要があります。 せっかくコンテナで実行させるアプリケーションに集中したいのにもかかわらずそれ以外の部分に気を配る必要となります。 そのため、可能ならば --init オプション等で一律対応できたほうが管理上も楽です。

AWS ECS の場合

https://github.com/aws/amazon-ecs-agent/issues/852

ECS agent v1.15.0 から --init オプション相当の対応が入っています。

タスク定義にて、 Linux パラメータの initProcessEnabled を設定することで、--init オプション相当のものを設定して実行する事ができるようになっています。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html

Kubernetes の場合

Kubernetes equivalent of docker run --init

Kubernetes でも同様のことができるのかについて、StackOverflow でも質問が投げられています。 また、こちらのブログでは、Kubernetes での pause コンテナについて紹介されています。

The Almighty Pause Container

これは Pod 内での namespace を共有する機能を使用し、pause コンテナというものを作成するといったものです。 pause コンテナは Pod 内の他のコンテナの親コンテナとしで PID 1 で起動し、他のコンテナについては PID 1 以外で起動するようになります。 この方法で、SIGINT, SIGTERM 等のシグナルは pause コンテナで適切に処理されるようになるようです(が、まだ完全には理解できてない)

pause コンテナの機能については、

  • v1.7 でデフォルトで ON
  • v1.8 ではデフォルトで OFF
  • v1.10 からは alpha サポートとなり、 PodShareProcessNamespace という feature フラグが追加されました

Feature Gates

こちらに Feature フラグのデフォルト値についてまとめられていますが、

  • v1.10 では PodShareProcessNamespace はデフォルト false
  • v1.12 からは Beta 扱いとなりデフォルト true

となっており、2018/12/09 現在、GKE で指定できる最新バージョンは v1.11.4、 Docker for Mac では v1.10.3AWS EKS では v1.10.3 が利用されているようなので、上記機能はデフォルトで false となっています。

Share Process Namespace between Containers in a Pod

こちらで紹介されている設定にて、

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  shareProcessNamespace: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    securityContext:
      capabilities:
        add:
        - SYS_PTRACE
    stdin: true
    tty: true

を Docker for macKubernetes 環境で試してみましたが、

$ kubectl attach -it nginx -c shell
If you don't see a command prompt, try pressing enter.
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 sh
    9 root      0:00 ps aux

となってしまい、namespace 共有はされていませんでした。(なぜだ) GKE でも v1.11.4クラスタを立ち上げて試してみましたがこちらでも同様に確かめることができませんでした。(なぜだろう)

デフォルトの挙動を変えるためには、Kubernetes の Feature gates を設定する必要があります。 その場合、kubelet にオプションを指定して起動する必要があります。 Docker for mac, GKE 等の環境ではFeature gates を変更する方法は用意されておらず、現状使えないようです。 そのため、現状では GKE のアルファクラスタを使用する方法や、自分で Kubernetes 環境を作っている場合 kubelet のパラメータを変更して起動する方法しかなさそうに見えます。

Kubernetes 自体は AWS ECS と違い rkt のサポートもされており、広くプラットフォームとして使われることを想定されていることから、 Docker オプションの直接的なサポート等がされていないように感じました。

まとめ

Docker の --init オプションから、コンテナオーケストレーション環境での同等な設定方法について AWS ECS/Kubernetes での方法を調査しました。

現状 Docker のみを対象とするのであれば、--init オプションを付けてアプリケーションを起動させ、アプリケーション作成に集中したほうがよさそうです。 とはいえ、Kubernetes 等を使うのを想定するのであれば、 tini 等を設定した Docker イメージを作成したほうがよいかもしれません。