Docker in Docker のベタープラクティス

# Docker in Docker したいケース

Jenkins などの CI ツールを Docker コンテナ上で動かしたいことがあります。
プロダクト別に Jenkins を分けたい、とか、本番環境と同じ OS 上でテストを実行したい、などの理由で。

Jenkins 上のテストで DB を使用したい場合、テスト用のデータが入った DB の Docker image を作っておいて、テストジョブを走らせる時に Docker コンテナを作成してテストコードから参照できたらいいですよね。

このような場合には Docker コンテナ（Jenkins 稼動）上で Docker コンテナ（テスト用DB 稼動）を動かすことになります。これを Docker in Docker といいます。

Docker in Docker をするには以下に挙げる二つの方法があるようです（[参考](#参考)）。どちらがベターでしょうか。


# 方法① docker:dind イメージを使用する

Jenkins コンテナのベースイメージとして docker:dind を使用すると、コンテナ上から docker を実行できるようになります。docker:dind についての詳細は[参考](#参考)を参照してください。

## やってみる

ホストマシン上に以下のイメージがあります。

```console
[root@localhost vagrant]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              05188b417f30        4 days ago          196.8 MB
hello-world         latest              c54a2cc56cbb        4 days ago          1.848 kB
docker              latest              b7b7422b4d51        12 days ago         75.76 MB
```

docker:dind イメージを privileged オプションを付けて起動します。

```console
[root@localhost ~]# docker run --privileged -e HTTPS_PROXY=プロキシ --name dind -d docker:dind
```

docker:dind は Data Volume を作成して、コンテナ内の /var/lib/docker にマウントします。そのため、ホストマシンに Data Volume が作成されます。

```console
[root@localhost ~]# ls /var/lib/docker/volumes/
634df4e69a7ea66ff117b775c6ea573775a1775aa00b197947dc4f5dad769a65  metadata.db
```

コンテナにログインして hello-world コンテナを起動します。

```console
[root@localhost ~]# docker exec -it dind sh
/ # docker run hello-world
Unable to find image 'hello-world:latest' locally
... (hello-world イメージがダウンロードされます) ...

/ # docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS
cce166f99e54        hello-world         "/hello"            4 seconds ago       Exited (0) 3 seconds ago
```

コンテナを起動できました。
ホストマシン上で動いている Docker daemon は hello-world イメージを持っているのですが、docker:dind の Docker daemon は同じイメージを新たにダウンロードします。

さて、ホストに戻って docker:dind コンテナを削除します。

```console
[root@localhost ~]# docker stop dind
[root@localhost ~]# docker rm dind
```

再度 docker:dind コンテナを起動して、ホストマシンの /var/lib/docker/volumes を参照すると、ディレクトリが増えていることが分かります。Data Volume なので、一つ目のコンテナのデータが残ったままになるのです。これは手動で消す必要があります。

## 良い点

ホストマシンの Docker daemon と docker:dind コンテナ上の Docker daemon が別になっているので、ホストマシンからコンテナ上のコンテナは見えませんし、その逆もまた見えません。つまりコンテナが階層構造になっているので、管理しやすいかなと思います。

## 悪い点

docker コンテナのいいところの一つに、コンテナ上ではっちゃけてもホストマシンに影響がない、というのがありますが、privileged オプションを付けると台無しかなと思います。

また、暗黙的に Data Volume が使われるので、コンテナの再生成を繰り返すとゴミが溜まっていきます。つまり、Jenkins コンテナを破棄しても、ホストマシン上にデータが残ったままになります。これを理解せずに使用していると、ディスクスペースを不要に消費してしまいます。

# 方法② ホストマシン上の Docker daemon を共有する

Docker コンテナ上からホストマシンの Docker daemon を利用します。起動したコンテナは起動元のコンテナと同じ階層になるので、Docker in Docker というとちょっと語弊があるかもしれません。 

## やってみる

ホストマシン上に以下のイメージがあります。

```console
[root@localhost vagrant]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              05188b417f30        4 days ago          196.8 MB
hello-world         latest              c54a2cc56cbb        4 days ago          1.848 kB
docker              latest              b7b7422b4d51        12 days ago         75.76 MB
```

普通のコンテナを上げます。

```console
[root@localhost vagrant]# docker run hello-world

...

[root@localhost vagrant]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
4d68c3c4acbb        hello-world         "/hello"            7 seconds ago       Exited (0) 6 seconds ago                       evil_varahamihira
```

ホストマシンの Docker daemon を参照できるコンテナを上げて（Docker daemon の socket をマウントするのみ）、コンテナにログインします。
Docker バイナリが必要（※Docker daemon の起動は不要）なので、公式の docker イメージを使用します。

```console
[root@localhost vagrant]# docker run -v /var/run/docker.sock:/var/run/docker.sock -ti docker sh
```

Docker daemon を利用できることを確かめます。
先ほど作成した hello-world のコンテナの存在を確認します。

```console
/ # docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
03436f286399        docker              "docker-entrypoint.sh"   19 seconds ago      Up 18 seconds                                  sharp_sammet
4d68c3c4acbb        hello-world         "/hello"                 4 minutes ago       Exited (0) 4 minutes ago                       evil_varahamihira
/ #
```

ホストマシンのコンテナを参照できました。

削除も確認します。

```console
/ # docker rm 4d68c3c4acbb
4d68c3c4acbb
/ # docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS               NAMES
03436f286399        docker              "docker-entrypoint.sh"   About a minute ago   Up About a minute                       sharp_sammet
```

このコンテナ内から centos コンテナを起動してログインしてみます。

```console
/ # docker run -ti centos bash
[root@3af05e3e57cd /]#
```

centos コンテナを起動できました。
イメージの取得処理も走っていません。ホストマシンの docker daemon が管理しているイメージを使用できています。

さて、この状態でホストマシン上で Docker コンテナを一覧するとどうなるでしょうか。

```console
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS
3af05e3e57cd        centos              "bash"                   3 minutes ago       Up 3 minutes
03436f286399        docker              "docker-entrypoint.sh"   7 minutes ago       Up 7 minutes
```

公式docker イメージのコンテナと、そのコンテナ上で起動した centos コンテナが、並列に見えますね。親子関係は見えません。

## 良い点

ホストマシンの socket をマウントするだけなので分かりやすいですし、Docker イメージを重複して持たないのでディスクスペースを節約できす。

## 悪い点

ホストマシンで動いてる他の Docker コンテナも見えちゃうので、例えば Jenkins コンテナ上で他の Jenkins コンテナを停止できてしまいます。

また、ホストマシン上からは Jenkins コンテナと、Jenkins コンテナ上で動いているコンテナが並列に見える（親子関係が分からない）ので、管理しにくい気がします。

# 結論

docker:dind を使用するよりはホストマシンの Docker daemon を共有する方がベターかなと思います。

いずれにせよ、プロダクト環境ではあまりやりたくないですねー。

# 参考

http://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/
https://github.com/jpetazzo/dind