多平台构建

多平台构建指的是针对多个不同操作系统或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. 使用 cross-compilation 进行多阶段构建

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
    
    • If you're running on an x86-64 machine, you should see x86_64.
    • If you're running on an ARM machine, you should see aarch64.

使用Docker Build Cloud构建多平台Neovim

此示例演示了如何使用 Docker Build Cloud 运行多平台构建,以编译并导出 Neovim 二进制文件 用于 linux/amd64linux/arm64 平台。

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. 验证二进制文件是否为两个平台构建。你应该看到nvim二进制文件适用于linux/amd64linux/arm64

    $ 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. 创建一个基础的 Dockerfile 来构建 Go 应用程序:

    # 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构建参数。当您使用--platform标志与docker build时,这些参数在Dockerfile中自动可用。

    • Pin the golang image to the platform of the builder using the --platform=$BUILDPLATFORM option.
    • Add ARG instructions for the Go compilation stages to make the TARGETOS and TARGETARCH build arguments available to the commands in this stage.
    • Set the GOOS and GOARCH environment variables to the values of TARGETOS and TARGETARCH. The Go compiler uses these variables to do cross-compilation.

    # 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 构建进行跨编译更加容易。