docker-jitsi-meetのENTRYPOINT

docker-jitsi-meetのbaseコンテナイメージ指定されている/initスクリプトは何だろうと思い調べた内容である。結論から言うと、s6というスーパバイザプログラムでプロセスを実行している。

entrypoint /init

# ...割愛
RUN \
apt-dpkg-wrap apt-get update && \
apt-dpkg-wrap apt-get install -y apt-transport-https apt-utils ca-certificates gnupg && \
apt-dpkg-wrap apt-get install -y wget && \
wget -qO - https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz | tar xfz - -C / && \
wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | apt-key add - && \
wget -q https://github.com/subchen/frep/releases/download/v1.3.5/frep-1.3.5-linux-amd64 -O /usr/bin/frep && \
apt-dpkg-wrap apt-get --purge remove -y wget && \
echo "deb https://download.jitsi.org $JITSI_RELEASE/" > /etc/apt/sources.list.d/jitsi.list && \
echo "deb http://ftp.debian.org/debian stretch-backports main" > /etc/apt/sources.list.d/backports.list && \
apt-dpkg-wrap apt-get update && \
apt-dpkg-wrap apt-get dist-upgrade -y && \
apt-cleanup && \
chmod +x /usr/bin/frep

RUN \
[ "$JITSI_RELEASE" = "unstable" ] && \
apt-dpkg-wrap apt-get update && \
apt-dpkg-wrap apt-get install -y jq procps curl vim iputils-ping net-tools && \
apt-cleanup || \
true

ENTRYPOINT [ "/init" ] # <-- これ

s6 overlay

s6 overlayは、s6というunix系のスーパバイザツールをベースとしたDockerイメージ作成のためのツールセットのことらしい。s6は、daemontoolsやrunitのようなスーパバイザツールである。
Dockerfileのサンプルは以下のようになる。
FROM debian:buster-slim

ARG S6_VERSION=2.1.0.0

ADD https://github.com/just-containers/s6-overlay/releases/download/v$S6_VERSION/s6-overlay-amd64.tar.gz /tmp/
RUN tar xzf /tmp/s6-overlay-amd64.tar.gz -C /

# Do something if any
# ARG PUID=1000
# ARG PGID=1000
# RUN addgroup -g ${PGID} myapp && adduser -D -u ${PUID} -G my app -h /app -D myapp

ENTRYPOINT ["/init"]
ENTRYPOINTは/initというスクリプトとなる。s6 overlayツールは、プロセス実行のライフサイクルをステージという単位で扱いプロセス実行している。docker-jitsi-meetは上記をbaseイメージとして使用し、各種サービスのコンテナイメージをbaseイメージから派生させ作成している。

s6 overlayのステージ

以下の初期ステージ、実行ステージ、終了ステージ、の3つのステージで構成される。

stage 1

イメージの準備ステージ。具体的には環境変数の設定などである。

stage 2

ユーザ提供のファイルが実行されるステージ。
  • /etc/fix-attrs.dで所有者やパーミッションなどの設定
  • /etc/cont-init.dのスクリプト実行
  • /etc/services.dのサービスを実行しs6が監視を開始

stage 3

終了処理のステージ、プロセスのclean upと停止処理が行われる。
  • /etc/cont-finish.dでfinalize処理
  • SIGTERMでgracefulシャットダウン、SIGKILLで強制停止
基本的には、stage2と3のxxx.dで実行されるフックを使用し目的のプログラムを実行する感じになる。
試しに実行してみよう。
$ docker run -it --rm s6demo0:latest /bin/sh
[s6-init] making user provided files available at /var/run/s6/etc...exited 0.
[s6-init] ensuring user provided files have correct perms...exited 0.
[fix-attrs.d] applying ownership & permissions fixes...
[fix-attrs.d] done.
[cont-init.d] executing container initialization scripts...
[cont-init.d] done.
[services.d] starting services
[services.d] done.
# pstree
s6-svscan-+-foreground---foreground---sh---pstree
          `-s6-supervise
# Ctrl-Dで終了させる
[cmd] /bin/sh exited 0
[cont-finish.d] executing container finish scripts...
[cont-finish.d] done.
[s6-finish] waiting for services.
[s6-finish] sending all processes the TERM signal.
[s6-finish] sending all processes the KILL signal and exiting.
コンソールログから、ステージのライフサイクルを確認できる。また、実行したshは、s6-svscanというプロセスの管理下に置かれていることが分かる。s6-svscanに関する内容は以下にある。
参考)s6-svscan

s6 overlayを使ったイメージ作成と実行

単純な文字列を出力するだけのアプリを動かすイメージを作成する。

ファイル構成

$ tree .
.
├── Dockerfile
├── app
│   ├── myapp1
│   └── myapp2
├── docker-compose.yaml
└── etc
    ├── cont-finish.d
    │   └── 01-fininalize
    ├── cont-init.d
    │   └── 01-config
    ├── fix-attrs.d
    │   └── 01-myattr
    └── services.d
        ├── myapp1
        │   ├── finish
        │   └── run
        └── myapp2
            ├── finish
            └── run

8 directories, 11 files
  • etc下にs6で必要となるリソースを配置する。すべてのステージのファイルは必要ではないが、サンプルでは用意する。
  • app下にアプリケーションを置く。今回は、単なるsleepするシェルスクリプトである。

Dockerfileとdocker-compose.yaml

Dockerfile
s6 overlayのインストールとユーザ作成、/initを実行するだけのDockerfileである。
FROM debian:buster-slim

ARG S6_VERSION=2.1.0.0

# s6-overlayのインストール
ADD https://github.com/just-containers/s6-overlay/releases/download/v$S6_VERSION/s6-overlay-amd64.tar.gz /tmp/
RUN tar xzf /tmp/s6-overlay-amd64.tar.gz -C /

# UserとGroupの作成
ARG PUID=1000
ARG PGID=1000
RUN addgroup --gid ${PGID} myapp && adduser --gid ${PGID} --uid ${PUID} --no-create-home --system myapp

ENTRYPOINT ["/init"]
docker-compose.yaml
ホスト側のフックのためのスクリプトを配置したディレクトリをマウントする。
version: "3.8"

services:
  s6demo:
    image: s6demo:latest
    container_name: s6demo
    environment:
      MYENV: myenv
    volumes:
      - ./app:/app
      - ./etc/fix-attrs.d:/etc/fix-attrs.d
      - ./etc/cont-init.d:/etc/cont-init.d
      - ./etc/services.d:/etc/services.d
      - ./etc/cont-finish.d:/etc/cont-finish.d
s6 overlayのリソース
etc/fix-attrs.d/01-myattr
/app true myapp,32768:32768 0700 0755
これは、以下の形式で記述している。/appディレクトリに対する所有者及びパーミッションを設定している。
path recurse account fmode dmode
参考
etc/cont-init.d/01-config
#!/usr/bin/with-contenv bash

echo $MYENV
shebangで指定しているwith-contentというプログラムでは、コンテナの環境変数を構築しexecコマンドで引数で指定されるプログラムを実行している。
etc/services.d/myapp1/run
#!/usr/bin/with-contenv bash

exec /app/myapp2
etc/services.d/myapp1/finish
#!/bin/bash

echo "finish myapp1"
etc/services.d/myapp2/run
#!/usr/bin/with-contenv bash

exec /app/myapp2
etc/services.d/myapp2/finish
#!/bin/bash

echo "finish myapp2"
etc/cont-finish.d/
#!/bin/bash

echo "finish myapp1"
app
単なるsleepするだけのシェルスクリプト。
app/myapp1
#!/bin/bash

echo "start myapp1"

sleep 10

# わざと異常終了させてみる

exit 1 
app/myapp2
#!/bin/bash

echo "start myapp2"

sleep 20

イメージの実行

作成したイメージとアプリを実行してみる。
$ docker-compose up
Creating s6demo ... done
Attaching to s6demo
s6demo    | [s6-init] making user provided files available at /var/run/s6/etc...exited 0.
s6demo    | [s6-init] ensuring user provided files have correct perms...exited 0.
s6demo    | [fix-attrs.d] applying ownership & permissions fixes...
s6demo    | [fix-attrs.d] 01-myattr: applying... 
s6demo    | [fix-attrs.d] 01-myattr: exited 0.
s6demo    | [fix-attrs.d] done.
s6demo    | [cont-init.d] executing container initialization scripts...
s6demo    | [cont-init.d] 01-config: executing... 
s6demo    | myenv
s6demo    | [cont-init.d] 01-config: exited 0.
s6demo    | [cont-init.d] done.
s6demo    | [services.d] starting services
s6demo    | start myapp2
s6demo    | [services.d] done.
s6demo    | start myapp1
s6demo    | finish myapp1
s6demo    | start myapp1
s6demo    | finish myapp2
s6demo    | start myapp2
s6demo    | finish myapp1
s6demo    | start myapp1
... 
このプログラムを実行すると、myapp1とmyapp2が終了しても繰り返しプロセスが実行されることが分かる(通常のサーバープログラムは、上記例のようにすぐにプロセスが終了することはない)。s6-svscanによるスーパバイザが機能していることが上記例から分かる。
$ watch --interval 1 'pstree -p -a'
Every 1.0s: pstree -p -a                                                                                

s6-svscan,1 -t0 /var/run/s6/services
  |-s6-supervise,37 s6-fdholderd
  |-s6-supervise,267 myapp1
  |   `-myapp1,707 /app/myapp1
  |       `-sleep,709 10
  `-s6-supervise,268 myapp2
      `-myapp2,700 /app/myapp2
          `-sleep,702 20 
終了させる場合は、以下のようにできる。
$ docker-compose stop
# or
$ docker-compose kill -s SIGTERM
# or
$ docker-compose exec s6demo  s6-svscanctl -t /var/run/s6/services
コンテナのログには以下のように表示される。
s6demo    | [cont-finish.d] executing container finish scripts...
s6demo    | [cont-finish.d] 01-fininalize: executing... 
s6demo    | finish myapp
s6demo    | [cont-finish.d] 01-fininalize: exited 0.
s6demo    | [cont-finish.d] done.
s6demo    | [s6-finish] waiting for services.
s6demo    | [s6-finish] sending all processes the TERM signal.
s6demo    | [s6-finish] sending all processes the KILL signal and exiting.
s6demo exited with code 0

その他

initやinit-stageXのスクリプトは、execlineというスクリプト言語で記述されている。シェルのようだがシェルとは異なる。コマンド実行に有用な機能が備わっている。
execline

参考リンク

byebyehaikikyou

日記やIT系関連のネタ、WordPressに関することなど様々な事柄を書き付けた雑記です。ITエンジニア経験があるのでプログラミングに関することなどが多いです。

シェアする

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

コメントする