多平臺構建

多平臺構建是指一次構建呼叫,它針對多個不同的作業系統或 CPU 架構組合。在構建映象時,這允許您建立可以在多個平臺(例如 linux/amd64linux/arm64windows/amd64)上執行的單個映象。

為什麼選擇多平臺構建?

Docker 透過將應用程式及其依賴項打包到容器中來解決“在我的機器上能工作”的問題。這使得在不同環境(例如開發、測試和生產)中執行相同的應用程式變得容易。

但容器化本身只解決了問題的一部分。容器共享主機核心,這意味著在容器內部執行的程式碼必須與主機的架構相容。這就是為什麼您不能在 arm64 主機上執行 linux/amd64 容器(不使用模擬),也不能在 Linux 主機上執行 Windows 容器。

多平臺構建透過將同一應用程式的多個變體打包到單個映象中來解決此問題。這使您可以在不同型別的硬體上執行相同的映象,例如執行 x86-64 的開發機器或雲中基於 ARM 的 Amazon EC2 例項,而無需模擬。

單平臺和多平臺映象的區別

多平臺映象與單平臺映象的結構不同。單平臺映象包含一個指向單個配置和一組層的清單。多平臺映象包含一個清單列表,指向多個清單,每個清單都指向不同的配置和一組層。

Multi-platform image structure

當您將多平臺映象推送到登錄檔時,登錄檔會儲存清單列表和所有單獨的清單。當您拉取映象時,登錄檔會返回清單列表,Docker 會根據主機的架構自動選擇正確的變體。例如,如果您在基於 ARM 的 Raspberry Pi 上執行多平臺映象,Docker 會選擇 linux/arm64 變體。如果您在 x86-64 筆記型電腦上執行相同的映象,Docker 會選擇 linux/amd64 變體(如果您使用 Linux 容器)。

先決條件

要構建多平臺映象,您首先需要確保您的 Docker 環境已設定為支援它。有兩種方法可以做到這一點:

  • 您可以從“經典”映象儲存切換到 containerd 映象儲存。
  • 您可以建立並使用自定義構建器。

Docker Engine 的“經典”映象儲存不支援多平臺映象。切換到 containerd 映象儲存可確保您的 Docker Engine 可以推送、拉取和構建多平臺映象。

建立使用具有多平臺支援的驅動程式(例如 docker-container 驅動程式)的自定義構建器,將允許您構建多平臺映象,而無需切換到不同的映象儲存。但是,您仍然無法將構建的多平臺映象載入到 Docker Engine 映象儲存中。但您可以使用 docker build --push 直接將它們推送到容器登錄檔。

啟用 containerd 映象儲存的步驟取決於您使用的是 Docker Desktop 還是獨立 Docker Engine:

要建立自定義構建器,請使用 docker buildx create 命令建立一個使用 docker-container 驅動程式的構建器。

$ docker buildx create \
  --name container-builder \
  --driver docker-container \
  --bootstrap --use
注意

使用 docker-container 驅動程式的構建不會自動載入到您的 Docker Engine 映象儲存中。有關更多資訊,請參閱構建驅動程式

如果您使用獨立 Docker Engine 並且需要使用模擬構建多平臺映象,您還需要安裝 QEMU,請參閱手動安裝 QEMU

構建多平臺映象

觸發構建時,使用 --platform 標誌定義構建輸出的目標平臺,例如 linux/amd64linux/arm64

$ docker buildx build --platform linux/amd64,linux/arm64 .

策略

您可以根據用例使用三種不同的策略構建多平臺映象:

  1. 透過QEMU使用模擬
  2. 使用具有多個原生節點的構建器
  3. 在多階段構建中使用交叉編譯

QEMU

如果您的構建器已經支援,使用 QEMU 模擬構建多平臺映象是最簡單的入門方式。使用模擬無需更改 Dockerfile,BuildKit 會自動檢測可用於模擬的架構。

注意

使用 QEMU 模擬可能比原生構建慢得多,特別是對於計算密集型任務,如編譯和壓縮或解壓縮。

如果可能,請改用多個原生節點交叉編譯

Docker Desktop 預設支援在模擬下執行和構建多平臺映象。無需配置,因為構建器使用 Docker Desktop VM 中捆綁的 QEMU。

手動安裝 QEMU

如果您在 Docker Desktop 之外使用構建器,例如您在 Linux 上使用 Docker Engine 或自定義遠端構建器,則需要安裝 QEMU 並在主機作業系統上註冊可執行檔案型別。安裝 QEMU 的先決條件是:

  • Linux 核心版本 4.8 或更高版本
  • binfmt-support 版本 2.1.7 或更高版本
  • QEMU 二進位制檔案必須靜態編譯並使用 fix_binary 標誌註冊

使用tonistiigi/binfmt映象,透過一個命令在主機上安裝 QEMU 並註冊可執行檔案型別:

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

這會安裝 QEMU 二進位制檔案,並將它們註冊到binfmt_misc,從而使 QEMU 能夠執行非原生檔案格式進行模擬。

一旦 QEMU 安裝完成並且可執行檔案型別在主機作業系統上註冊,它們就可以在容器內部透明地工作。您可以透過檢查 F 是否在 /proc/sys/fs/binfmt_misc/qemu-* 的標誌中來驗證您的註冊。

多個原生節點

使用多個原生節點可以更好地支援 QEMU 無法處理的更復雜情況,並且還提供更好的效能。

您可以使用 --append 標誌向構建器新增其他節點。

以下命令從名為 node-amd64node-arm64 的 Docker 上下文建立多節點構建器。此示例假定您已新增這些上下文。

$ docker buildx create --use --name mybuild node-amd64
mybuild
$ docker buildx create --append --name mybuild node-arm64
$ docker buildx build --platform linux/amd64,linux/arm64 .

雖然這種方法優於模擬,但管理多節點構建器會帶來設定和管理構建器叢集的一些開銷。或者,您可以使用 Docker Build Cloud,這是一項在 Docker 基礎設施上提供託管多節點構建器的服務。使用 Docker Build Cloud,您無需維護即可獲得原生多平臺 ARM 和 X86 構建器。使用雲構建器還提供額外的好處,例如共享構建快取。

註冊 Docker Build Cloud 後,將構建器新增到本地環境並開始構建。

$ docker buildx create --driver cloud <ORG>/<BUILDER_NAME>
cloud-<ORG>-<BUILDER_NAME>
$ docker build \
  --builder cloud-<ORG>-<BUILDER_NAME> \
  --platform linux/amd64,linux/arm64,linux/arm/v7 \
  --tag <IMAGE_NAME> \
  --push .

有關更多資訊,請參閱Docker Build Cloud

交叉編譯

根據您的專案,如果您使用的程式語言對交叉編譯有很好的支援,您可以利用多階段構建來從構建器的原生架構為目標平臺構建二進位制檔案。特殊的構建引數,例如 BUILDPLATFORMTARGETPLATFORM,在您的 Dockerfile 中自動可用。

在以下示例中,FROM 指令被固定到構建器的原生平臺(使用 --platform=$BUILDPLATFORM 選項)以防止模擬啟動。然後,預定義的 $BUILDPLATFORM$TARGETPLATFORM 構建引數在 RUN 指令中進行插值。在這種情況下,值只是用 echo 列印到標準輸出,但這說明了如何將它們傳遞給編譯器進行交叉編譯。

# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" > /log
FROM alpine
COPY --from=build /log /log

示例

以下是一些多平臺構建的示例:

使用模擬的簡單多平臺構建

此示例演示瞭如何使用 QEMU 模擬構建簡單的多平臺映象。該映象包含一個列印容器架構的單個檔案。

先決條件

  • Docker Desktop,或安裝了QEMU的 Docker Engine
  • 啟用了 containerd 映象儲存

步驟

  1. 建立空目錄並導航到該目錄

    $ mkdir multi-platform
    $ cd multi-platform
    
  2. 建立一個簡單的 Dockerfile,列印容器的架構

    # syntax=docker/dockerfile:1
    FROM alpine
    RUN uname -m > /arch
  3. linux/amd64linux/arm64 構建映象

    $ docker build --platform linux/amd64,linux/arm64 -t multi-platform .
    
  4. 執行映象並列印架構

    $ docker run --rm multi-platform cat /arch
    
    • 如果您在 x86-64 機器上執行,您應該會看到 x86_64
    • 如果您在 ARM 機器上執行,您應該會看到 aarch64

使用 Docker Build Cloud 構建多平臺 Neovim

此示例演示瞭如何使用 Docker Build Cloud 執行多平臺構建,以編譯並匯出 linux/amd64linux/arm64 平臺的 Neovim 二進位制檔案。

Docker Build Cloud 提供託管的多節點構建器,支援原生多平臺構建,無需模擬,這使得 CPU 密集型任務(如編譯)的速度大大加快。

先決條件

步驟

  1. 建立空目錄並導航到該目錄

    $ mkdir docker-build-neovim
    $ cd docker-build-neovim
    
  2. 建立一個構建 Neovim 的 Dockerfile。

    # syntax=docker/dockerfile:1
    FROM debian:bookworm AS build
    WORKDIR /work
    RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
        --mount=type=cache,target=/var/lib/apt,sharing=locked \
        apt-get update && apt-get install -y \
        build-essential \
        cmake \
        curl \
        gettext \
        ninja-build \
        unzip
    ADD https://github.com/neovim/neovim.git#stable .
    RUN make CMAKE_BUILD_TYPE=RelWithDebInfo
    
    FROM scratch
    COPY --from=build /work/build/bin/nvim /
  3. 使用 Docker Build Cloud 為 linux/amd64linux/arm64 構建映象

    $ docker build \
       --builder <cloud-builder> \
       --platform linux/amd64,linux/arm64 \
       --output ./bin .
    

    此命令使用雲構建器構建映象並將二進位制檔案匯出到 bin 目錄。

  4. 驗證二進位制檔案是否為兩個平臺構建。您應該會看到 linux/amd64linux/arm64nvim 二進位制檔案。

    $ tree ./bin
    ./bin
    ├── linux_amd64
    │   └── nvim
    └── linux_arm64
        └── nvim
    
    3 directories, 2 files
    

交叉編譯 Go 應用程式

此示例演示瞭如何使用多階段構建為多個平臺交叉編譯 Go 應用程式。該應用程式是一個簡單的 HTTP 伺服器,監聽埠 8080 並返回容器的架構。此示例使用 Go,但相同的原理適用於其他支援交叉編譯的程式語言。

使用 Docker 構建進行交叉編譯的工作原理是利用一系列預定義(在 BuildKit 中)的構建引數,這些引數為您提供有關構建器平臺和構建目標的資訊。您可以使用這些預定義引數將平臺資訊傳遞給編譯器。

在 Go 中,您可以使用 GOOSGOARCH 環境變數來指定要構建的目標平臺。

先決條件

  • Docker Desktop 或 Docker Engine

步驟

  1. 建立空目錄並導航到該目錄

    $ mkdir go-server
    $ cd go-server
    
  2. 建立構建 Go 應用程式的基本 Dockerfile

    # syntax=docker/dockerfile:1
    FROM golang:alpine AS build
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    RUN go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]

    此 Dockerfile 尚不能透過交叉編譯構建多平臺。如果您嘗試使用 docker build 構建此 Dockerfile,構建器將嘗試使用模擬為指定平臺構建映象。

  3. 要新增交叉編譯支援,請更新 Dockerfile 以使用預定義的 BUILDPLATFORMTARGETPLATFORM 構建引數。當您在 docker build 命令中使用 --platform 標誌時,這些引數在 Dockerfile 中自動可用。

    • 使用 --platform=$BUILDPLATFORM 選項將 golang 映象固定到構建器平臺。
    • 為 Go 編譯階段新增 ARG 指令,使 TARGETOSTARGETARCH 構建引數可用於此階段的命令。
    • GOOSGOARCH 環境變數設定為 TARGETOSTARGETARCH 的值。Go 編譯器使用這些變數進行交叉編譯。
    # syntax=docker/dockerfile:1
    FROM --platform=$BUILDPLATFORM golang:alpine AS build
    ARG TARGETOS
    ARG TARGETARCH
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]
    # syntax=docker/dockerfile:1
    FROM golang:alpine AS build
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    RUN go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]
    # syntax=docker/dockerfile:1
    -FROM golang:alpine AS build
    +FROM --platform=$BUILDPLATFORM golang:alpine AS build
    +ARG TARGETOS
    +ARG TARGETARCH
    WORKDIR /app
    ADD https://github.com/dvdksn/buildme.git#eb6279e0ad8a10003718656c6867539bd9426ad8 .
    -RUN go build -o server .
    +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o server .
    
    FROM alpine
    COPY --from=build /app/server /server
    ENTRYPOINT ["/server"]
    
  4. linux/amd64linux/arm64 構建映象

    $ docker build --platform linux/amd64,linux/arm64 -t go-server .
    

此示例演示瞭如何使用 Docker 構建為多個平臺交叉編譯 Go 應用程式。交叉編譯的具體步驟可能因您使用的程式語言而異。請查閱您的程式語言文件,瞭解有關為不同平臺交叉編譯的更多資訊。

提示

您可能還想檢視xx - Dockerfile 交叉編譯助手xx 是一個 Docker 映象,其中包含使 Docker 構建更容易進行交叉編譯的實用指令碼。