Edited at

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 についての詳細は参考を参照してください。


やってみる

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

[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 オプションを付けて起動します。

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

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

[root@localhost ~]# ls /var/lib/docker/volumes/

634df4e69a7ea66ff117b775c6ea573775a1775aa00b197947dc4f5dad769a65 metadata.db

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

[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 コンテナを削除します。

[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 というとちょっと語弊があるかもしれません。


やってみる

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

[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

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

[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 イメージを使用します。

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

Docker daemon を利用できることを確かめます。

先ほど作成した hello-world のコンテナの存在を確認します。

/ # 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
/ #

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

削除も確認します。

/ # 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 コンテナを起動してログインしてみます。

/ # docker run -ti centos bash

[root@3af05e3e57cd /]#

centos コンテナを起動できました。

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

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

[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