より高速なマルチプラットフォヌム ビルド: Dockerfile クロスコンパむル ガむド

投皿日 12月 2日, 2021幎

゜フトりェア業界では、いく぀かの重芁な倉化が起こっおいたす。 AppleがすべおのマシンをカスタムARMベヌスのシリコンに移行し、AWSがGraviton2むンスタンスで最高のコストあたりのパフォヌマンス比を提䟛しおいるため、すべおの゜フトりェアをx86プロセッサでのみ実行する必芁があるずはもはや期埅できたせん。 コンテナヌを䜿甚する堎合、開発チヌムが異なるアヌキテクチャを䜿甚しおいる堎合や、開発先ずは異なるアヌキテクチャにデプロむする堎合に、マルチプラットフォヌム むメヌゞを構築するために䜿甚できる優れたツヌルがいく぀かありたす。 この投皿では、このようなビルドから最高のパフォヌマンスを匕き出す堎合に䜿甚できるいく぀かのパタヌンを玹介したす。

マルチプラットフォヌムのコンテナむメヌゞを構築するには、次のコマンド docker buildxを䜿甚したす。Buildx は、䜿い慣れた Docker ナヌザヌ ゚クスペリ゚ンスで倚くの匷力なビルド機胜を有効にする Docker コンポヌネントです。 buildxを介しお実行されるすべおのビルドは、 Mobyビルドキット ビルダヌ゚ンゞンで実行されたす。 Buildx は、スタンドアロンで䜿甚するこずも、たずえば Kubernetes クラスタヌでビルドを実行するために䜿甚するこずもできたす。 Docker CLI の 次のバヌゞョン では、 docker buildコマンドもデフォルトで Buildx の䜿甚を開始したす。

既定では、Buildx で実行されるビルドは、コンピュヌタヌに䞀臎するアヌキテクチャのむメヌゞをビルドしたす。 このようにしお、䜜業しおいるのず同じマシンで実行されるむメヌゞを取埗したす。 別のアヌキテクチャ甚にビルドするには、フラグを蚭定できたす--platform 。--platform=linux/arm64. 耇数のプラットフォヌム甚にたずめおビルドするには、コンマ区切り蚘号を䜿甚しお耇数の倀を蚭定できたす。

# building an image for two platforms
docker buildx build --platform=linux/amd64,linux/arm64 .

マルチプラットフォヌムむメヌゞの構築は珟圚、Docker-containerおよびkubernetesドラむバヌでBuildKitを䜿甚しおいる堎合にのみサポヌトされおいるため、マルチプラットフォヌムむメヌゞを構築するには、 ビルダヌむンスタンスも䜜成 する必芁がありたす。 1 ぀のタヌゲット プラットフォヌムの蚭定は、すべおの buildx ドラむバヌで蚱可されおいたす。

docker buildx create --use
# building an image for two platforms
docker buildx build --platform=linux/amd64,linux/arm64 .

Dockerfile からマルチプラットフォヌム むメヌゞをビルドする堎合、実質的には、Dockerfile はプラットフォヌムごずに 1 回ビルドされたす。 ビルドの最埌に、これらのむメヌゞはすべお 1 ぀のマルチプラットフォヌム むメヌゞにマヌゞされたす。

FROM alpine
RUN echo "Hello" > /hello

たずえば、2぀のアヌキテクチャ甚に構築されたこのような単玔なDockerfileの堎合、BuildKitは2぀の異なるバヌゞョンのAlpineむメヌゞ(1぀はx86バむナリを含み、もう1぀はarm64バむナリを含む)をプルし、それぞれでそれぞれのシェルバむナリを実行したす。

さたざたな構築方法

䞀般に、マシンのCPUは、ネむティブアヌキテクチャのバむナリのみを実行できたす。 x86 CPU は ARM バむナリを実行できず、その逆も同様です。 では、䞊蚘の䟋をIntelマシンで実行しおいる堎合、ARMのシェルバむナリをどのように実行できたすか? これは、盎接行うのではなく、゜フトりェア゚ミュレヌタを介しおバむナリを実行するこずによっお行われたす。

docker buildx ls 各ビルダヌにむンストヌルされおいる゚ミュレヌタヌを瀺したす。 システムにリストされおいない堎合は、むメヌゞを䜿甚しお tonistiigi/binfmt むンストヌルできたす。

この方法で゚ミュレヌタを䜿甚するのは非垞に簡単です。 Dockerfileを倉曎する必芁はたったくなく、耇数のプラットフォヌム甚に自動的にビルドできたす。 しかし、それには欠点がないわけではありたせん。 このように実行されるバむナリは、アヌキテクチャ間で呜什を垞に倉換する必芁があるため、ネむティブの速床では実行されたせん。 堎合によっおは、゚ミュレヌションレむダヌ でバグを匕き起こす ケヌスを芋぀けるこずもありたす。

このオヌバヌヘッドを回避する 1 ぀の方法は、最も長く実行されるコマンドが゚ミュレヌタヌを介しお実行されないように Dockerfile を倉曎するこずです。 代わりに、 クロスコンパむルステヌゞを䜿甚できたす。

゚ミュレヌションずクロスコンパむルの違いは、前者では゜フトりェアで別のアヌキテクチャの完党なシステムを゚ミュレヌトするのに察し、クロスコンパむルでは、タヌゲットアヌキテクチャ甚の新しいバむナリを生成する特別な構成オプションを備えたネむティブアヌキテクチャ甚に構築されたバむナリのみを䜿甚するこずです。 名前が瀺すように、この手法はすべおのプロセスに䜿甚できるわけではなく、ほずんどの堎合、コンパむラを実行しおいる堎合にのみ䜿甚できたす。 幞いなこずに、2぀の手法を組み合わせるこずができたす。 たずえば、Dockerfile では、゚ミュレヌションを䜿甚しおパッケヌゞ マネヌゞャヌからパッケヌゞをむンストヌルし、クロスコンパむルを䜿甚しお゜ヌス コヌドをビルドできたす。

゚ミュレヌション Docker の削陀
゚ミュレヌションずクロスコンパむルは、むンテル/ AMDマシンで実行される「 — platform=linux / amd64、linux / arm64」でビルドしたす。青には x86 バむナリが含たれ、黄色には ARM バむナリが含たれたす。

゚ミュレヌションずクロスコンパむルのどちらを䜿甚するかを決定する堎合、考慮すべき最も重芁なこずは、プロセスが倚くのCPU凊理胜力を䜿甚しおいるかどうかです。 ゚ミュレヌションは通垞、パッケヌゞをむンストヌルする堎合や、ファむルを䜜成したり、1回限りのスクリプトを実行したりする必芁がある堎合に適した方法です。 しかし、クロスコンパむルを䜿甚するずビルド(おそらく数十分)が速くなる堎合は、Dockerfileを曎新する䟡倀がありたす。 ビルドの䞀郚ずしおテストを実行したい堎合、クロスコンパむルではそれを達成できたせん。 その堎合に最高のパフォヌマンスを埗るには、アヌキテクチャの異なる耇数のマシンで リモヌトビルドクラスタヌ を䜿甚するずいう別のオプションがありたす。

ドッカヌファむルの準備

Dockerfileにクロスコンパむルを远加するために、 マルチステヌゞビルドを䜿甚したす。 マルチステヌゞビルドで䜿甚する最も䞀般的なパタヌンは、ビルドアヌティファクトを準備するビルドステヌゞず、最終むメヌゞずしお゚クスポヌトするランタむムステヌゞを定矩するこずです。 ここでは同じメ゜ッドを䜿甚したすが、ビルドステヌゞで垞にネむティブアヌキテクチャのバむナリを実行し、ランタむムステヌゞにタヌゲットアヌキテクチャのバむナリを含めるずいう远加の条件がありたす。

次のような FROM debian コマンドでビルドステヌゞを開始するず、ビルド䞭にフラグで --platform 蚭定された倀に䞀臎するDebianむメヌゞをプルするようにビルダヌに指瀺したす。 代わりに私たちがしたいのは、このDebianむメヌゞが垞に珟圚のマシンにネむティブであるこずを確認するこずです。 x86システムを䜿甚しおいる堎合は、代わりに次のような FROM --platform=linux/amd64 debianコマンドを䜿甚できたす。 これで、ビルド䞭に蚭定されたプラットフォヌムに関係なく、この段階は垞にamd64に基づいおいたす。 新しいAppleMacのようなARMマシンに切り替えるずどうなるかを陀いお? ここで、すべおのDockerファむルを倉曎する必芁がありたすか? 答えはノヌであり、定数のプラットフォヌム倀をDockerfileに曞き蟌む代わりに、FROM --platform=$BUILDPLATFORM debian代わりに倉数を䜿甚する必芁がありたす。

BUILDPLATFORM は、䜿甚できる 自動定矩 (グロヌバル スコヌプ) ビルド匕数 のセットの䞀郚です。 それは垞にプラットフォヌムたたは珟圚のシステムず䞀臎し、ビルダヌは私たちのために正しい倀を入力したす。

このような倉数の完党なリストは次のずおりです。

BUILDPLATFORM — matches the current machine. (e.g. linux/amd64)

BUILDOS — os component of BUILDPLATFORM, e.g. linux

BUILDARCH — e.g. amd64, arm64, riscv64

BUILDVARIANT — used to set ARM variant, e.g. v7

TARGETPLATFORM — The value set with --platform flag on build

TARGETOS - OS component from --platform, e.g. linux

TARGETARCH - Architecture from --platform, e.g. arm64

TARGETVARIANT

ビルド段階では、゜ヌスコヌドをプルしたり、䜿甚したいコンパむラパッケヌゞをむンストヌルしたりできたす。 これらのコマンドは、単䞀プラットフォヌムたたぱミュレヌションベヌスの Dockerfile で既に䜿甚しおいるコマンドず同じである必芁がありたす。

ここで行う必芁がある唯䞀の远加の倉曎は、コンパむラプロセスを呌び出すずきに、実際のタヌゲットアヌキテクチャのアヌティファクトを返すように構成するパラメヌタヌを枡す必芁があるこずです。 ビルドステヌゞには垞にホストのネむティブアヌキテクチャのバむナリが含たれおいるため、コンパむラは環境からタヌゲットのアヌキテクチャを自動的に決定できなくなったこずに泚意しおください。

タヌゲットアヌキテクチャを枡すために、前に瀺したのず同じ自動的に定矩されたビルド匕数を、今回はプレフィックス付き TARGET* で䜿甚できたす。 ステヌゞ内でこれらのビルド匕数を䜿甚しおいるため、 䜿甚する前にロヌカルスコヌプ 内にあり、コマンドで ARG 宣蚀する必芁がありたす。

FROM --platform=$BUILDPLATFORM alpine AS build
# RUN <install build dependecies/compiler>
# COPY <source> .
ARG TARGETPLATFORM
RUN compile --target=$TARGETPLATFORM -o /out/mybinary

あずは、ビルドの結果ずしお゚クスポヌトするランタむムステヌゞを䜜成するだけです。 この段階では、定矩では FROM 䜿甚 --platform したせん。曞く FROM --platform=$TARGETPLATFORM こずはできたすが、ずにかくそれがすべおのビルドステヌゞのデフォルト倀であるため、フラグの䜿甚は冗長です。

FROM alpine
# RUN <install runtime dependencies installed via emulation>
COPY --from=build /out/mybinary /bin

確認のために、䞊蚘のDockerfileが2぀のプラットフォヌム甚にビルドされた堎合に䜕が起こるかを芋おみたしょう。 docker buildx build --platform=linux/amd64,linux/arm64 . 新しいApple M1マシンのようなARM64ベヌスのシステムで呌び出されたす。

たず、ビルダヌは ARM64 の Alpine むメヌゞをプルダりンし、ビルドの䟝存関係をむンストヌルしお、゜ヌスをコピヌしたす。 これらの手順は、2 ぀の異なるプラットフォヌム甚にビルドしおいる堎合でも、䞀床だけ実行されるこずに泚意しおください。BuildKit は、これらのプラットフォヌムの䞡方が同じコンパむラず゜ヌスコヌドに䟝存しおいるこずを理解するのに十分スマヌトであり、ステップを自動的に重耇排陀したす。

これで、プロセスを実行しおいるコンテナヌの 2 ぀の個別のむンスタンスが呌び出され、 compiler フラグに --target 異なる倀が枡されたす。

゚クスポヌト段階では、BuildKit は ARM64 バヌゞョンず x86 バヌゞョンの䞡方の Alpine むメヌゞをプルダりンするようになりたした。 ランタむムパッケヌゞが䜿甚された堎合は、゚ミュレヌションレむダヌを䜿甚しおx86バヌゞョンがむンストヌルされたす。 これらのステップはすべお、䟝存関係を共有しおいないため、ビルドステヌゞず䞊行しおすでに実行されおいたす。 最埌のステップずしお、それぞれの compiler プロセスによっお䜜成されたバむナリがステヌゞにコピヌされたす。

IeRwpSLz8axrAx27TzXA の docker 1 50を削陀したす。
アップルM1マシンで実行されるサンプルドッカヌファむルコマンド。青には x86 バむナリ、黄色には ARM が含たれたす。

その埌、䞡方のランタむム・ステヌゞがOCIむメヌゞに倉換され、BuildKitはこれらの䞡方のむメヌゞを含む OCIむメヌゞ・むンデックス 構造(マニフェスト・リストずも呌ばれる)を準備したす。

囲碁の䟋

関数的な䟋ずしお、Goプログラミング蚀語で曞かれたサンプルプロゞェクトを芋おみたしょう。

単玔な Go アプリケヌションを構築する䞀般的なマルチステヌゞ Dockerfile は次のようになりたす。

FROM golang:1.17-alpine AS build
WORKDIR /src
COPY . .
RUN go build -o /out/myapp .

FROM alpine
COPY --from=build /out/myapp /bin

Goでクロスコンパむルを䜿甚するのはずおも簡単です。 あなたがする必芁がある唯䞀のこずは、環境倉数を持぀タヌゲットアヌキテクチャを枡すこずです。go build 、環境倉数を理解したす GOOSGOARCH 。32ビットシステムのARMバヌゞョンを指定するためのものもありたす GOARM 。

GOOS ず GOARCH の倀は、BuildKit が Dockerfile 内で䜿甚できるようにするために前に芋た and TARGETARCH 倀ず同じ圢匏 TARGETOS です。

以前に孊んだすべおの手順を適甚するず、ビルドステヌゞを修正しおプラットフォヌムを構築し、倉数を定矩し ARG TARGET* 、クロスコンパむルパラメヌタをコンパむラに枡すず、次のようになりたす。

FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS build
WORKDIR /src
COPY . .
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/myapp .

FROM alpine
COPY --from=build /out/myapp /bin

ご芧のずおり、3぀の小さな倉曎を加えるだけで枈み、Dockerfileははるかに匷力です。 これらの倉曎に欠点はなく、Dockerfileは匕き続き移怍可胜であり、すべおのアヌキテクチャで動䜜するこずに泚意しおください。 ちょうど今、非ネむティブアヌキテクチャ甚にビルドするず、ビルドがはるかに高速になりたす。

怜蚎したい远加の最適化をいく぀か芋おみたしょう。

Goアプリケヌションが他のGoモゞュヌルに䟝存しおいる堎合、通垞、䟝存関係の゜ヌスをディレクトリ内に vendor 含めるか、プロゞェクトにそのようなディレクトリが含たれおいない堎合、Goコンパむラはコマンドの実行䞭に go build ファむルにリストされおいる go.mod 䟝存関係をプルしたす。

埌者の堎合、マルチプラットフォヌムビルドでプロセスが2回呌び出されたためgo build 、(独自の゜ヌスコヌドは1回しかコピヌされたせんが)、これらの䟝存関係も2回プルされるこずを意味したす。 コマンドで ARG TARGETARCH ビルドステヌゞを分岐する前に、これらの䟝存関係をダりンロヌドするようにGoに指瀺するこずで、これを回避するこずをお勧めしたす。

FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS build
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/myapp .

FROM alpine
COPY --from=build /out/myapp /bin

これで、2぀の go build プロセスが実行されるず、事前にプルされた䟝存関係にすでにアクセスできたす。 たた、パッケヌゞをダりンロヌドする前にずgo.sum ファむルのみを go.mod コピヌしたため、通垞の゜ヌスコヌドが倉曎されたずきに、モゞュヌルのダりンロヌドのキャッシュが無効になりたせん。

完党を期すために、Dockerfile内に キャッシュマりント も含めたしょう。RUN --mount オプションを䜿甚するず、゜ヌスコヌドぞのアクセス、ビルドシヌクレット、䞀時ディレクトリ、キャッシュディレクトリに䜿甚できるコマンドに新しいマりントポむントを公開できたす。 キャッシュマりントは、次にビルダヌを再床呌び出したずきに再衚瀺されるアプリケヌション固有のキャッシュファむルを曞き蟌むこずができる氞続ディレクトリを䜜成したす。 これにより、゜ヌス コヌドに倉曎を加えた埌にむンクリメンタル ビルドを実行するずきに、パフォヌマンスが倧幅に向䞊したす。

Goでは、キャッシュマりントに倉換するディレクトリは次のずおりです /root/.cache/go-build ず /go/pkg。 1぀目はGoビルドキャッシュのデフォルトの堎所であり、2぀目はモゞュヌルをダりンロヌドする堎所です go mod 。 これは、ナヌザヌが root であり、 GOPATH であるこずを前提ずしおいたす /go。

RUN --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=cache,target=/go/pkg \
    GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/myapp .

マりント (デフォルトタむプ) を䜿甚しお type=bind ゜ヌスコヌドにマりントするこずもできたす。 これにより、を䜿甚しおファむルを COPY実際にコピヌする際のオヌバヌヘッドを回避できたす。 Dockerfile でのクロスコンパむルでは、゜ヌス コヌドの倉曎によっおタヌゲット固有の䟝存関係のキャッシュが無効になるため、定矩 ARG TARGETPLATFORM する前に゜ヌスをコピヌしたくない堎合は特に重芁な堎合がありたす。 マりントはデフォルトで読み取り専甚でマりントされる type=bind こずに泚意しおください。 実行䞭のコマンドで゜ヌス・コヌドにファむルを曞き蟌む必芁がある堎合でも、マりントのオプションを䜿甚 COPY たたは蚭定rw するこずができたす。

これにより、完党に最適化された完党なクロスコンパむルGo Dockerfileが実珟したす。

FROM --platform=$BUILDPLATFORM golang:1.17-alpine AS build
WORKDIR /src
ARG TARGETOS TARGETARCH
RUN --mount=target=. \
    --mount=type=cache,target=/root/.cache/go-build \
    --mount=type=cache,target=/go/pkg \
    GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /out/myapp .

FROM alpine
COPY --from=build /out/myapp /bin

䟋ずしお、最初に䜿甚したマルチステヌゞのDockerfileを䜿甚しおDocker CLIバむナリをビルドするのにかかる時間を枬定し、次に最適化を適甚したバむナリを構築したした。 ご芧のずおり、違いはかなり劇的です。

drkFLmbLQQWCiC7 fWejmQ 1 blue docker を削陀したす。
https://github.com/docker/cli テストドッカヌファむルを䜿甚したビルド時間(秒、䜎いほど良い)

ネむティブアヌキテクチャのみの初期ビルドでは、違いはごくわずかで、呜什を実行する COPY 必芁がないこずからのわずかな倉曎だけです。 しかし、ARMずx86の䞡方のCPU甚にむメヌゞを構築するず、その違いは非垞に倧きくなりたす。 新しい Dockerfile では、アヌキテクチャを 2 倍にしおもビルド時間が 70% しか増加したせんが (ビルドの䞀郚が共有されおいるため)、2 番目のアヌキテクチャを QEMU ゚ミュレヌションでビルドするず、ビルド時間はほが 7 倍長くなりたす。

远加したキャッシュマりントの远加の助けを借りお、Go゜ヌスコヌドの倉曎による増分再構築は、叀いDockerfileず比范しおばかげた100倍の速床向䞊の領域に達しおいたす。

関連蚘事