docker buildx を使用してクロスプラットフォーム Go イメージを構築する

目次

  1. 前提

  2. ドッカービルドx

    1. Buildx を有効にする

    2. ビルダーインスタンス

    3. ビルドドライバー

  3. buildx のクロスプラットフォーム ビルド戦略

  4. 複数のアーキテクチャ Go イメージを一度に構築する練習

    1. ソースコードとDockerfile

    2. クロスプラットフォームビルドを実行する

    3. ビルド結果を確認する

  5. Golang の CGO プロジェクトをクロスコンパイルする方法

    1. クロスコンパイル環境と依存関係を準備する

    2. クロスコンパイル CGO の例

  6. 要約する

  7. 参考リンク

異なるオペレーティング システムやプロセッサ アーキテクチャ上でアプリケーションを実行するのが一般的であるため、異なるプラットフォーム用に個別のディストリビューションを構築するのが一般的です。アプリケーションの開発に使用するプラットフォームが、デプロイ先のプラットフォームと異なる場合、これを達成するのは簡単ではありません。たとえば、x86 アーキテクチャでアプリケーションを開発し、ARM プラットフォーム マシンに展開するには、通常、開発とコンパイルのために ARM プラットフォーム インフラストラクチャを準備する必要があります。

複数のデプロイメント用のイメージ配布を一度に構築すると、アプリケーションの配信効率が大幅に向上します。アプリケーションのクロスプラットフォーム デプロイメントが必要なシナリオでは、docker buildx を使用してクロスプラットフォーム イメージを構築することも高速で効率的なソリューションです。

前提

ほとんどのイメージ ホスティング プラットフォームはマルチプラットフォーム イメージをサポートしています。つまり、ミラー リポジトリ内の 1 つのタグに、異なるプラットフォームの複数のイメージを含めることができます。ミラー リポジトリを例に取ると、このタグには、異なるシステムとアーキテクチャの 10 個のイメージが含まれています (プラットフォーム  =docker hub システム  ) + アーキテクチャ):python3.9.6

 クロスプラットフォームをサポートするイメージを渡す docker pull か プルする場合、 現在実行中のプラットフォームに一致するイメージが自動的に選択されます。この機能の存在により、プラットフォーム間でイメージを配布する際、イメージの消費に関しては何もする必要がなく、イメージの生成、つまりクロスプラットフォームのイメージをどのように構築するかだけを考慮する必要があります。docker rundocker

ドッカービルドx

デフォルトの docker build コマンドではクロスプラットフォームのビルド タスクを完了できないため、  コマンド ラインの機能を拡張するにはコマンド ラインのプラグインdocker をインストールする 必要があります。Moby BuildKit によって提供されるビルド イメージの追加機能 を使用する機能 。これにより、複数のビルダー インスタンスを作成し、複数のノードでビルド タスクを並行して実行し、プラットフォーム間でビルドできます。buildxbuildx

Buildx を有効にする

macOS または Windows システム用の Docker Desktop、および Linux ディストリビューションは、  パッケージ インストール deb によって  組み込まれており 、追加のインストールは必要ありません。rpmdockerbuildx

docker このコマンドがない 場合は buildx 、インストール用のバイナリ パッケージをダウンロードできます。

  1. まず、  Docker buildxプロジェクト のリリース ページから、プラットフォームに適したバイナリ ファイルを見つけます。
  2. バイナリ ファイルをローカルにダウンロードし、名前を に変更し docker-buildx、docker プラグイン ディレクトリに移動します ~/.docker/cli-plugins
  3. バイナリに実行権限を付与します。

ローカル docker バージョンが 19.03 より高い場合は、次のコマンドを使用してローカルに直接ビルドしてインストールできます。これはより便利です。

$ export DOCKER_BUILDKIT=1
$ docker build --platform=local -o . git://github.com/docker/buildx
$ mkdir -p ~/.docker/cli-plugins
$ mv buildx ~/.docker/cli-plugins/docker-buildx

を使用して buildx ビルドする方法は次のとおりです。

docker buildx build .

buildx ユーザー エクスペリエンスは基本的にコマンドのエクスペリエンスと同じであり、  など の一般的なオプション docker build もサポートしています 。build-t-f

ビルダーインスタンス

docker buildx ビルド構成とノードはビルダー インスタンス オブジェクトを通じて管理され、コマンド ラインによってビルド タスクがビルダー インスタンスに送信され、ビルダーはそれを実行条件を満たすノードに割り当てます。同じサービス プログラムに基づいて複数のビルダー インスタンスを作成し docker 、それらを異なるプロジェクトに提供して、各プロジェクトの構成を分離することもできます。また、 docker リモート ノードのグループ用のビルダー インスタンスを作成してビルド配列を形成し、異なるノード間で迅速に切り替えることもできます。配列。

コマンドを使用して docker buildx create ビルダー インスタンスを作成します。これにより、現在使用されている Docker サービスを使用してノードの新しいビルダー インスタンスが作成されます。DOCKER_HOST リモート ノードを使用するには、環境変数を使用してリモート ポートを指定するか、 サンプルの作成時に 事前にリモート ノードに切り替えることができますdocker context次の例では、リモート ノード上に新しいビルダー インスタンスを作成し、そのドライバー、ターゲット プラットフォーム、およびインスタンス名をコマンド ライン オプションで指定します。

$ export DOCKER_HOST=tcp://10.10.150.66:2375
$ docker buildx create --driver docker-container --platform linux/amd64,linux/arm64 --name remote-builder
remote-builder

docker buildx ls 使用可能なすべてのビルダー インスタンスとその中のノードがリストされます。

$ docker buildx ls
NAME/NODE         DRIVER/ENDPOINT         STATUS   PLATFORMS
remote-builder    docker-container                 
  remote-builder0 tcp://10.10.150.66:2375 inactive linux/amd64*, linux/arm64*
default *         docker                           
  default         default                 running  linux/amd64, linux/386

インスタンスが作成された後、インスタンスに新しいノードを追加し続けることができます。 docker buildx create コマンドの オプションを 使用すると、オプションで指定されたビルダー インスタンス--append <node> にノードを追加できます 。--name <builder>

 $ docker buildx create --name default --append remote-builder0

docker buildx inspectdocker buildx stop および docker buildx rm コマンドは、インスタンスのライフサイクルを管理するために使用されます。

docker buildx use <builder> 指定されたビルダー インスタンスに切り替えます。

ビルドドライバー

buildx インスタンスは、異なる「ドライバー」を使用する 2 つの方法でビルド タスクを実行します。

  • docker ドライバー: Docker サーバーに統合されている BuildKit ライブラリを使用してビルドを実行します。
  • docker-container ドライバー: BuildKit を含むコンテナーを起動し、コンテナー内でビルドを実行します。

docker ドライバーは少数 buildx の機能 (1 回の実行で複数のプラットフォーム イメージを同時にビルドするなど) を使用できません。また、イメージのデフォルトの出力形式にも違いがあります。ドライバーはデフォルトでビルド結果を直接出力しますdocker 。 Docker イメージ形式を docker イメージ ディレクトリ (通常 /var/lib/overlay2) にコピーすると、コマンドの実行後に docker images 出力ミラーを一覧表示でき、 オプションdocker container で出力形式をミラーまたはその他の形式に指定する必要があります --output 。

複数のプラットフォーム用のイメージを一度にビルドするために、 docker container 以下ではドライバーのビルダー インスタンスを使用します。

buildx のクロスプラットフォーム ビルド戦略

さまざまなビルド ノードとターゲット プログラミング言語に応じて、buildx 次の 3 つのクロスプラットフォーム ビルド戦略がサポートされています。

  1. QEMU のユーザー モードを使用して軽量の仮想マシンを作成し、仮想マシン システムにミラー イメージを構築します。
  2. 異なるターゲット プラットフォームの複数のノードをビルダー インスタンスに追加し、ネイティブ ノードを通じて対応するプラットフォーム イメージを構築します。
  3. 段階的にビルドし、さまざまなターゲット アーキテクチャにクロスコンパイルします。

QEMU は通常、完全なオペレーティング システムをエミュレートするために使用されますが、ユーザー モードでも実行できます。 binfmt_misc ホスト システムにバイナリ変換ハンドラを登録し、プログラムの実行中にバイナリ ファイルを動的に変換し、ターゲット CPU からシステム コールを転送します。必要に応じて、アーキテクチャは現在のシステムの CPU アーキテクチャに変換されます。最終的な効果は、仮想マシンでターゲット CPU アーキテクチャのバイナリを実行するようなものです。Docker Desktop には QEMU サポートが組み込まれており、実行要件を満たす他のプラットフォームは次の方法でインストールできます。

$ docker run --privileged --rm tonistiigi/binfmt --install all

この方法は既存の Dockerfile を変更する必要がなく、実装コストは非常に低くなりますが、明らかに効率は高くありません。

さまざまなシステム アーキテクチャのネイティブ ノードをビルダー インスタンスに追加すると、クロスプラットフォーム コンパイルのサポートが強化され、より効率的になりますが、十分なインフラストラクチャ サポートが必要です。

プロジェクトのビルドに使用されるプログラミング言語がクロスコンパイル (C や Go など) をサポートしている場合は、Dockerfile が提供する段階的ビルド機能を使用できます。まず、ビルド ノードと同じアーキテクチャでターゲット アーキテクチャのバイナリ ファイルをコンパイルします。次に、これらのバイナリ ファイルをコンパイルし、ターゲット スキーマの別のミラーにコピーします。以下では具体的な例をGoで実装していきます。この方法は追加のハードウェアを必要とせず、より優れたパフォーマンスを実現できますが、それを実現できるのは特定のプログラミング言語のみです。

複数のアーキテクチャ Go イメージを一度に構築する練習

ソースコードとDockerfile

main.go 以下では、サンプル プログラム ファイルの内容が次のとおりであると仮定して、単純な Go プロジェクトを例として取り上げます 。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Println("Hello world!")
	fmt.Printf("Running in [%s] architecture.\n", runtime.GOARCH)
}

ビルドプロセスを定義する Dockerfile は次のとおりです。

FROM --platform=$BUILDPLATFORM golang:1.14 as builder

ARG TARGETARCH

WORKDIR /app
COPY main.go /app/main.go
RUN GOOS=linux GOARCH=$TARGETARCH go build -a -o output/main main.go

FROM alpine:latest
WORKDIR /root
COPY --from=builder /app/output/main .
CMD /root/main

ビルド プロセスは 2 つのフェーズに分かれています。

  • golang 1 つのステージでは、現在のビルド ノードと同じプラットフォームのイメージをプルし 、Go のクロスコンパイル機能を使用してターゲット アーキテクチャ用のバイナリにコンパイルします。
  • 次に、 alpine ターゲット プラットフォームのイメージをプルし、前のステージのコンパイル結果をイメージにコピーします。

クロスプラットフォームビルドを実行する

ビルド コマンドを実行するときは、イメージ名の指定に加えて、ターゲット プラットフォームと出力形式の指定という 2 つの重要なオプションがあります。

docker buildx build オプションで ビルドのターゲット プラットフォームを指定します--platform 。Dockerfile 内の FROM コマンドで --platform フラグが設定されていない場合、ベース イメージはターゲット プラットフォームによってプルされ、最終的に生成されたイメージもターゲット プラットフォームに属します。 さらに、このオプションの値はBUILDPLATFORM、 、TARGETPLATFORMBUILDARCH など のパラメータを介して Dockerfile で使用できます 。TARGETARCHドライバーを使用する場合 docker-container 、このオプションはカンマで区切られた複数の値を入力として受け入れ、複数のターゲット プラットフォームを同時に指定できます。すべてのプラットフォームのビルド結果は出力としてイメージ リスト全体に結合されるため、直接ビルドすることはできませんローカル docker images 画像として出力します。

docker buildx build 豊富な出力動作をサポートします。--output=[PATH,-,type=TYPE[,KEY=VALUE] オプションを通じて、ビルド結果の出力タイプとパスを指定できます。一般的に使用される出力タイプは次のとおりです。

  • local: ビルド結果は、ファイルシステム形式で dest 指定されたローカル パス に書き込まれます--output type=local,dest=./output
  • tar: ビルド結果は、パッケージ化後に dest 指定されたローカル パスに書き込まれます。
  • oci: ビルド結果は、 dest 指定されたローカル パスに OCI 標準イメージ形式で書き込まれます。
  • docker: ビルド結果は、 dest 指定されたローカル パスに書き込まれるか、  docker Docker 標準イメージ形式でイメージ ライブラリにロードされます。複数のターゲット プラットフォームを同時に指定する場合、このオプションは使用できません。
  • image: ミラー イメージまたはミラー リストとして出力し、 push=true リモート ウェアハウスに直接プッシュするオプションをサポートします。このオプションは、複数のターゲット プラットフォームを同時に指定するときに使用できます。
  • type=image,push=true レジストリのコンパクトな表現:

この例では、次の docker buildx build コマンドを実行します。

$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo -o type=registry .

linux/amd64このコマンドは、 linux/arm64 と 3 つのプラットフォームのイメージを現在のディレクトリに同時に構築し linux/arm 、出力結果をリモートの Alibaba Cloud ミラー ウェアハウスに直接プッシュします。

構築プロセスは次のように分解できます。

  1. docker ビルド コンテキストをビルダー インスタンスに転送します。
  2. ビルダーは、 --platform 基本イメージの取得やビルド ステップの実行など、コマンド ライン オプションで指定された各ターゲット プラットフォームのイメージをビルドします。
  3. ビルド結果がエクスポートされ、イメージ ファイル レイヤーがリモート ウェアハウスにプッシュされます。
  4. マニフェスト JSON ファイルを生成し、ミラー タグとしてリモート リポジトリにプッシュします。

ビルド結果を確認する

実行後、 docker buildx imagetools リモート ウェアハウスにプッシュされたイメージを検査できます。

$ docker buildx imagetools inspect registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest
Name:      registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:e2c3c5b330c19ac9d09f8aaccc40224f8673e12b88ff59cb68971c36b76e95ca
           
Manifests: 
  Name:      registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:cb6a7614ee3db03c8858e3680b1585f32a6fe3de9b371e37e25cf42a83f6e0ba
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/amd64
             
  Name:      registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:034aa0077a452a6c2585f8b4969c7c85d5d2bf65f801fcc803a00d0879ce900e
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/arm64
             
  Name:      registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:db0ee3a876fb789d2e733471385eef0a056f64ee12d9e7ef94e411469d054eb5
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/arm/v7

最後に、さまざまなプラットフォームでタグを含む latest イメージをプルして実行し、ビルド結果が正しいかどうかを確認します。Docker Desktop を使用する場合、統合された仮想化機能はさまざまなプラットフォームのイメージを実行し、 sha256 値によってイメージを直接プルできます。

$ docker run --rm registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:cb6a7614ee3db03c8858e3680b1585f32a6fe3de9b371e37e25cf42a83f6e0ba
Hello world!
Running in [amd64] architecture.
$ docker run --rm registry.cn-hangzhou.aliyuncs.com/waynerv/arch-demo:latest@sha256:034aa0077a452a6c2585f8b4969c7c85d5d2bf65f801fcc803a00d0879ce900e
Hello world!
Running in [arm64] architecture.

Golang の CGO プロジェクトをクロスコンパイルする方法

一般的なオペレーティング システムと CPU アーキテクチャへのクロスコンパイルのサポートは Golang の大きな利点ですが、上記の例のソリューションは純粋な Go コードにのみ適用できます。プロジェクトが C コードを呼び出す場合、状況はさらに複雑になります cgo 。

クロスコンパイル環境と依存関係を準備する

C コードをターゲット プラットフォームに正常にコンパイルするには、まずターゲット プラットフォームの C クロス コンパイラ (通常はベース) をコンパイル環境にインストールする必要があります。一般的に使用される Linux ディストリビューションには、ほとんどのプラットフォーム用のクロス コンパイラ インストール パッケージが用意されています gcc。これは、パッケージ マネージャーのインストールを通じて直接インストールできます。

次に、ターゲット プラットフォームの C 標準ライブラリをインストールする必要があります (通常、標準ライブラリはクロス コンパイラのインストール依存関係として使用され、個別にインストールする必要はありません)。呼び出すコードに応じて、追加の C 依存ライブラリ (など) をインストールする必要がある場合もあります libopus-dev 。

amd64 アーキテクチャの 公式イメージをベース イメージとして使用し golang:1.14 、Linux ディストリビューション Debian を使用するコンパイルを実行します。クロスコンパイルの対象プラットフォームを とすると linux/arm64、準備するクロスコンパイラは gcc-aarch64-linux-gnu、C標準ライブラリは libc6-dev-arm64-cross、インストール方法は以下のようになります。

$ apt-get update
$ apt-get install gcc-aarch64-linux-gnu

libc6-dev-arm64-cross も同時にインストールされます。

Debian パッケージ マネージャーが提供するマルチ アーキテクチャ インストール機能のおかげで dpkg 、コードが libopus-dev 他の非標準ライブラリに依存している場合、 <library>:<architecture> 次の方法でそのアーキテクチャ arm64 のインストール パッケージをインストールできます。

$ dpkg --add-architecture arm64
$ apt-get update
$ apt-get install -y libopus-dev:arm64

クロスコンパイル CGO の例

次のサンプルコードがあるとします cgo 。

package main

/*
#include <stdlib.h>
*/
import "C"
import "fmt"

func Random() int {
    return int(C.random())
}

func Seed(i int) {
    C.srandom(C.uint(i))
}

func main()  {
    rand := Random()
    fmt.Printf("Hello %d\n", rand)
}

使用するDockerfileは以下のとおりです。

FROM --platform=$BUILDPLATFORM golang:1.14 as builder
ARG TARGETARCH
RUN apt-get update && apt-get install -y gcc-aarch64-linux-gnu
WORKDIR /app
COPY . /app/
RUN if [ "$TARGETARCH" = "arm64" ]; then CC=aarch64-linux-gnu-gcc && CC_FOR_TARGET=gcc-aarch64-linux-gnu; fi && \
  CGO_ENABLED=1 GOOS=linux GOARCH=$TARGETARCH CC=$CC CC_FOR_TARGET=$CC_FOR_TARGET go build -a -ldflags '-extldflags "-static"' -o /main main.go

Dockerfile は  クロスコンパイラーとしてapt-get インストールされ 、サンプル プログラムは比較的シンプルであるため、追加の依存ライブラリは必要ありません。gcc-aarch64-linux-gnuコンパイル時に 、 使用するクロスコンパイラ と 環境変数を指定するgo build 必要があります 。CCCC_FOR_TARGET

同じ Dockerfile に基づいて複数のターゲット プラットフォームをコンパイルするために (ターゲット アーキテクチャが amd64/のみであると仮定しますarm64)、一番下の RUN 命令では、Bash の条件判断構文を通じて異なるコンパイル コマンドを実行するトリックを使用します。

  • ビルドタスクのターゲットプラットフォームが の場合 arm64、 環境変数CC には CC_FOR_TARGET インストールされているクロスコンパイラを指定します(値が異なることに注意してください)。
  • ビルドタスクのターゲットプラットフォームが の場合 amd64、クロスコンパイラに関連する変数を指定しないと、デフォルトの変数が gcc コンパイラとして使用されます。

最後に、buildx を使用してビルドを実行するコマンドは次のとおりです。

$ docker buildx build --platform linux/amd64,linux/arm64 -t registry.cn-hangzhou.aliyuncs.com/waynerv/cgo-demo -o type=registry .

要約する

Buildx プラグインを使用すると、インフラストラクチャなしでクロスプラットフォーム アプリケーション イメージを docker 簡単に。 

ただし、デフォルトでは、QEMU を介してターゲット プラットフォームの命令を仮想化する方法には明らかなパフォーマンスのボトルネックがあり、アプリケーションの作成に使用される言語がクロスコンパイルをサポートしている場合は、組み合わせとクロスコンパイルによってより高い効率を達成できます buildx 。

最後に、この記事では高度なシナリオのソリューション、つまり CGO を使用して Golang プロジェクトをクロスコンパイルする方法を紹介し、 linux/arm64 プラットフォームへのコンパイルの例を示します。

参考リンク

おすすめ

転載: blog.csdn.net/hackermmm/article/details/128121729