默认情况下 Docker 在机器上只能构建当前机器架构的镜像,本文实践了在 x86_64 下构建 arm64 架构的镜像,并推送多架构镜像到 Docker Hub,记录如下
环境信息
系统:Ubuntu 20.04 x86_64
Docker 版本:24.0.7
buildx 版本:0.12.0
不能使用 Centos 7 进行试验 🧪,下文有补充说明
安装 docker-buildx 插件
buildx 是 Docker CLI 的插件,它通过 BuildKit 提供了扩展的构建能力。
通过包管理器安装比较方便,但仓库中的 buildx 一般不是最新版本,更推荐手动安装最新版本。
通过二进制手动安装(推荐)
下载并放置插件,下载地址:Github buildx release
$ mkdir -p $HOME/.docker/cli-plugins
$ wget https://github.com/docker/buildx/releases/download/v0.12.0/buildx-v0.12.0.linux-amd64 -O $HOME/.docker/cli-plugins/docker-buildx
$ chmod +x $HOME/.docker/cli-plugins/docker-buildx
查看插件版本
$ docker buildx version
github.com/docker/buildx v0.12.0 542e5d810e4a1a155684f5f3c5bd7e797632a12f
通过 Linux 自带包管理器
# Ubuntu
$ sudo apt install docker-buildx-plugin
# Centos
$ sudo yum install docker-buildx-plugin
当前 Ubuntu 和 Centos 仓库中版本均为 v0.11.2,稍落后于最新版本 v0.12.0
了解 binfmt_misc
在继续之前,先简单了解下 binfmt_misc,仅了解即可
binfmt_misc 是 Linux 内核的一项功能,全称是混杂二进制格式的内核支持(Kernel Support for miscellaneous Binary Formats),它能够使Linux支持运行几乎任何格式的程序,包括编译后的 Java、Python 或 Emacs 程序。
为了能够让 binfmt_misc 运行任意格式的程序,至少需要做到两点:特定格式二进制程序的识别方式,以及其对应的解释器位置。虽然 binfmt_misc 听上去很强大,其实现的方式却意外地很容易理解,类似于 bash 解释器通过脚本文件的第一行(如#!/usr/bin/python3)得知该文件需要通过什么解释器运行,binfmt_misc 也预设了一系列的规则,如读取二进制文件头部特定位置的魔数,或者根据文件扩展名(如.exe、.py)以判断可执行文件的格式,随后调用对应的解释器去运行该程序。Linux默认的可执行文件格式是elf,而binfmt_misc的出现拓宽了Linux的执行限制,将一点展开成一个面,使得各种各样的二进制文件都能选择它们对应的解释器执行。
binfmt_misc 模块自 Linux 2.6.12-rc2 版本中引入,先后经历了几次功能上的略微改动,一是 3.18 版本中将解释器路径长度限制从原来的 255 字节拓宽到 1920 字节,二是在 4.8 版本中新增 “F”(fix binary,固定二进制)标志位,使 mount 命名空间变更和 chroot 后的环境中依然能够正常调用解释器执行二进制程序。由于我们需要构建多架构容器,必须使用“F”标志位才能 binfmt_misc 在容器中正常工作,因此内核版本需要在4.8以上才可以。
CentOS 7目前使用的内核是 3.10,如果想要让 CentOS 7 构建多架构容器,那么只能够采用升级内核的方法解决,可安装 elrepo 中的 kernel-ml 内核软件包,也可自己编译内核并替换。
这里解释了我之前在 Centos7 系统下执行命令 tonistiigi/binfmt 报错的问题,因为 Centos 7 的内核太老了!
[opc@centos-7 ~]$ sudo docker run --privileged --rm tonistiigi/binfmt --install all
installing: ppc64le cannot register "/usr/bin/qemu-ppc64le" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
installing: arm64 cannot register "/usr/bin/qemu-aarch64" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
installing: arm cannot register "/usr/bin/qemu-arm" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
installing: s390x cannot register "/usr/bin/qemu-s390x" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
installing: riscv64 cannot register "/usr/bin/qemu-riscv64" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
installing: mips64le cannot register "/usr/bin/qemu-mips64el" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
installing: mips64 cannot register "/usr/bin/qemu-mips64" to /proc/sys/fs/binfmt_misc/register: write /proc/sys/fs/binfmt_misc/register: invalid argument
由于人工注册解释器的方式过于繁琐,社区的开发者们提供了专门的程序用来注册各类架构的解释器,并封装成容器镜像,镜像中就包含了注册程序以及各类 qemu 模拟程序。
此处所说的就是 tonistiigi/binfmt 镜像做的事情,接下来使用它来安装注册多架构解释器。
安装二进制文件格式解释器
先使用 docker buildx ls
查看当前系统中 Docker Buildx 支持使用的架构
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker
default default running v0.11.7+d3e6c1360f6e linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386
安装二进制文件格式解释器:
$ docker run --privileged --rm tonistiigi/binfmt --install all
installing: arm64 OK
installing: arm OK
installing: s390x OK
installing: ppc64le OK
installing: riscv64 OK
installing: mips64le OK
installing: mips64 OK
...
安装后再次查看架构支持
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default * docker
default default running v0.11.7+d3e6c1360f6e linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
在列表中已经可以看到 linux/arm64 等不同平台架构。
稍做补充:
buildx 插件本质上调用了 buildkit 的 api,构建是在 buildkit 的环境中进行的。是否支持多架构,取决于 buildkit 的环境,如果需要 buildkit 支持多架构,需要在宿主机执行。
通过运行 tonistiigi/binfmt 镜像并执行 --install all 命令,会注册并安装适用于不同架构的二进制文件格式解释器。这样,BuildKit 可以在构建过程中根据需要适配不同的架构,实现跨平台的构建。
创建并使用新的构建实例
使用 docker-container 驱动程序创建一个新的构建器,它可以让您访问更复杂的功能,比如多平台构建和更高级的缓存导出功能。这些功能在默认的 docker 驱动程序中目前不受支持。
$ docker buildx create --name mybuilder --bootstrap --use
在 docker buildx create 命令中,--bootstrap 是可选标志,作用是在创建完构建实例后,立即初始化这个实例,包括下载所需的镜像和运行环境,未指定时,这个实例会在后续的第一次构建任务中自动完成初始化。
交叉编译
以下步骤在 amd64 系统下执行,构建出 arm64 架构的镜像
创建示例程序
$ go mod init hello-world
main.go
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("the current platform architecture is %s.\n", runtime.GOARCH)
}
编译程序
$ GOARCH=arm64 GOOS=linux go build main.go
# 正常在 amd64 平台下编译出 arm64 架构的 go 可执行程序,无法正常运行。
# 但是因为前面安装 tonistiigi/binfmt,所以此处能执行,把它复制到其它 amd64 平台就无法执行了
# 虽然此处能运行,但不要依赖这个特性用于生产环境的二进制执行,性能不行
$ ./main
the current platform architecture is arm64.
$ uname -m
x86_64
构建镜像
Dockerfile
# 使用 alpine 镜像作为基础
FROM alpine:latest
# 将主程序复制到容器中
COPY main /app/main
# 设置工作目录
WORKDIR /app
# 运行主程序
CMD ["./main"]
使用 Buildx 插件构建镜像
$ docker buildx build -t hello-world --platform=linux/arm64 --load .
不指定 --load 参数,构建的镜像不会保存到本地镜像仓库,也可以指定 --push 推送到 Docker Hub 镜像仓库。
构建后使用 docker inspect hello-world:latest
查看镜像详情,可以从输出看到架构信息:
"Architecture": "arm64",
"Os": "linux",
制作多平台镜像
体验了交叉编译,接下来制作多平台的镜像
# 第一阶段:构建
FROM golang:1.21 as builder
WORKDIR /src
# 复制 Go 相关文件
COPY go.mod .
COPY main.go .
# 接受 TARGETPLATFORM 作为参数
ARG TARGETPLATFORM
# 设置环境变量来选择正确的 CROSS-COMPILE 配置
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -f1 -d /) \
&& export GOARCH=$(echo ${TARGETPLATFORM} | cut -f2 -d /) \
&& go build -o /app/main .
# 第二阶段:构建最终镜像
FROM alpine
WORKDIR /app
# 从第一阶段复制可执行文件
COPY --from=builder /app/main /app/main
ENTRYPOINT ["./main"]
TARGETPLATFORM 变量是 buildx 支持的变量,非原生 docker build 变量。
构建并推送
$ docker buildx build -t kissbug8720/hello-world:latest --platform=linux/arm64,linux/amd64 --push .
注意此处的仓库地址 "kissbug8720/hello-world" 需要替换为你自己的地址
此处需要指定 --push 参数,因为 --load 参数下不支持生成镜像清单(manifest list),会构建失败。
另外构建前先执行 docker login 登录
运行容器查看结果
访问:https://hub.docker.com/r/kissbug8720/hello-world
在页面上已经可以看到 linux/amd64 和 linux/arm64 两个架构的镜像,在不同的系统下启动容器,docker 会根据 Manifest 信息拉取当前架构的镜像运行
# 在 x86_64 架构系统运行
$ docker run --rm kissbug8720/hello-world:latest
the current platform architecture is amd64.
# 在 aarch64 架构系统运行
$ docker run --rm kissbug8720/hello-world:latest
the current platform architecture is arm64.
至此,构建多平台的镜像完成。
查看 Manifest 信息
可以使用以下命令查看镜像的 Manifest 信息。
$ docker buildx imagetools inspect kissbug8720/hello-world:latest
Name: docker.io/kissbug8720/hello-world:latest
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:eeea71ba456ed4ad82d58bee83eb1cff81cdb499fb3dfe1529e0464d9758290b
Manifests:
Name: docker.io/kissbug8720/hello-world:latest@sha256:79c9b2ee932a75c6064595dc1827270bbbba6638f9de244d944574ce1c706de7
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/arm64
Name: docker.io/kissbug8720/hello-world:latest@sha256:ddd2394b302e380772bf7633894cfdceef466d55e2fcd54a4653cae598eb28e3
MediaType: application/vnd.oci.image.manifest.v1+json
Platform: linux/amd64
多架构镜像制作先学习到这里