マルチステージ¶
このセクションは
構築ステップを並列に実行できるため、構築パイプラインをより速くより効率的にできる。
プログラムを実行するために必要なものだけを含む、形跡が最小の最終イメージを作成できる。
Dockerfile 内では FROM
命令によって
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c021c8a7051f 5 seconds ago 150MB
プログラムは実行可能なバイナリにコンパイルされているため、最終イメージ内に Go 言語ユーティリティは不要になります。
ステージの追加¶
マルチステージビルドの使用により、構築用と実行環境用で、異なるベースイメージが選べます。
以下のように Dockerfile を編集します。この変更により、最小の scratch
イメージをベースとして使う別のステージを作成します。最終の scratch
ステージでは、前のステージで構築されたバイナリを、新しいステージのファイルシステムへとコピーしています。
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
+
+ FROM scratch
+ COPY --from=0 /bin/client /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
これで、イメージを構築して調査したら、値が著しく小さくなったと分かるでしょう。
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 436032454dd8 7 seconds ago 8.45MB
150MB だったイメージは、ほんの 8.45MB になりました。最終的なイメージに含まれるのはバイナリだけであり、他になにもないからです。
並列処理¶
これまでは、イメージの形跡を減らしました。以下の手順では、マルチステージビルドの並列処理を使い、構築速度を改善できる方法を見ていきます。現時点の構築は、次々にバイナリを作成します。しかし、サーバの前にクライアントを作成する必要はなく、その逆の場合も同じです。
バイナリの構築ステップを、個々のステージに分割できます。最後の scratch
ステージでは、それぞれの適切な構築ステージからバイナリをコピーします。それぞれの構築をステージに分ければ、 Docker は並列でそれらを実行できます。
構築用のステージでは、Go コンパイルツールとアプリケーション依存関係の、それぞれのバイナリが必要です。これらに共通するステップを、再利用可能な FROM イメージ AS ステージ名
のパターンを使い、ステージに対して名前を割り当てます。これでそのステージ名は、他のステージの FROM
命令内で参照できるようになります( FROM ステージ名
)。
また、バイナリ構築ステージでも名前を割り当て可能なため、バイナリを最終 scratch
イメージにコピーするには COPY --from=ステージ名
としてステージ名を参照します。
# syntax=docker/dockerfile:1
- FROM golang:{{site.example_go_version}}-alpine
+ FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
+
+ FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
+
+ FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch
- COPY --from=0 /bin/client /bin/server /bin/
+ COPY --from=build-client /bin/client /bin/
+ COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
これで、最初のバイナリを次々に構築する方法に替わり、 build-client
と build-server
ステージが同時に処理されます。
構築ターゲット¶
最終イメージは小さくなり、並列処理を使って構築できるようになりました。しかし、このイメージは少し変わってて、同じイメージ内にクライアントとサーバ両方のイメージが入っています。これらは2つのイメージに分けるべきではないでしょうか。
1つの Dockerfile を使って、複数のイメージの作成が可能です。 --target
フラグを使い、構築の FROM scratch
ステージの名前を書き換え、 client
と server
の異なる2つのステージにします。
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
- FROM scratch
- COPY --from=build-client /bin/client /bin/
- COPY --from=build-server /bin/server /bin/
- ENTRYPOINT [ "/bin/server" ]
+ FROM scratch AS client
+ COPY --from=build-client /bin/client /bin/
+ ENTRYPOINT [ "/bin/client" ]
+ FROM scratch AS server
+ COPY --from=build-server /bin/server /bin/
+ ENTRYPOINT [ "/bin/server" ]
これで、クライアントとサーバのプログラムが分けられた Docker イメージ(タグ)を構築できます。
$ docker build --tag=buildme-client --target=client .
$ docker build --tag=buildme-server --target=server .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme-client latest 659105f8e6d7 20 seconds ago 4.25MB
buildme-server latest 666d492d9f13 5 seconds ago 4.2MB
イメージは更に小さくなり、それぞれ 4 MB です。
この変更は、バイナリをそれぞれ構築する必要もありません。 client
ターゲットの構築を選ぶ場合、 Docker は対象となるターゲットが必要なステージのみ構築します。 build-server
と server
ステージは必要がなければ飛ばします。同様に、 server
ターゲットの構築では、 build-client
と client
ステージを飛ばします。