この記事はDocker Advent Calendar 2016の8日目の記事です。
Docker Advent Calendar 2016 - Qiita
動機
microservices運用してるし、検証用プロジェクトとか遊び用プロジェクトとかあるし、複数docker-composeをローカルで運用するのしんどい。
dind(ディーインディー)
dindとは何ぞやと思う方もいるかもしれないですが、dindはDocker in Dockerの略でDockerコンテナのでさらにDockerのデーモンを稼働させて、その内部のDocker上にさらにコンテナを配置するという手法です。
dind対応のdockerのDockerイメージはDockerHubから入手できます。
dindがついているのがdindのDockerイメージ。とりあえず今回は1.12.3-dindを使ってみることにします。あ、Docker環境は各自で用意してくださいw 当方はMacユーザーなのでDocker for Macを利用しています。
dindコンテナを立てる
次にdindコンテナを立てるわけですが、docker runをポチポチ実行していくよりちゃんと構成管理した方が良いと思うので、docker-compose を使ってdindコンテナを立てます。docker-composeはv2のスキーマを利用し、docker-compose.ymlを適当な場所に作成します。
version: "2"
services:
dind:
image: docker:1.12.3-dind
privileged: true
基本的にDockerコンテナ内でさらにDockerコンテナを立てることはできないわけですが、privileged(特権)モードで起動することでDocker in Dockerが可能になり、中のDockerもデバイスにアクセスすることができるようになります。
このdocker-compose.ymlがあるディレクトリで、docker-composeを使ってdindコンテナを起動します。正しく起動すると、docker psコマンドでコンテナの存在を確認できるはずです。
$ docker-compose up -d
Creating dockercomposemultiple_dind_1
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e7090c2fc2e3 docker:1.12.3-dind "dockerd-entrypoint.s" 9 hours ago Up 25 seconds 2375/tcp dockercomposemultiple_dind_1
複数docker-compose環境
いよいよ本題で、複数docker-compose環境をつくります。まずはdindを使ってDocker上に複数のDockerホストを立てます。これをdocker-composeで書くとこんな感じになります。
version: "2"
services:
dind_a:
image: docker:1.12.3-dind
privileged: true
tty: true
ports:
- 5301:2375 # dockerのポート
- 9001:80 # dind内のwebサーバのポート
dind_b:
image: docker:1.12.3-dind
privileged: true
tty: true
ports:
- 5302:2375
- 9002:80
dind_c:
image: docker:1.12.3-dind
privileged: true
tty: true
ports:
- 5303:2375
- 9003:80
dind_a, dind_b, dind_cという3つのdindコンテナを立ててます。また、各dindのDockerポートのポート2375をローカルでポート衝突しないように5301, 5302, 5303と割り当て。さらに、dindの中でWebサーバ(Nginxだけど)を立てるので、9001-9003のポートでそれぞれバインド。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2bc566a17f71 docker:1.12.3-dind "dockerd-entrypoint.s" 3 minutes ago Up 3 minutes 0.0.0.0:9003->80/tcp, 0.0.0.0:5303->2375/tcp dockercomposemultiple_dind_c_1
d3a784a2cac5 docker:1.12.3-dind "dockerd-entrypoint.s" 4 minutes ago Up 3 minutes 0.0.0.0:9001->80/tcp, 0.0.0.0:5301->2375/tcp dockercomposemultiple_dind_a_1
44ec044653da docker:1.12.3-dind "dockerd-entrypoint.s" 4 minutes ago Up 3 minutes 0.0.0.0:9002->80/tcp, 0.0.0.0:5302->2375/tcp dockercomposemultiple_dind_b_1
それぞれのdind Dockerにはこのポートを利用してローカルからTCPベースでアクセスが可能。例えば、DockerのRemote APIを大元のホストからはこんな感じで叩くことができます。
$ curl http://127.0.0.1:5301/containers/json
[]
docker-composeを複数管理する
各dindコンテナに対応するdocker-composeをそれぞれ作成します。先程のdocker-composeが親dockerのものなら、これは子dockerのためのものです。ディレクトリ構成はこんな感じ。
(path-to-path)----docker-compose.yml
|--service_a
|--docker-compose.yml
|--service_b
|--docker-compose.yml
|--service_b
|--docker-compose.yml
service_a, b, cはもちろんdind_a, dind_b, dind_cに対応。しかし、docker-compose入れ子構成を実際にどのように扱うのかという疑問が残るはずです。
direnv
direnvという便利ツールがあります(Homebrewでインストール可能)。direnvを利用すれば、移動した先のディレクトリによって環境変数を変えるということができます。これを利用します。 Dockerは環境変数によって、利用対象のDockerホストを変えることができます。この特性を利用すれば、各dindに対応したディレクトリに移動した際に、あたかもdindコンテナにSSHログインしたかのように各dind内のdocker操作ができる、というわけです。
direnvでserviceディレクトリ別の環境変数設定
各サービスディレクトリで環境変数を設定します。
(service_a) $ direnv edit .
とするとvimが開くので、環境変数DOCKER_HOSTを設定します。設定する値はdindのTCPアドレスです。service_aであれば5301をバインドしているので、以下のようになります。
export DOCKER_HOST="tcp://127.0.0.1:5301"
dindコンテナ上のDockerで、Nginxを動かす
実際にNginxをdindのDocker上で動かして見ましょう。service_aのdocker-compose.ymlを以下のように用意します。
version: "2"
services:
nginx:
image: nginx:latest
ports:
- 80:80
何の変哲もないデフォルトのNginxです。素のNginxは80でHTTPをListenしており、dindコンテナの80ポートにバインドします(ローカルの80ではない)。大元のdocker-composeでは、dindの80ポートを9001-9003でそれぞれバインドしています。つまり、ローカルからは http://localhost:9001
- http://localhost:9003
でアクセスできるという算段です。
service_aディレクトリでdocker-composeを操作して、dind上のNginxを起動します。
(service_a) $ docker-compose up -d
Starting servicea_nginx_1
(service_a) $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4b035809daf9 nginx:latest "nginx -g 'daemon off" 17 minutes ago Up 30 seconds 0.0.0.0:80->80/tcp, 443/tcp servicea_nginx_1
dindコンテナ上でNginxコンテナが起動しました。では、この状態で一つ上のディレクトリに戻ってみると・・・
(service_a) $ cd ..
(root) $ docker ps
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2bc566a17f71 docker:1.12.3-dind "dockerd-entrypoint.s" 16 minutes ago Up 16 minutes 0.0.0.0:9003->80/tcp, 0.0.0.0:5303->2375/tcp dockercomposemultiple_dind_c_1
d3a784a2cac5 docker:1.12.3-dind "dockerd-entrypoint.s" 17 minutes ago Up 16 minutes 0.0.0.0:9001->80/tcp, 0.0.0.0:5301->2375/tcp dockercomposemultiple_dind_a_1
44ec044653da docker:1.12.3-dind "dockerd-entrypoint.s" 17 minutes ago Up 16 minutes 0.0.0.0:9002->80/tcp, 0.0.0.0:5302->2375/tcp dockercomposemultiple_dind_b_1
このように、dindの3つのコンテナが表示されます。環境変数によって、ターゲットとなるDockerホストが変わったからですね。
では最後に、dind上のNginxにアクセスしてみましょう。これはもちろんどこのディレクトリだろうが、ブラウザからだろうが関係なく見れます。
$ curl http://localhost:9001
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
これはdind上のNginxが返したレスポンス。このような構成にすると、完全に独立したdocker-compose環境を複数同時に稼働させることが可能ですね。
まとめ
このdindネタ、まだまだ色々応用が効きます。本当はこのdocker-composeをそのままCIに持っていってE2Eテストをする、っていうネタまで書きたかったけどあまりにもボリュームが大きくなりすぎだし、今日はここまでにしておきます。