高度な Dockerfiles: BuildKit とマルチステージビルドを使用したビルドの高速化とイメージの縮小

9月 2024 日更新: 今日、ほとんどの人がBuildKitを使用しており、ブログはこれを反映するように更新されています。

Dockerfiles のマルチステージ ビルド機能を使用すると、より優れたキャッシュとセキュリティ フットプリントを備えた小さなコンテナー イメージを作成できます。 このブログ記事では、ビルドステージとランタイムステージの間でファイルをコピーするだけでなく、機能を最大限に活用できる、より高度なパターンをいくつか紹介します。 マルチステージビルドを初めて使用する場合は、まず使用ガイドを読むことから始めることをお勧めします。

ビルドキットに関する注記

ここにあるすべてのパターンは古いビルダーでも機能しますが、それらの多くは BuildKit バックエンドを使用するとより効率的に実行されます (バージョン 23.0以降の Docker Engine のデフォルト)。 たとえば、BuildKit は未使用のステージを効率的にスキップし、可能な場合はステージを同時にビルドします。 これらのケースを個々の例の下にマークしました。 これらのパターンを使用する場合は、BuildKit を有効にすることを強くお勧めします。 他のすべての BuildKit ベースのビルダーもこれらのパターンをサポートしています。

• • •

ステージからの継承

マルチステージ ビルドでは、いくつかの新しい構文の概念が追加されました。 まず、コマンドで始まるFROMAS stagenameステージに名前を付け、コマンドでCOPYオプションを使用して--from=stagename、そのステージからファイルをコピーできます。実際、コマンドと--fromフラグにははるかに多くの共通点があり、FROMそれらが同じ名前であることは偶然ではありません。どちらも同じ引数を取り、それを解決してから、その時点から新しいステージを開始するか、ファイルコピーのソースとして使用します。

つまり、使用できる--from=stagenameのと同じ方法で、前のステージを現在のステージのソースイメージとして使用することもできますFROM stagename。 これは、Dockerfile 内の複数のコマンドが同じ共通部分を共有している場合に便利です。 これにより、共有コードが小さくなり、子ステージを分離したままにして、子ステージを分離して、一方が再構築されたときに他のステージのビルドキャッシュが無効にならないようにします。 各ステージは、 を呼び出してdocker buildフラグを使用して--target個別に構築することもできます。

FROM ubuntu AS base
RUN apt-get update && apt-get install git

FROM base AS src1
RUN git clone …

FROM base AS src2
RUN git clone …

BuildKit では、この例の 2 番目と 3 番目のステージが同時にビルドされます。

画像を直接使用する

以前は画像参照のみをサポートしていたコマンドでFROMビルドステージ名を使用するのと同様に、これを好転させて、フラグ付きの--from画像を直接使用できます。 これにより、他の画像から直接ファイルをコピーできます。 たとえば、次のコードでは、image を使用して linuxkit/ca-certificates TLS CA ルートを現在のステージに直接コピーできます。

FROM alpine
COPY --from=linuxkit/ca-certificates / /

共通イメージのエイリアス

ビルドステージにはコマンドを含める必要はなく、1FROM行でもよい。 複数の場所でイメージを使用している場合、これは読みやすさを向上させ、共有イメージを更新する必要があるときに 1 行だけを変更する必要があることを確認するのに役立ちます。

FROM alpine:3.6 AS alpine

FROM alpine
RUN

FROM alpine
RUN

この例では、画像 alpineを使用する場所は実際には alpine:3.6 そうではありません alpine:latest。 に更新alpine:3.7するときは、 変更する必要があるのは1行だけで、ビルドのすべての部分が更新されたバージョンを使用していることを確認できます。

これは、エイリアスで build 引数を使用するとさらに強力になります。 次の例は前の例と同じですが、ユーザーはオプションを設定する--build-arg ALPINE_VERSION=valueことで、このビルドで使用されているすべてのインスタンスをオーバーライドできます。コマンドで使用されるFROM引数は、最初のビルド段階の前に定義する必要があることに注意してください。

ARG ALPINE_VERSION=3.6

FROM alpine:${ALPINE_VERSION} AS alpine

FROM alpine
RUN

'‐‐from' でのビルド引数の使用

コマンドの COPY flag で指定された--from値にビルド引数を含めることはできません。たとえば、次の例は無効です。

// THIS EXAMPLE IS INTENTIONALLY INVALID

FROM alpine AS build-stage0
RUN

FROM alpine
ARG src=stage0
COPY --from=build-${src} . .

これは、ビルドを開始する前にステージ間の依存関係を決定する必要があるため、毎回すべてのコマンドを評価する必要がないためです。 たとえば、image でalpine定義された環境変数は、値の評価--fromに影響を与える可能性があります。 コマンドの引数を評価できる理由は、FROMこれらの引数がステージを開始する前にグローバルに定義されているためです。 幸いなことに、前に学習したように、1 つのFROMコマンドでエイリアス ステージを定義し、代わりにそれを参照することができます。

ARG src=stage0
FROM alpine AS build-stage0
RUN

FROM build-${src} AS copy-src
FROM alpine
COPY --from=copy-src . .

ビルド引数srcをオーバーライドすると、最終COPY要素のソースステージが切り替わるようになりました。 これにより一部のステージが使用されなくなった場合、BuildKit ベースのビルダーのみがこれらのステージを効率的にスキップして実行されないようにする機能を持っていることに注意してください。

ビルド引数を使用する条件

Dockerfiles にスタイル条件のサポートを追加するIF/ELSE要求がありました。 このようなものが将来追加されるかどうかはまだ不明です — BuildKit のカスタムフロントエンドサポートの助けを借りて、将来それを試すかもしれません。 一方、いくつかの計画では、現在の多段階の概念を使用して同様の動作を得る可能性があります。

// THIS EXAMPLE IS INTENTIONALLY INVALID

FROM alpine
RUN
ARG BUILD_VERSION=1

IF $BUILD_VERSION==1
RUN touch version1
ELSE IF $BUILD_VERSION==2
RUN touch version2
DONE

RUN

前の例は、 を使用して条件を記述する方法を擬似コードで示IF/ELSEしています。 現在のマルチステージビルドで同じ動作を行うには、異なるブランチ条件を個別のステージとして定義し、引数を使用して正しい依存関係パスを選択する必要があります。

ARG BUILD_VERSION=1

FROM alpine AS base
RUN

FROM base AS branch-version-1
RUN touch version1

FROM base AS branch-version-2
RUN touch version2

FROM branch-version-${BUILD_VERSION} AS after-condition

FROM after-condition
RUN

この Dockerfile の最後のステージは、ビルド引数によってBUILD_VERSION解決されるイメージのエイリアスであるステージに基づいていますafter-condition。の値に応じてBUILD_VERSION、異なる中間セクション ステージが選択されます。

BuildKit ベースのビルダーのみが未使用のブランチをスキップできることに注意してください。 以前のビルダーでは、すべてのステージが引き続きビルドされていましたが、最終的なイメージを作成する前にその結果が破棄されていました。

最小限の生産段階のための開発/テストヘルパー

最後に、前のパターンを組み合わせて、最小限の運用イメージを作成し、その内容を使用してテストの実行や開発イメージを作成できる Dockerfile を作成する方法を示す例を紹介します。 基本的な Dockerfile の例から始めます。

FROM golang:alpine AS stage0

FROM golang:alpine AS stage1


FROM scratch
COPY --from=stage0 /binary0 /bin
COPY --from=stage1 /binary1 /bin

これは、最小限の運用イメージを作成する場合によく発生します。 しかし、代替の開発者イメージを取得したり、最終段階でこれらのバイナリを使用してテストを実行したりする場合はどうでしょうか。 明らかな方法は、同じバイナリをテスト段階と開発者段階にもコピーすることです。 問題は、すべての本番バイナリを同じ組み合わせでテストするという保証がないことです。 最終段階で何かが変更され、他のステージに同じ変更を加えるのを忘れたり、バイナリがコピーされるパスを間違えたりする可能性があります。 結局のところ、個々のバイナリではなく、最終的なイメージをテストしたいと思います。

別のパターンは、運用ステージの後に開発者とテスト ステージを定義し、運用ステージの内容全体をコピーすることです。 その後、実動ステージの 1 つのFROMコマンドを使用して、実動ステージを最後のステップとして再びデフォルトにすることができます。

FROM golang:alpine AS stage0


FROM scratch AS release
COPY --from=stage0 /binary0 /bin
COPY --from=stage1 /binary1 /bin

FROM golang:alpine AS dev-env
COPY --from=release / /
ENTRYPOINT ["ash"]

FROM golang:alpine AS test
COPY --from=release / /
RUN go test …

FROM release

デフォルトでは、このDockerfileはデフォルトの最小イメージの構築を続行します。
 --target=dev-envオプションは、常に完全なリリースバイナリを含むシェルでイメージをビルドするようになりました。

• • •

これがお役に立てば幸いで、より効率的な多段階Dockerfileを作成するためのアイデアが得られました。 BuildKit リポジトリを使用して、より効率的なビルドと新しい Dockerfile 機能のために新しい開発を追跡できます。サポートが必要な場合は、 Docker コミュニティ Slack #buildkit チャンネルに参加できます。