QEMU 是一个很棒的开源项目,它可以模拟很多平台。将 QEMU 和 Docker 结合起来使用能使得我们更容易的构建跨平台的容器镜像。集成 QEMU依赖于 Linux 内核功能 。Linux 内核中的 binfmt_misc功能可以使得内核识别任意类型的可以执行文件格式,并传递到特定的用户空间应用程序和虚拟机(https://zh.wikipedia.org/wiki/Binfmt_misc)。当 Linux 遇到一种无法识别的可执行文件格式(比如说其它平台的可执行文件格式)时,它会检查有没有配置任何“用户空间应用程序”用于处理它。如果检测到了,就将可执行文件传递给该应用程序。
为此,我们需要在内核当中注册其它平台的可执行文件格式。
对于使用 Docker Desktop(MacOS 和 Windows 上都是)的同学,因为默认配置了 binfmt_misc,可以跳过这一步。而使用 Linux 发行版操作系统的同学则需要自行安装配置 binfmt_misc,以便能够非原生的其它平台的镜像。
要在宿主机上执行其它 CPU 平台的指令,需要安装 QEMU 模拟器。因为程序执行时会在当前程序可见的文件系统中查找动态库,而在容器或chroot环境中注册的处理程序在其它的 cgroup namespace 中可能无法找到,所以需要静态编译连接的QEMU。同时,我们需要安装一个包含足够新的update-binfmts二进制文件的包,以便能够支持fix-binary(F)标志,并在注册QEMU模拟器时实际使用,这样才能结合 buildx 一起镜像跨平台构建。
QEMU 和 binfmt_misc 支持工具可以通过宿主机或者Docker 容器镜像安装。但是,使用Docker镜像安装配置能让事情变得更加简单。镜像 docker/binfmt 中包含QEMU二进制文件和在binfmt_misc中注册QEMU的安装脚本。
docker run --privileged docker/binfmt:66f9012c56a8316f9244ffd7622d7c21c1f6f28d执行完后,我们验证下是否注册成功了。成功注册后,/proc/sys/fs/binfmt_misc 目录中会有多个qemu-前缀的文件。查看 /proc/sys/fs/binfmt_misc/qemu-aarch64 文件内容,可以看到 falgs 标志为 OCF,说明这个处理程序是通过 (F)标志注册的,能够正常的结合 buildx 完成跨平台构建。
⚡ root@kofj-hk ~ ls -al /proc/sys/fs/binfmt_misc total 0 drwxr-xr-x 2 root root 0 Oct 12 20:19 . dr-xr-xr-x 1 root root 0 Oct 12 20:19 .. -rw-r--r-- 1 root root 0 Oct 12 20:19 python2.7 -rw-r--r-- 1 root root 0 Oct 12 20:19 python3.6 -rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-aarch64 -rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-arm -rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-ppc64le -rw-r--r-- 1 root root 0 Oct 12 20:21 qemu-s390x --w------- 1 root root 0 Oct 12 20:19 register -rw-r--r-- 1 root root 0 Oct 12 20:19 status ⚡ root@kofj-hk ~ cat /proc/sys/fs/binfmt_misc/qemu-aarch64 enabled interpreter /usr/bin/qemu-aarch64 flags: OCF offset 0 magic 7f454c460201010000000000000000000200b7 mask ffffffffffffff00fffffffffffffffffeffff 使用 buildx 构建前置依赖注备好后,我们终于可以使用 buildx 构建多平台镜像了。与其它方案不同的是,使用 buildx 可以让我们不必改动 dockerfile。
Buildx 始终使用 BuildKit 引擎构建镜像,不需要配置环境变量DOCKER_BUILDKIT=1。BuildKit 可以很好的用于多个平台的构建,而不仅适用于我们当前构建镜像时所使用的平台和操作系统。进行构建时,使用 --platform标志可以用于指定构建输出的目标平台(例如 linux/amd64,linux/arm64,linux/riscv64)。
首先,我们先准备好 Dockerfile 文件:
FROM golang:1.14 as builder COPY . /src WORKDIR /src RUN ls -al && go build -a -tags netgo -ldflags '-w' -mod=vendor -v -o /src/bin/webapp /src/cmd/main.go # Final image FROM ubuntu:18.04 LABEL authors="Fanjian Kong" COPY --from=builder /src/bin/webapp /app/ WORKDIR /app CMD ["/app/webapp"]然后,让我们尝试下运行 buildx。
✘ ⚡ root@kofj-hk ~ docker buildx build --platform linux/amd64,linux/arm64,linux/arm -t harbor-community.tencentcloudcr.com/multi-arch/demo:2020-10-12 . --push error: auto-push is currently not implemented for docker driver, please create a new builder instance居然报错 error: auto-push is currently not implemented for docker driver, please create a new builder instance 了!别担心,这是因为 Docker 默认的 builder 是不支持多平台构建的。我们可以通过 docker buildx ls 查看当前节点上的 builder 有哪些。
⚡ root@kofj-hk ~ docker buildx ls NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS default * docker default default running linux/amd64, linux/386为了使用多平台构建功能,我们需要新建一个 builder,并设置当前 builder 为新建的。
# 新建同时切换 builder docker buildx create --use --name mybuilder # 只新建,然后再切换 builder docker buildx create --name mybuilder docker buildx use mybuilder现在,让我们再次执行 buildx,看着一切向着期待的方向发展了。
注意事项