Entrykit のすすめ 

# はじめに
あなたは Docker イメージを作る際に `start.sh` や `startup.sh` というファイルを作った経験はないだろうか。「Dockerfile の `CMD` だと 1 コマンドしか書けないから、シェルスクリプトで頑張る」というやつだ。例えば、環境変数を受け取って設定ファイルを動的に作ったり、メインプロセス起動前に何かしら初期化処理をするなど。そんなあなたに Entrykit というツールを紹介しよう。

# Entrykit とは
Entrykit は一言でいうと、コンテナ内のプロセス起動時に便利な軽量 init システムだ。Docker の起動コマンドに Entrykit を使うと、起動時にテンプレートファイルを元に設定ファイルをレンダリングしてくれたり、メインプロセスの前にコマンドを実行できたりする。

- **公式サイト** - https://github.com/progrium/entrykit
- **読み方** - エントリーキット
- **一言でいうと** - コンテナ内のプロセス起動時に便利な軽量 init システム
- **公式ドキュメント** - https://github.com/progrium/entrykit/blob/master/README.md
- **最新バージョン** - [Releases · progrium/entrykit](https://github.com/progrium/entrykit/releases) を参照

この記事では Entrykit を使うと何ができて、どう嬉しいかを例とともに紹介する。

# インストール
Entrykit は Go で書かれていて、GitHub にバイナリリリースが置いてあるため、それをコンテナの中に置いて使う。

```Dockerfile
FROM gliderlabs/alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

RUN apk-install openssl \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink
```

`entrykit --symlink` はサブコマンドのシンボリックリンクを作ってくれる。

```
$ entrykit --symlink
entrykit
Creating symlink /bin/entrykit ...
Creating symlink /bin/codep ...
Creating symlink /bin/prehook ...
Creating symlink /bin/render ...
Creating symlink /bin/switch ...
```

# render - テンプレートのレンダリング

`render` コマンドは、指定したテンプレートをコンテナ起動時にレンダリングしてから、ルートプロセスを起動してくれる。特にコンテナ起動時に環境変数で設定ファイルの中身を上書きしたいときに便利だ。

## 簡単な例

例えば、`nginx.conf` をレンダリングしたいとしよう。

まずはテンプレートとなる `nginx.conf.tmpl` を用意する。テンプレートエンジンは [sigil](https://github.com/gliderlabs/sigil) が用いられる。

```nginx
daemon off;
error_log /dev/stdout notice;
user nobody nogroup;
worker_processes {{ var "WORKER_PROCESSES" | default "1" }};
events {
    worker_connections  1024;
}
http {
  server {
    listen 80;
    server_name localhost;
    access_log /dev/stdout;
    root /;

    location / {
      index /index.html;
    }
  }
}
```

4 行目の部分がテンプレートを使って置換したいところ。
コンテナ起動時に `WORKER_PROCESSES` という環境変数で設定できるようにする。

```
worker_processes {{ var "WORKER_PROCESSES" | default "1" }};
```

表示する HTML ファイル(`index.html`) も用意しておこう。

```html
hello world
```

次に `Dockerfile` を用意しよう。


```Dockerfile
FROM gliderlabs/alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

# Install Entrykit
RUN apk-install openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

RUN apk-install nginx
COPY ./nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl
COPY ./index.html /index.html

ENTRYPOINT ["render", "/etc/nginx/nginx.conf", "--", "/usr/sbin/nginx"]
```

ポイントは 18 行目。テンプレートファイルをコンテナの中に入れておく必要がある。

```Dockerfile
COPY ./nginx.conf.tmpl /etc/nginx.conf.tmpl
```

そして、entrykit の `render` コマンドにレンダリング先ファイル `nginx.conf` を指定する。

```Dockerfile
ENTRYPOINT ["render", "/etc/nginx.conf", "--", "/usr/sbin/nginx"]
```

これで entrykit を使った nginx イメージをビルドして起動してみる。

```
$ docker build -t=entrykit-example .
```

```
$ docker run -it --rm -p 80:80 entrykit-example
2016/02/18 08:10:46 [notice] 1#0: using the "epoll" event method
2016/02/18 08:10:46 [notice] 1#0: nginx/1.8.1
2016/02/18 08:10:46 [notice] 1#0: OS: Linux 4.1.13-boot2docker
2016/02/18 08:10:46 [notice] 1#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 08:10:46 [notice] 1#0: start worker processes
2016/02/18 08:10:46 [notice] 1#0: start worker process 11
```

```
2016/02/18 08:10:46 [notice] 1#0: start worker process 11
```

Nginx の worker プロセスは 1 つである。環境変数 `WORKER_PROCESSES` を使って worker プロセス数を変えてみよう。

```
$ docker run -it --rm -p 80:80 -e WORKER_PROCESSES=2 entrykit-example
2016/02/18 08:11:54 [notice] 1#0: using the "epoll" event method
2016/02/18 08:11:54 [notice] 1#0: nginx/1.8.1
2016/02/18 08:11:54 [notice] 1#0: OS: Linux 4.1.13-boot2docker
2016/02/18 08:11:54 [notice] 1#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 08:11:54 [notice] 1#0: start worker processes
2016/02/18 08:11:54 [notice] 1#0: start worker process 8
2016/02/18 08:11:54 [notice] 1#0: start worker process 9
```

プロセスが 2 つになったことがわかる。

## 複数ファイルのレンダリング

`render` コマンドは、レンダリングするファイルを複数指定することも可能だ。

`index.html` を `index.html.tmpl` にリネームして以下のように変えてみよう。

```tmpl
{{ var "MESSAGE" | default "hello world" }}
```

`index.html` の代わりに `index.html.tmpl` をコンテナイメージに追加して、`render` コマンドが受け取るレンダリング先ファイルを 1 つ追加する。

```Dockerfile
FROM gliderlabs/alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

RUN apk-install openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

RUN apk-install nginx
COPY ./nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl
COPY ./index.html.tmpl /index.html.tmpl # updated line

ENTRYPOINT ["render", "/etc/nginx/nginx.conf", "/index.html", "--", "/usr/sbin/nginx"] # updated line
```

ビルドしなおして、起動してみる。

```
$ docker build -t=entrykit-example .
$ docker run -it --rm -p 80:80 -e WORKER_PROCESSES=2 entrykit-example
```

nginx コンテナにアクセスしてみると `hello world` と表示されているはずだ。
次は環境変数 `MESSAGE` を設定して起動してみよう。

```
$ docker run -it --rm -p 80:80 -e WORKER_PROCESSES=2 -e MESSAGE='hello entrykit' entrykit-example
```

今度は `hello entrykit` と表示されるはずだ。


# switch - 起動コマンドの切り替え
`switch` は簡単に言うと `ENTRYPOINT` を切り替えるコマンド。普段は `ENTRYPOINT` に指定したプロセスを起動するが、シェルを起動してコンテナの中に入り、デバッグしたいケースもあるだろう。

## 簡単な例
Entrykit の入った Nginx コンテナを使って、以下の 3 パターンを実行してみよう。

- Nginx を通常通り起動する
- シェルを起動してコンテナの中に入る
- Nginx のバージョンを確認する

Entrykit の入った Nginx コンテナを用意する。

```Dockerfile
FROM gliderlabs/alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

RUN apk-install add openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

RUN apk-install nginx
ADD ./nginx.conf /etc/nginx/nginx.conf
ADD index.html /index.html

ENTRYPOINT [ \
  "switch", \
    "shell=/bin/sh", \
    "version=nginx -v", "--", \
  "/usr/sbin/nginx" \
]
```

何もコマンドを指定せずに起動すると `--` の後の `/usr/sbin/nginx` が実行される

```
$ docker run -it --rm -p 80:80 entrykit-example
2016/02/18 08:49:20 [notice] 1#0: using the "epoll" event method
2016/02/18 08:49:20 [notice] 1#0: nginx/1.8.1
2016/02/18 08:49:20 [notice] 1#0: OS: Linux 4.1.13-boot2docker
2016/02/18 08:49:20 [notice] 1#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 08:49:20 [notice] 1#0: start worker processes
2016/02/18 08:49:20 [notice] 1#0: start worker process 8
```

`ENTRYPOINT` を指定しているのでもちろん、`-h` を渡すと Nginx のヘルプが出る

```
$ docker run -it --rm -p 80:80 entrykit-example -h
nginx version: nginx/1.8.1
Usage: nginx [-?hvVtq] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
  -?,-h         : this help
  -v            : show version and exit
  -V            : show version and configure options then exit
  -t            : test configuration and exit
  -q            : suppress non-error messages during configuration testing
  -s signal     : send signal to a master process: stop, quit, reopen, reload
  -p prefix     : set prefix path (default: /usr/share/nginx/)
  -c filename   : set configuration file (default: /etc/nginx/nginx.conf)
  -g directives : set global directives out of configuration file
```

さて今度は `shell` を渡してみよう。

```
$ docker run -it --rm -p 80:80 entrykit-example shell
/ #
```

`switch` コマンドに渡した `shell` タスク = `/bin/sh` が実行されていることがわかる。

同様に、`version` を指定すると

```
$ docker run -it --rm -p 80:80 entrykit-example version
nginx version: nginx/1.8.1
```

バージョンが表示された。バージョンを表示するだけだとさほど旨みはないかもしれないが、長いコマンドを実行するケースでは `switch` タスクが活きてくるはずだ。

# prehook - プレフックの実行

`prehook` はメインプロセス起動前にコマンドを 1 つまたは複数実行できる

## 簡単な例
Nginx の例を示そう。

Nginx サーバプロセスをメインプロセスとして、それの起動前に Nginx バージョンを表示してみよう。

```Dockerfile
FROM gliderlabs/alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

RUN apk-install openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

RUN apk-install nginx
ADD ./nginx.conf /etc/nginx/nginx.conf
ADD index.html /index.html

ENTRYPOINT [ \
  "prehook", \
    "nginx -v", "--", \
  "/usr/sbin/nginx" \
]
```

コンテナをビルドして実行してみよう。

```
$ docker run -it --rm -p 80:80 entrykit-example
nginx version: nginx/1.8.1
2016/02/18 09:02:45 [notice] 1#0: using the "epoll" event method
2016/02/18 09:02:45 [notice] 1#0: nginx/1.8.1
2016/02/18 09:02:45 [notice] 1#0: OS: Linux 4.1.13-boot2docker
2016/02/18 09:02:45 [notice] 1#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 09:02:45 [notice] 1#0: start worker processes
2016/02/18 09:02:45 [notice] 1#0: start worker process 10
```

サーバ起動前にバージョンが表示されてるのがわかる。

複数のプレコマンドを実行することもできる

```Dockerfile
ENTRYPOINT [ \
  "prehook", \
    "echo hello world", \
    "nginx -v", "--", \
  "/usr/sbin/nginx" \
]
```

```
$ docker run -it --rm -p 80:80 entrykit-example
hello world
nginx version: nginx/1.8.1
2016/02/18 09:07:00 [notice] 1#0: using the "epoll" event method
2016/02/18 09:07:00 [notice] 1#0: nginx/1.8.1
2016/02/18 09:07:00 [notice] 1#0: OS: Linux 4.1.13-boot2docker
2016/02/18 09:07:00 [notice] 1#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 09:07:00 [notice] 1#0: start worker processes
2016/02/18 09:07:00 [notice] 1#0: start worker process 12
```

# codep - 複数プロセスの並列起動

`codep` は複数プロセスを並列に起動してくれるコマンドだ。一般的な init システムと違って、`codep` は並列に起動しているプロセスのどれかが死んだ場合に他のプロセスを kill する。送られてくるシグナルは両方にプロキシされる。

`codep` は confd とか consul-template といった、メインプロセス起動後も設定ファイルを更新しつづけるプロセスを並列に走らせるのに便利だ。

## 簡単な例
Nginx サーバプロセスと並列に `hello world` と echo し続けるプロセスを走らせてみよう。

`hello` というスクリプトを用意する。

```bash
#!/bin/sh
while true; do
  sleep 2
  echo 'hello world'
done
```

```Dockerfile
FROM gliderlabs/alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

RUN apk-install openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

RUN apk-install nginx
ADD ./nginx.conf /etc/nginx/nginx.conf
ADD index.html /index.html
ADD hello /bin/hello
RUN chmod +x /bin/hello

ENTRYPOINT [ \
  "codep", \
    "/bin/hello", \
    "/usr/sbin/nginx" \
]
```

```
$ docker run -it --rm -p 80:80 entrykit-example
2016/02/18 09:25:15 [notice] 10#0: using the "epoll" event method
2016/02/18 09:25:15 [notice] 10#0: nginx/1.8.1
2016/02/18 09:25:15 [notice] 10#0: OS: Linux 4.1.13-boot2docker
2016/02/18 09:25:15 [notice] 10#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 09:25:15 [notice] 10#0: start worker processes
2016/02/18 09:25:15 [notice] 10#0: start worker process 14
hello world
hello world
hello world
hello world
```

`docker top` の結果はこうなる

```
$ docker top d90cebcb55d6
UID   PID PPID C STIME  TTY TIME                CMD
root  6836 943 0 09:38  ?   00:00:00            codep /bin/hello /usr/sbin/nginx
root  6846 6836 0 09:38 ?   00:00:00            /bin/sh /bin/hello
root  6848 6836 0 09:38 ?   00:00:00            nginx: master process /usr/sbin/nginx
nobody 6849 6848 0 09:38 ?  00:00:00            nginx: worker process
root  6855 6846 0 09:38 ?   00:00:00            sleep 2
```

複数のルートプロセスが起動していることがわかる

# Entrykit コマンドのチェイン
entrykit の command は chain させることが可能だ。

## 簡単な例

```Dockerfile
FROM alpine:3.2
MAINTAINER Seigo Uchida <spesnova@gmail.com> (@spesnova)

ENV ENTRYKIT_VERSION 0.4.0

WORKDIR /

RUN apk --update add openssl \
  && rm -rf /var/cache/apk/* \
  && wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

RUN apk --update add nginx
ADD ./nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl
ADD index.html /index.html
ADD hello /bin/hello
RUN chmod +x /bin/hello

ENTRYPOINT [ \
  "switch", \
    "shell=/bin/sh", \
    "version=nginx -v", "--", \
  "render", \
    "/etc/nginx/nginx.conf", "--", \
  "prehook", \
    "nginx -v", "--", \
  "codep", \
    "/bin/hello", \
    "/usr/sbin/nginx" \
]

```

起動してみる

```
$ docker run -it --rm -p 80:80 -e WORKER_PROCESSES=2 entrykit-example
nginx version: nginx/1.8.1
2016/02/18 09:52:26 [notice] 19#0: using the "epoll" event method
2016/02/18 09:52:26 [notice] 19#0: nginx/1.8.1
2016/02/18 09:52:26 [notice] 19#0: OS: Linux 4.1.13-boot2docker
2016/02/18 09:52:26 [notice] 19#0: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2016/02/18 09:52:26 [notice] 19#0: start worker processes
2016/02/18 09:52:26 [notice] 19#0: start worker process 22
2016/02/18 09:52:26 [notice] 19#0: start worker process 23
hello world
hello world
hello world
```

- prehook
- render
- codep

が動いてるのがわかる

```
$ docker run -it --rm -p 80:80 -e WORKER_PROCESSES=2 entrykit-example shell
/ #
```

switch も動いている

# まとめ
Entrykit というツールの存在を紹介し、具体的にどう使えばいいのかを解説した。実際に自分も `start.sh` を作ったことがあるし、他人のイメージでよく見かける。それでもシェルスクリプトじゃないと実現できないってことも当然あるだろうけど、置き換えられるものも多々あるかなという印象。とりあえず Entrykit、オススメです。