Docker Advent Calendar 2016の10日目の記事です。土曜日は子供と遊んでいると思うので1日早く金曜日に公開しちゃいます。
Dockerでコンテナイメージを作成するときにはDockerfileを利用するのが最も一般的ですが、標準のdocker buildはインストラクション毎に中間レイヤーを作成し保存することを強制するようになっており、イメージ肥大化およびイメージビルドのパフォーマンス低下を避けるために、ユーザはコマンドを&&
で繋げるRUN
インストラクションを必要最小限な数になるよう最適化した奇妙なDockerfileを記述するという理不尽な作業が必要となります。
この強制レイヤリングは一応イメージビルド時の試行錯誤をしているときに中間までキャッシュできるので良い、という建前になっており、最初のうちはちょっと便利かなぁと思ったりもしますが、要件が固まっているベースレイヤは名前つけて必要ならpushもしちゃってFROM
に書くと思いますので、実際に慣れてくるとこの仕組みはただ単に遅くてイライラするだけのものになり、Dockerfileの見た目も汚なくなってしまいます。
実例をみてみましょう。ENV
とRUN
が3つずつある単なるDockerfileです。イメージの読み出しと保存があるので、ビルドの実行には3秒もあれば良いと考えるのが妥当でしょうか。実行環境はThinkPad T440s, 12GB mem, SSD storage, Fedora 25です。
FROM fedora ENV FOO1=BAR1 ENV FOO2=BAR2 ENV FOO3=BAR3 RUN pwd RUN pwd RUN pwd
標準のdocker build
を実行してみます。13秒かかりました。
$ docker -v Docker version 1.12.3, build 97974ae/1.12.3 $ time docker build . --no-cache -t normal-dockerbuild:latest Sending build context to Docker daemon Step 1 : FROM fedora ---> 5bafccc4d5dc Step 2 : ENV FOO1 BAR1 ---> Running in fa7318e71dac ---> 7751ce4fb55e Removing intermediate container fa7318e71dac Step 3 : ENV FOO2 BAR2 ---> Running in e9f8c9062182 ---> d4240ff674a4 Removing intermediate container e9f8c9062182 Step 4 : ENV FOO3 BAR3 ---> Running in 9108d53e3193 ---> 2b1dd6f364fb Removing intermediate container 9108d53e3193 Step 5 : RUN pwd ---> Running in f77b5b36eb26 / ---> e22d9ef35156 Removing intermediate container f77b5b36eb26 Step 6 : RUN pwd ---> Running in 43e1e4b3a758 / ---> 6e5bec6562f6 Removing intermediate container 43e1e4b3a758 Step 7 : RUN pwd ---> Running in d6a73a87c283 / ---> b8a0040efd28 Removing intermediate container d6a73a87c283 Successfully built b8a0040efd28 real 0m13.206s user 0m0.041s sys 0m0.056s $ docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE normal-dockerbuild latest b8a0040efd28 1 seconds ago 199.9 MB <none> <none> 6e5bec6562f6 3 seconds ago 199.9 MB <none> <none> e22d9ef35156 5 seconds ago 199.9 MB <none> <none> 2b1dd6f364fb 8 seconds ago 199.9 MB <none> <none> d4240ff674a4 10 seconds ago 199.9 MB <none> <none> 7751ce4fb55e 12 seconds ago 199.9 MB docker.io/fedora latest 5bafccc4d5dc 2 weeks ago 199.9 MB
次のテストのために作ったイメージは一旦削除します。
$ docker rmi normal-dockerbuild:latest $ docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi
ここでおもむろにOpenShiftのリリースからocクライアントをダウンロードします。今回利用したのはv1.4.0-rc1のLinux版です。OpenShiftにはdocker buildと同等のoc ex dockerbuild
コマンドがあり、成果物はdocker build
と同じですが、中間レイヤをDocker daemonに投げることはしません。
oc ex dockerbuild
を実行してみます。2.5秒でビルドできました。このケースでは標準のdocker build
より5倍高速にビルドできています。
$ time ./oc ex dockerbuild . ex-dockerbuild:latest --> FROM fedora --> ENV FOO1=BAR1 --> ENV FOO2=BAR2 --> ENV FOO3=BAR3 --> RUN pwd / --> RUN pwd / --> RUN pwd / --> Committing changes to ex-dockerbuild:latest ... --> Done real 0m2.543s user 0m0.205s sys 0m0.011s $ docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE ex-dockerbuild latest 6b854accd835 1 seconds ago 199.9 MB docker.io/fedora latest 5bafccc4d5dc 2 weeks ago 199.9 MB
また、Dockerfileを利用しないコンテナイメージビルドの方法として、Ansible Containerというのも絶賛開発中ですのでお試しください。DockerfileではなくAnsibleを利用してコンテナイメージをビルドします。
テストに利用したスクリプトを以下に置いておきます。
#!/bin/bash set -x docker ps -q -f status=exited | xargs --no-run-if-empty docker rm docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi docker images -a time docker build . --no-cache -t normal-dockerbuild:latest docker images -a docker rmi normal-dockerbuild:latest docker ps -q -f status=exited | xargs --no-run-if-empty docker rm docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi time ./oc ex dockerbuild . ex-dockerbuild:latest docker images -a docker rmi ex-dockerbuild:latest
Storage Driverの差異について追記
ブコメでStorage Driverがdevicemapperのせいで差が大きく出ているのでは、という指摘があった(いいご指摘です、ありがとうございます)ので、overlayfsでの結果も載せておきます。差は縮まりましたが4倍の差がでており十分に高速です。
$ time docker build . --no-cache -t normal-dockerbuild:latest real 0m6.784s user 0m0.043s sys 0m0.053s $ time ./oc ex dockerbuild . ex-dockerbuild:latest real 0m1.511s user 0m0.172s sys 0m0.013s
個人的にはもうdockerは専用のクラスタへオフロードしてしまって手元のラップトップでは動作させていない、かつ最近リリースされたFedora 25にしたときにすっかりStorage Driverの設定を忘れていたので、追記前の結果はもっさりdevicemapper loopbackでした。