多平台构建
多平台构建指一次构建调用即可面向多种不同的操作系统或 CPU 架构组合。在构建镜像时,这允许您创建可在多个平台(例如 linux/amd64
、linux/arm64
和 windows/amd64
)上运行的单个镜像。
为何进行多平台构建?
Docker 通过将应用程序及其依赖项打包到容器中,解决了“在我的机器上可以运行”的问题。这使得在不同环境(例如开发、测试和生产)中运行同一应用程序变得容易。
但仅靠容器化只能解决部分问题。容器共享主机内核,这意味着容器内部运行的代码必须与主机的架构兼容。这就是为什么您不能在 arm64 主机上运行 linux/amd64
容器(不使用仿真),也不能在 Linux 主机上运行 Windows 容器的原因。
多平台构建通过将同一应用程序的多个变体打包到一个镜像中来解决此问题。这使得您可以在不同类型的硬件上运行同一个镜像,例如运行 x86-64 的开发机器或云中的基于 ARM 的 Amazon EC2 实例,而无需进行仿真。
单平台镜像与多平台镜像的区别
多平台镜像与单平台镜像具有不同的结构。单平台镜像包含一个指向单个配置和一组层的 manifest。多平台镜像包含一个 manifest list,指向多个 manifest,每个 manifest 指向不同的配置和一组层。
当您将多平台镜像推送到注册表时,注册表会存储 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/amd64
和 linux/arm64
$ docker buildx build --platform linux/amd64,linux/arm64 .
策略
您可以使用三种不同的策略构建多平台镜像,具体取决于您的用例
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-amd64
和 node-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。
交叉编译
根据您的项目,如果您使用的编程语言对交叉编译有良好的支持,则可以利用多阶段构建来从构建器的原生架构为目标平台构建二进制文件。特殊的构建参数,例如 BUILDPLATFORM
和 TARGETPLATFORM
,将在您的 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 镜像存储
步骤
创建一个空目录并导航到该目录
$ mkdir multi-platform $ cd multi-platform
创建一个简单的 Dockerfile,该文件打印容器的架构
# syntax=docker/dockerfile:1 FROM alpine RUN uname -m > /arch
为
linux/amd64
和linux/arm64
构建镜像$ docker build --platform linux/amd64,linux/arm64 -t multi-platform .
运行镜像并打印架构
$ docker run --rm multi-platform cat /arch
- 如果您在 x86-64 机器上运行,您应该看到
x86_64
。 - 如果您在 ARM 机器上运行,您应该看到
aarch64
。
- 如果您在 x86-64 机器上运行,您应该看到
使用 Docker Build Cloud 构建多平台 Neovim
本示例演示了如何使用 Docker Build Cloud 运行多平台构建,以编译和导出适用于 linux/amd64
和 linux/arm64
平台的 Neovim 二进制文件。
Docker Build Cloud 提供托管的多节点构建器,支持原生多平台构建,无需模拟,这使得执行编译等 CPU 密集型任务的速度快得多。
前提条件
步骤
创建一个空目录并导航到该目录
$ mkdir docker-build-neovim $ cd docker-build-neovim
创建一个用于构建 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 /
使用 Docker Build Cloud 为
linux/amd64
和linux/arm64
构建镜像。$ docker build \ --builder <cloud-builder> \ --platform linux/amd64,linux/arm64 \ --output ./bin .
此命令使用云构建器构建镜像,并将二进制文件导出到
bin
目录。验证二进制文件是否已为两个平台构建。您应该会看到
linux/amd64
和linux/arm64
的nvim
二进制文件。$ tree ./bin ./bin ├── linux_amd64 │ └── nvim └── linux_arm64 └── nvim 3 directories, 2 files
交叉编译 Go 应用
本示例演示了如何使用多阶段构建为多个平台交叉编译 Go 应用程序。该应用程序是一个简单的 HTTP 服务器,它监听端口 8080 并返回容器的架构。本示例使用 Go 语言,但同样的原理也适用于支持交叉编译的其他编程语言。
使用 Docker 构建进行交叉编译的工作原理是利用一系列预定义的(在 BuildKit 中)构建参数,这些参数提供有关构建器平台和构建目标的信息。您可以使用这些预定义参数将平台信息传递给编译器。
在 Go 中,您可以使用 GOOS
和 GOARCH
环境变量指定要构建的目标平台。
前提条件
- Docker Desktop 或 Docker Engine
步骤
创建一个空目录并导航到该目录
$ mkdir go-server $ cd go-server
创建一个构建 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,构建器将尝试使用模拟来为指定平台构建镜像。要添加交叉编译支持,请更新 Dockerfile 以使用预定义的
BUILDPLATFORM
和TARGETPLATFORM
构建参数。当您使用docker build
附带--platform
标志时,这些参数会在 Dockerfile 中自动可用。- 使用
--platform=$BUILDPLATFORM
选项将golang
镜像锁定到构建器的平台。 - 为 Go 编译阶段添加
ARG
指令,以便在该阶段的命令中提供TARGETOS
和TARGETARCH
构建参数。 - 将
GOOS
和GOARCH
环境变量设置为TARGETOS
和TARGETARCH
的值。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"]
- 使用
为
linux/amd64
和linux/arm64
构建镜像$ docker build --platform linux/amd64,linux/arm64 -t go-server .
本示例展示了如何使用 Docker 构建为多个平台交叉编译 Go 应用程序。执行交叉编译的具体步骤可能因您使用的编程语言而异。请查阅您的编程语言文档以了解更多关于为不同平台进行交叉编译的信息。
提示
您可能还想考虑查看 xx - Dockerfile 交叉编译助手。
xx
是一个 Docker 镜像,其中包含使 Docker 构建交叉编译更容易的实用脚本。