はじめに
Dockerコンテナ内でdockerコマンドが使いたい。
このエントリは、いわゆるDocker in Dockerでネストしたいわけではなくて、同じdockerデーモンを共有する兄弟コンテナを立てたい場合の話です。
どういうユースケースかというと、
- 社内Heroku的なアプリケーションをDockerコンテナで動かして、Web画面をポチポチするとdocker service createしたいとか
- HubotをDockerコンテナで動かして、特定の発言に反応してdocker runしたいとか
- JenkinsをDockerコンテナで動かして、CIの実行でdocker runしたいとか
Dockerを使ってるとなんでもDockerにしたい病になるので、Dockerコンテナを起動するような必要があるアプリケーションもDockerで管理したい。
作戦としては必要なイメージにdockerクライアントのバイナリだけをインストールしておいて、
docker runするときにホスト側の/var/run/docker.sockをマウントする。
dockerクライアントだけをインストール
dockerコマンドだけ欲しいんだけど、普通にDockerをインストールするとデーモンも付いてくるし、インストール処理でいろいろ設定変更されるので、そういうのはして欲しくないんだ。
というわけで何か良い方法ないかなと思ってdockerのissueを眺めてたら同じような話があった
Release client-only binaries for Linux
要するにビルド済のバイナリはクライアントとデーモンで分かれたので、リリースページにダウンロードURL書いてあるからアーカイブを展開して必要なバイナリファイルだけコピーしてねということらしい。
https://github.com/docker/docker/releases
なるほど。やってみよう。ミニマムな検証コード。以下のDockerfileを準備する。
FROM debian:jessie
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
ENV DOCKER_CLIENT_VERSION=1.12.3
ENV DOCKER_API_VERSION=1.24
RUN curl -fsSL https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_CLIENT_VERSION}.tgz \
| tar -xzC /usr/local/bin --strip=1 docker/docker
最初にcurlがなかったので、curlを入れてる。
で、簡単にワンライナーの解説しておくと、curlで必要なバイナリをダウンロードしてきたのをパイプでtarに渡して展開している。
このとき、 --strip=1
で展開時のディレクトリ作成を抑止し、 docker/docker
が抽出したいファイルを指定して、 -C
で展開先のパスを /usr/local/bin
に指定することで中間ファイルを一切残さずに直接1ファイルだけ配置している。
ちなみに DOCKER_API_VERSION
の環境変数を設定しているのは、dockerデーモンの方がバージョンが古くて通信できないときに、環境変数を上書きしてAPIをダウングレードする用です。dockerのバージョンとAPIバージョンの対応関係は以下を参照。
稼働確認
よし。稼働確認してみよう。ポイントとして起動する時に -v
でホスト側のdocker.sockをマウントします。
コンテナ側にホストのアクセスを許可しているのでセキュリティ的にリスクがあることはご注意下さい。
$ docker build -t docker-client ./
$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock docker-client /bin/bash
root@68a109a5d6e2:/# docker version
Client:
Version: 1.12.3
API version: 1.24
Go version: go1.6.3
Git commit: 6b644ec
Built: Wed Oct 26 23:26:11 2016
OS/Arch: linux/amd64
Server:
Version: 1.12.1
API version: 1.24
Go version: go1.6.3
Git commit: 23cf638
Built: Thu Aug 18 17:52:38 2016
OS/Arch: linux/amd64
root@68a109a5d6e2:/# docker run nginx nginx -V
nginx version: nginx/1.11.5
built by gcc 4.9.2 (Debian 4.9.2-10)
built with OpenSSL 1.0.1t 3 May 2016
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed'
うん。よさげ。
dockerコマンドのバイナリだけ入ったDockerfile作っておくか。。。あ、?
ここまで書いてdockerコマンドのバイナリだけ入ったDockerfile作っておいたら便利そうだなと思って、
リポジトリを作ろうとしてふと誰かもう作ってないかDocker Hubを調べたら、
公式のdocker自体のDockerfileが似たようなことをしていた。
ワンライナーじゃないし、クライアント以外のバイナリもコピーしちゃってるけど。
そうか公式か。あとから考えるとあってしかるべきなんだけど。その発想はなかった。
公式の方がチェックサム確認してたりするので、ちゃんとしてます。
ちゃんとチェックサムみたい人は公式のDockefileを参考にするとよいんじゃなかろうか。
そして、よくよく考えたら、何かのベースのイメージがあってそのなかでdockerコマンドが使いたいんであって、
dockerコマンドだけ使えるDockerfileあっても、ホストのdockerコマンド使えよってだけで、あんまりうれしくないよね。
Fromで継承するのはたぶんそのアプリケーションの言語のランタイムになるだろう。
というわけでdockerコマンドが必要なイメージにさっきのcurlのワンライナーをコピペする運用で十分な気がする。
まとめ
Dockerコンテナ内でdockerコマンドが使い方が分かった。これでdockerizeが捗りますね。