マルチステージ・ビルドを使う¶
マルチステージ・ビルド(multi-stage build)は Docker 17.05 以上のデーモンとクライアントを必要とする新機能です。
マルチステージ・ビルドの前に¶
イメージ構築にあたり最もチャレンジングなものの1つに、イメージ容量を小さくし続けるというものがあります。Dockerfile 中の命令ごとにイメージにレイヤを追加するため、次のレイヤへと移る前に、不要なアーティファクトを忘れずクリーンアップし続ける必要があります。本当に効率的な これまで Dockerfile を書くためには、以降のレイヤで必要になるアーティファクトのみを保持するために、シェル芸(shell tricks)を駆使する必要と、レイヤを維持するロジックを用いる必要がありました。
実際にとても一般的になったのは、開発用途に1つのファイル(アプリケーションの構築に必要な全てを含む)を用いることです。そして、プロダクション向けに、そのアプリケーションを実行するために必要なものだけを含む、1つのレイヤへとスリムダウンすることです。これが従来はずっと「構築パターン(builder pattern)」として参照されていました。しかし、2つの Dockerfile を持つのは、理想的ではありません。
以下の Dockerfile.build
と Dockerfile
は、この構築パターンを遵守した例です。
Dockerfile.build
:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY app.go .
RUN go get -d -v golang.org/x/net/html \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
また、この例では2つの RUN
コマンドを Bash の &&
演算子を使い1行にまとめ、イメージ中に追加レイヤが増えないのを防いでいます。ですが、これは失敗しがちでメンテナンスが大変です。これは、他のコマンドの連結が簡単ですが、場合によっては行の最後で \
文字を使うのを忘れがちです。
Dockerfile
:
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
build.sh
:
#!/bin/sh
echo Building alexellis2/href-counter:build
docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
-t alexellis2/href-counter:build . -f Dockerfile.build
docker container create --name extract alexellis2/href-counter:build
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker build --no-cache -t alexellis2/href-counter:latest .
rm ./app
build.sh
スクリプトを実行するにあたり、まずイメージを構築する必要があります。コンテナを作成し、そこからアーティファクトをコピーし、2つめに構築するイメージにコピーします。ローカルディスク上で両イメージがシステム上で場所を取るだけでなく、 app
アーティファクトも同様に場所をとります。
マルチステージ・ビルドは、この状況をとてもシンプルにします。
マルチステージ・ビルドを使う¶
マルチステージ・ビルドでは、 Dockerfile の中で複数の FROM
命令文を使います。各 FROM
命令は、異なるベースを使い、それを使って新しい構築ステージを始めます。あるステージから別のステージに対し、コピーするアーティファクトを選べるため、最終イメージで不要なすべてを残したままにできます。これがどのような挙動か確認するために、先ほどのセクションで使った Dockerfile をマルチステージ・ビルドに対応させましょう。
Dockerfile
:
FROM golang:1.7.3
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
あなたが必要なのは1つの Dockerfile だけです。構築スクリプトを分ける必要はありません。 docker build
を実行するだけです。
$ docker build -t alexellis2/href-counter:latest .
最終結果は、先ほどと同じ小さなプロダクション・イメージですが、複雑さは極めて減少しました。もうこれで中間イメージを作成する必要はありませんし、ローカルシステム上にアーティファクトを展開する必要も、もうありません。
どのような挙動でしょうか? 2つめの FROM
命令は、 alpine:latest
をベースとして新しい構築ステージを開始します。 COPY --from=0
行が、以前のステージで構築したアーティファクトを、この新しいイメージの中にコピーします。Go SDK や他の中間アーティファクトは残したままであり、最終イメージの中に保存しません。
構築ステージに名前を付ける¶
デフォルトでは、ステージに名前がなく、ステージを 0 で始まる整数値で参照します。しかし、 FROM
命令の中で AS <名前>
を追加することにより、ステージに対して名前を付けられます。先ほどの例を改善し、ステージに対して名前を付け、その名前を COPY
命令で使います。つまり、Dockerfile に記述する( FROM )命令の順番を入れ替えたとしても、 COPY
命令は壊れません。
FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
特定の構築ステージ後に停止¶
イメージの構築時、Dockerfile 含まれる各イメージを全て構築する必要はありません。特定のターゲット(target)構築ステージを指定できます。以下のコマンドは、以前の Dockerfile
を使いますが、 builder
という名前のステージで停止します。
$ docker build --target builder -t alexellis2/href-counter:latest .
いくつかの場合に、これが非常にパワフルになるでしょう。
- 特定の構築ステージをデバッグする用途
- デバッグ用の目印として
debug
ステージを使うか、ツールを有効化することで、production
ステージをスリムにする用途 testing
イメージを使い、アプリがテストデータを処理できるようにしますが、プロダクションが使う別のステージ構築時には実際のデータを使う用途
外部イメージを「ステージ」として使う¶
マルチステージ・ビルドを使う時、 Dockerfile でこれまで作成済みのステージからコピーするだけ、という制限はありません。 COPY --from
命令で別のイメージからコピーできるだけでなく、ローカルで利用可能なイメージとタグの利用や、Docker レジストリ上やタグ ID ですらも利用できます。それらからアーティファクトのコピーが必要であれば、Docker クライアントはイメージを取得します。構文は次の通りです。
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
以前のステージを新しいステージとして使う¶
以前のステージを残したまま、そこを``FROM`` 命令を使って参照できます。以下は例です。
FROM alpine:latest as builder
RUN apk --no-cache add build-base
FROM builder as build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp
FROM builder as build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
参考
- Use multi-stage builds
- https://docs.docker.com/develop/develop-images/multistage-build/