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