多平台构建

多平台构建指一次构建调用即可面向多种不同的操作系统或 CPU 架构组合。在构建镜像时,这允许您创建可在多个平台(例如 linux/amd64linux/arm64windows/amd64)上运行的单个镜像。

为何进行多平台构建?

Docker 通过将应用程序及其依赖项打包到容器中,解决了“在我的机器上可以运行”的问题。这使得在不同环境(例如开发、测试和生产)中运行同一应用程序变得容易。

但仅靠容器化只能解决部分问题。容器共享主机内核,这意味着容器内部运行的代码必须与主机的架构兼容。这就是为什么您不能在 arm64 主机上运行 linux/amd64 容器(不使用仿真),也不能在 Linux 主机上运行 Windows 容器的原因。

多平台构建通过将同一应用程序的多个变体打包到一个镜像中来解决此问题。这使得您可以在不同类型的硬件上运行同一个镜像,例如运行 x86-64 的开发机器或云中的基于 ARM 的 Amazon EC2 实例,而无需进行仿真。

单平台镜像与多平台镜像的区别

多平台镜像与单平台镜像具有不同的结构。单平台镜像包含一个指向单个配置和一组层的 manifest。多平台镜像包含一个 manifest list,指向多个 manifest,每个 manifest 指向不同的配置和一组层。

Multi-platform image structure

当您将多平台镜像推送到注册表时,注册表会存储 manifest list 和所有单独的 manifest。当您拉取镜像时,注册表会返回 manifest list,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 Desktop,请在Docker Desktop 设置中启用 containerd 镜像存储。

  • 如果您使用的是 Docker Engine 独立版,请使用daemon 配置文件启用 containerd 镜像存储。

要创建自定义构建器,请使用 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 虚拟机中捆绑的 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 安装完毕并在主机操作系统上注册可执行文件类型后,它们将在容器内部透明地工作。您可以通过检查 /proc/sys/fs/binfmt_misc/qemu-* 中的标志是否包含 F 来验证注册。

多个原生节点

使用多个原生节点为 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 构建交叉编译更容易的实用脚本。

页面选项