docker-plugins の使い方


## 概要
Docker に シンプルな hook の機構をもたらしてくれる docker-plugins の使い方

## docker-plugins とは
docker-plugins は Dockerのライフサイクルイベント（docker events）をトリガーに任意のプラグインを実行するのを可能にしてくれる。[Docker Remote API の events stream](https://docs.docker.com/reference/api/docker_remote_api_v1.15/#monitor-dockers-events) を直接使って hook 処理 を行う事はできるが、git hooks のようにカジュアルにできるようにしたのがポイントだと思う。

### 利用可能な hooks

* exists - `docker-plugins` が events stream を listen し始める前から存在してるコンテナに対して起きるイベント
* create
* destroy
* die
* export
* kill
* pause
* restart
* start
* stop
* unpause
* untag - サポートはされているがビルトインのプラグインでは利用しない
* delete - サポートはされているがビルトインのプラグインでは利用しない

## docker-plugins の仕組み
docker-plugins も コンテナとして動かして利用することを前提に作られている。docker-plugins コンテナ起動時にホストの docker socket をマウントすることで、コンテナ内からホストの Docker daemon に対して Docker コンテナ/イメージ の操作を行う。

docker-plugins は以下の 2 つのツール/ライブラリを利用している

* [progrium/dockerhook](https://github.com/progrium/dockerhook)
* [progrium/plugn](https://github.com/progrium/plugn)

### plugn
go と bash でできた hook システム。

下記の構造で plugins を作成し、

```bash
plugins/
├── available
│   ├── example-plugin1
│   │   ├── hookA
│   │   ├── hookB
│   │   └── plugin.toml
│   └── example-plugin2
│       ├── hookA
│       ├── hookC
│       └── plugin.toml
└── config.toml
```

hook を trigger する

```bash
# plugin を利用可能にする
$ plugn enable <plugin>
$ plugn trigger <hook> (<params>)
```

`$ plugn trigger <hook>` コマンドでは、`plugn` で利用可能な plugins が持つ `<hook>` という名前のスクリプトを実行する。

使い方例:

```bash
# example-plugin1 を利用可能にする
$ plugn enable example-plugin1

# hookA hook をトリガーする
# これで plugins/available/example-plugin1/hookA が引数 "mike" とともに実行される
$ plugn trigger hookA mike
```


この仕組みで hooks システムを実現している。

### dockerhook
docker の events stream を listen し、各イベントが起きると、引数で受け取った hook スクリプトにイベント対象のコンテナ ID を渡して実行する daemon といった感じ。docker-plugins では以下のように利用されている。

```bash
dockerhook "/bin/plugn trigger"
```

### hook が実行される流れ
例えば、ビルトインの autoremove プラグインでは、docker コンテナが die すると自動的にそれを `docker rm` するプラグイン。
処理の流れは以下のようになる:

0. docker-plugins コンテナを起動
 * コンテナの起動スクリプトにて `dockerhook "/bin/plugn trigger"` が実行される
 * dockerhook が docker の events stream を listen し始める
0. 手動でコンテナ A を `start` する
0. 手動でコンテナ A を `stop` する
0. dockerhook が events stream より die イベントを受け取る
 * dockerhook が `plugn trigger die <コンテナID>` を実行する
* plugn が利用可能なプラグインの中の `die` スクリプトを実行する
 * autoremove プラグインの `die` スクリプトで `$ docker rm $1` が実行され、コンテナ A が rm される 

## インストール

```bash
$ docker pull progrium/plugins
```

## ビルトインプラグイン

```bash
$ docker run --rm progrium/plugins plugn list
  autoremove           1.0   disabled   Removes any container that dies, including stops and kills
  envhooks             1.0   disabled   Evaluates hook code from environ variables ENVHOOKS_<EVENT>
  timeout              1.0   disabled   Kills containers with TIMEOUT set after TIMEOUT seconds
  webhooks             1.0   disabled   Hits WEBHOOKS_URL or WEBHOOKS_<EVENT>_URL with a POST for each event
```

### autoremove
コンテナの `die` イベント時に、そのコンテナを `docker rm` するプラグイン。

docker-plugins コンテナを起動する。

```bash
$ docker run \
  --rm \
  -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e "ENABLE=autoremove" \
  progrium/plugins

  autoremove           1.0   enabled    Removes any container that dies, including stops and kills
  envhooks             1.0   disabled   Evaluates hook code from environ variables ENVHOOKS_<EVENT>
  timeout              1.0   disabled   Kills containers with TIMEOUT set after TIMEOUT seconds
  webhooks             1.0   disabled   Hits WEBHOOKS_URL or WEBHOOKS_<EVENT>_URL with a POST for each event

2014/11/06 01:26:04 info: listening for Docker events...
```

nginx コンテナを起動し、

```bash
$ docker run -d nginx:1.7.7
d81e0ef17250fc4fe095a99fb1cd7210c735bce6fd5b4f2afe615444c3e34260

$ docker ps | grep nginx
5522df9fa283        nginx:1.7.7                      "nginx -g 'daemon of   11 seconds ago      Up 11 seconds       443/tcp, 80/tcp          nginx
```

nginx コンテナを stop または kill してみると

```bash
$ docker stop nginx
nginx

# rm されてる
$ docker ps | grep nginx
```

### timeout
`TIMEOUT` 経過するとそのコンテナを `docker kill` するプラグイン

```bash
$ docker run \
  --rm \
  -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e "ENABLE=timeout" \
  progrium/plugins
```

```bash
$ docker run -d --name nginx -e TIMEOUT=5 nginx:1.7.7
```


```bash
# 起動直後
$ docker ps -a | grep nginx
c7b92e079d98        nginx:1.7.7  "nginx -g 'daemon of   4 seconds ago       Up 4 seconds             443/tcp, 80/tcp          nginx

# 5 秒後
$ docker ps -a | grep nginx
c7b92e079d98        nginx:1.7.7  "nginx -g 'daemon of   14 seconds ago      Exited (-1) 9 seconds ago                            nginx
```

### envhooks
利用可能な hooks ごとにシェルコマンドが実行する。

die イベント時にログに dead と出力する例

```bash
$ docker run \
  --rm \
  -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e "ENABLE=envhooks" \
  -e "DEBUG=true" \
  -e "ENVHOOKS_DIE=echo dead \$1" \
  progrium/plugins
```

```bash
$ docker run -d --name nginx -e TIMEOUT=5 nginx:1.7.7
0e6ffc2eb50fa0487ef94942997cdac56769b6a4bb18fc976a2c1fc14aae8840
```

kill する

```bash
$ docker kill
```

docker-plugins のログ

```bash
2014/11/06 04:26:46 info: trigger: 0e6ffc2eb50f die
2014/11/06 04:26:46 info: trigger: 0e6ffc2eb50f kill
dead 0e6ffc2eb50fa0487ef94942997cdac56769b6a4bb18fc976a2c1fc14aae8840
```

### webhooks
各イベントごとに `WEBHOOKS_URL` または `WEBHOOKS_<EVENT>_URL` へ POST する

```bash
$ docker run \
  --rm \
  -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e "ENABLE=webhooks" \
  -e "WEBHOOKS_URL=http://requestb.in/151o37b1" \
  progrium/plugins
```

### autoremove と timeout の組み合わせ

```bash
$ docker run \
  --rm \
  -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e "ENABLE=autoremove timeout" \
  progrium/plugins
```

```bash
$ docker run -d --name nginx -e TIMEOUT=5 nginx:1.7.7
```

5 秒後にはコンテナが timeout プラグインにより kill された後 autoremove プラグインにより rm される


## プラグイン
### プラグインのインストール
[progrium/docker-plugin-demo](https://github.com/progrium/docker-plugin-demo) の形式でリポジトリを作り、コンテナ起動時に `INSTALL` で指定するとプラグインをインストールできる。

```bash
$ docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -e "INSTALL=https://github.com/progrium/docker-plugin-demo.git"
  -e "ENABLE=docker-plugin-demo" \
  progrium/plugins

Cloning into 'docker-plugin-demo'...
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 7 (delta 1), reused 7 (delta 1)
Unpacking objects: 100% (7/7), done.
Checking connectivity... done.
Starting docker-plugins with:
  autoremove           1.0   disabled   Removes any container that dies, including stops and kills
  docker-plugin-demo   1.0   enabled    Just a demo
  envhooks             1.0   disabled   Evaluates hook code from environ variables ENVHOOKS_<EVENT>
  timeout              1.0   disabled   Kills containers with TIMEOUT set after TIMEOUT seconds
  webhooks             1.0   disabled   Hits WEBHOOKS_URL or WEBHOOKS_<EVENT>_URL with a POST for each event
```

（このプラグインは `DEMO_TOKEN` 設定しないと動かない）

### プラグインの作成
コンテナの起動時にコンテナ情報を etcd に登録し、終了時に削除する簡易なプラグインを作ってみる

```bash
$ mkdir register && cd register
$ git init
```

```bash
#!/bin/bash
# start

DOCKER_IMAGE="$(/bin/docker inspect $1 | jq -r '.[].Config.Image')"
etcdctl set /containers/$1 $DOCKER_IMAGE
```

```bash
#!/bin/bash
# die

DOCKER_IMAGE="$(/bin/docker inspect $1 | jq -r '.[].Config.Image')"
etcdctl rm /containers/$1 $DOCKER_IMAGE
```

```toml
[plugin]
name = "register"
description = "Register container to etcd"
version = "1.0"
maintainer_name = "Seigo Uchida"
maintainer_email = "spesnova@gmail.com"

[plugin.config]

[plugin.compatibility]
```

```bash
$ git add .
$ git commit -m "Initial commit"
```

docker-plugins 起動

```bash
$ docker run \
  --rm \
  -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /home/core/register:/tmp/register \
  -v /usr/bin/etcdctl:/usr/bin/etcdctl:r \
  -e "ETCDCTL_PEERS=http://172.17.8.101:4001" \
  -e "INSTALL=/tmp/register" \
  -e "ENABLE=register" \
  -e "DEBUG=true" \
  progrium/plugins
```

```bash
# nginx コンテナを起動すると..
$ docker run -d --name nginx nginx:1.7.7
5a840eb25b12dba53f111068b270c04e94c36babb61a2adbbdc209f02a19caf4

# etcd にキーが追加されてる
$ etcdctl ls /containers
/containers/5a840eb25b12dba53f111068b270c04e94c36babb61a2adbbdc209f02a19caf4

# イメージ名が取得出来る
$ etcdctl get /containers/5a840eb25b12dba53f111068b270c04e94c36babb61a2adbbdc209f02a19caf4
nginx:1.7.7

# nginx コンテナを kill すると..
$ docker kill nginx

# キーが削除されている
$ etcdctl ls /containers
```

## メモと感想
### ちょっとしたことやるのに向いている気がする
`plugin.toml` と hook スクリプト の 2 ファイル作れば好きな hook が仕込めるので楽。
逆に、複雑なことをやろうとすると大変。`$ docker inspect <コンテナID>` を `jq` でパースした後 bash で結構色々がんばる必要があると思う。

## REF

* [progrium/docker-plugins](https://github.com/progrium/docker-plugins)
* [Extending Docker with Plugins](http://vimeo.com/110835013)