使用 docker buildx 构建跨平台镜像

buildx是Docker官方提供的一个构建工具,它可以帮助用户快速、高效地构建Docker镜像,并支持多种平台的构建。使用buildx,用户可以在单个命令中构建多种架构的镜像,例如x86和arm架构,而无需手工操作多个构建命令。此外buildx还支持Dockerfile的多阶段构建和缓存,这可以大大提供镜像构建的效率和速度。

buildx是一个管理Docker构建的CLI插件,底层使用了BuildKit扩展了Docker构建功能。

BuildKit是Docker官方提供一个高性能构建引擎,可以用来替代Docker原有的构建引擎。相比于原有引擎,BuildKit具有更快的构建速度、更高的并行性、更少的资源占用和更好的安全性。

要安装并使用buildx,需要Docker Engine版本号大于等于19.03。

跨平台镜像构建策略

builder支持是那种不通策略构建跨平台镜像。

在内核中使用QEMU仿真支持

如果你正在使用Docker Desktop,则已经支持了QEMU,QEMU是最简单的构建跨平台镜像策略。它不需要对原有的Dockerfile进行任何更改,BuildKit会通过binfmt_misc这一Linux内核功能实现跨平台程序的执行。

工作原理:

QEMU是一个处理器模拟器,可以模拟不通的CPU架构,我们可以把它理解为是另一种形式的虚拟机。在buildx中,QEMU用于在构建过程中执行非本地架构的二进制文件。例如,在x86主机上构建一个ARM镜像时,QEMU可以模拟ARM环境并运行ARM二进制文件。

binfmt_misc是Linux内核的一个模块,它允许用户注册可执行文件格式和响应的解释器。当内核遇到未知格式的可执行文件时,会使用binfmt_misc查找与该文件格式关联的解释器(在这种情况下是QEMU)并运行文件。rk3568的平台上此功能是关闭的。

QEMU和binfmt_misc的结合使得通过buildx跨平台构建成为可能。这样我们就可以在一个架构的主机上构建针对其他架构的Docker镜像,而无需拥有实际的目标硬件。

虽然Docker Desktop预配置了binfmt_misc对其他平台的支持,但对于其他版本Docker,你可能需要使用tonistiigi/binfmt镜像启动一个特权容器来进行支持。

复制代码
docker run --privileged --rm tonistiigi/binfmt --install all

运行此命令会安装不同架构的解释器程序,即/usr/bin目录下的qemu模拟器:

在我们编译服务器上加载了binfmt_misc模块:

docker buildx使用qemu的作用

Docker Buildx 是 Docker 的一个实验性功能,它扩展了 Docker 的构建功能,包括使用多节点、qemu 等。QEMU 是一个开源的虚拟机软件,它可以模拟不同的 CPU 和其他硬件,使得我们可以在一个操作系统上构建另一种操作系统的镜像。

使用 Docker Buildx 的 qemu 功能,可以帮助我们构建面向多种不同架构(如 ARM、MIPS 等)的 Docker 镜像。

以下是一个简单的例子,展示如何使用 Docker Buildx 和 QEMU 来构建一个面向 ARM 架构的 Docker 镜像:

复制代码
# 创建一个新的 buildkit 实例
docker buildx create --name mybuilder --use
 
# 启动 buildkit 实例
docker buildx start mybuilder
 
# 启用 QEMU 驱动支持
docker buildx inspect --bootstrap
 
# 构建一个面向 ARM 架构的 Docker 镜像
docker buildx build --platform linux/arm/v7 -t myimage:latest .

在这个例子中,--platform 参数指定了我们想要构建的目标平台是 ARM v7。这样,Docker 会使用 QEMU 来模拟一个 ARM 环境,并在 x86 架构的机器上构建出面向 ARM 架构的 Docker 镜像。

binfmt_misc文件系统

binfmt-misc是Linux内核提供的一种类似windows上文件关联的功能,但比文件关联更强大的是,它不仅可以根据文件后缀名判断,还可以根据文件内容(Magic Bytes)使用不同的程序打开。一个典型的使用场景就是:使用qemu运行其他架构平台上的二进制文件。

开启binfmt-misc

临时开启可以使用以下命令:

复制代码
$ sudo mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc

这种方式重启后会失效,如果想长期生效,可以在/etc/fstab文件中增加一行:

复制代码
none  /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0

可以使用以下命令检查开启是否成功:

复制代码
$ mount | grep binfmt_misc
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,relatime)
$ ls -l /proc/sys/fs/binfmt_misc
总用量 0
--w------- 1 root root 0 2月   5 22:55 register
-rw-r--r-- 1 root root 0 2月   5 22:55 status

先准备一个arm64架构的程序,执行后发现有报错:

复制代码
bash: ./go-test:无法执行二进制文件: 可执行文件格式错误

现在,我们执行一下apt install qemu-user-binfmt命令,然后再运行上面的arm64程序,发现能正常运行了。安装qemu-user-binfmt后,会在/proc/sys/fs/binfmt_misc目录下创建若干个文件,其中就有一个qemu-aarch64,来看一下这个文件的内容:

复制代码
root@ubuntu:/proc/sys/fs/binfmt_misc# cat qemu-aarch64 
enabled
interpreter /usr/bin/qemu-aarch64-static
flags: OC
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff
root@ubuntu:/proc/sys/fs/binfmt_misc#

这个文件描述的是规则文件:

第一行enabled表示该规则启用 ;

第二行 interpreter /usr/bin/qemu-aarch64-static表示使用/usr/bin/qemu-aarch64-static来执行二进制文件;

第三行:OC表示运行的标志位,具体含义如下:

P: 表示perserve-argv,这意味着在调用模拟器时,原始的参数(argv)将被保留。这对于默写程序在运行时需要知道它们自己的名称(即argv0)的情况很有用

O: 表示offset,这意味着在启动模拟器之前,需要从二进制文件中读取一个偏移量,这个偏移量将作为模拟器的一个参数

C:表示credentials,这意味着模拟器将使用于原始程序相同的用户ID和组ID运行,这有助于确保模拟器在运行时与原始程序相同的权限。

第四行:offset 0表示从0便宜值开始读取文件;

第五行: magic 7f454c460201010000000000000000000200b700表示要匹配的模数字节;

arm64架构的ELF文件头部的magic字段如下,也就是说binfmt_misc文件系统可以根据ELF文件中的magic字段来决定此文件使用哪个架构的模拟器运行:

下面是两个不同架构的

mips架构的: 7f454c4601020100000000000000000000020008

arm64架构的:7f454c460201010000000000000000000200b700

第六行:mask ffffffffffffff00fffffffffffffffffeffffff 表示字节掩码,用哦过来忽略掉文件中的一些不重要的字节。

在x86_64系统中运行arm64架构的Docker镜像

现在我们用docker命令运行一个arm64的镜像:

复制代码
$ docker run -it arm64v8/ubuntu bash
Unable to find image 'arm64v8/ubuntu:latest' locally
latest: Pulling from arm64v8/ubuntu
005e2837585d: Pull complete 
Digest: sha256:ba545858745d6307f0d1064d0d25365466f78d02f866cf4efb9e1326a4c196ca
Status: Downloaded newer image for arm64v8/ubuntu:latest
standard_init_linux.go:207: exec user process caused "no such file or directory"

通过一番探索之后,发现只要执行下命令:apt install qemu-user-static,再启动docker容器就正常了。

使用 Dockerfile 中的多阶段交叉构建

交叉编译的复杂度不在于Docker,而是取决于程序本身。比如Go程序就很容易实现交叉编译,只需要在使用go build构建程序时执行GOOS、GOARCH两个环境变量即可实现。

创建builder

要使用buildx构建跨 平台镜像,我们需要先创建一个builder,可以翻译为构建器。

使用docker buildx ls命令可以查看builder列表:

复制代码
root@ubuntu:/proc# docker buildx ls
NAME/NODE         DRIVER/ENDPOINT             STATUS  PLATFORMS
mybuild *         docker-container                    
  mybuild0        unix:///var/run/docker.sock running linux/arm64*, linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/amd64/v4, linux/386
vigilant_hugle    docker-container                    
  vigilant_hugle0 unix:///var/run/docker.sock stopped 
default           docker                              
  default         default                     running linux/amd64, linux/386

*号表示当前正在使用的builder,当我们运行docker build命令时就是在使用此builder构建镜像。第二列的DRIVER/ENDPOINT表示使用的驱动程序。buildx支持以下几种驱动程序:

  • docker:使用捆绑到Docker守护进程中的BuildKit库,就是安装Docker后默认的BuildKit。
  • docker-container:使用Docker新创建一个专用的BuildKit容器。
  • kubernetes: 在kubernetes集群中创建一个BuildKit Pod。
  • remote:直接连接到手工管理的BuildKit守护进程。

因为使用docker驱动程序的默认builder不支持使用单条命令(默认builder的--platform参数只接收单个值)构建跨平台镜像,所以我们需要使用docker-container驱动创建一个新的builder。

命令语法如下:

复制代码
$ docker buildx create --name=<builder-name> --driver=<driver> --driver-opt=<driver-options>

参数含义如下;

--name: 构建起名称,必填。

--driver:构建器驱动程序,默认为docker-container。

--driver-opt:驱动程序选项,如选项--driver-opt=image=moby/buildkit:v0.11.3可以安装指定版本的BuildKit,默认值为moby/buildkit。

我们可以使用如下命令创建一个新的builder:

复制代码
$ docker buildx create --name mybuilder
mybuilder

再次查看 builder 列表:

复制代码
$ docker buildx ls
NAME/NODE       DRIVER/ENDPOINT             STATUS   BUILDKIT PLATFORMS
mybuilder *     docker-container
  mybuilder0    unix:///var/run/docker.sock inactive
default         docker
  default       default                     running  20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux   docker
  desktop-linux desktop-linux               running  20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

可以发现选中的构建起已经切换到了Mybuilder,如果没有选中,你需要手动使用docker buildx use mybuilder命令切换构建器。

启动 builder

我们新创建的mybuilder当前状态为inactive,需要启动才能使用。

复制代码
$ docker buildx inspect --bootstrap mybuilder
[+] Building 16.8s (1/1) FINISHED
 => [internal] booting buildkit                                                                                                                                  16.8s
 => => pulling image moby/buildkit:buildx-stable-1                                                                                                               16.1s
 => => creating container buildx_buildkit_mybuilder0                                                                                                              0.7s
Name:   mybuilder
Driver: docker-container

Nodes:
Name:      mybuilder0
Endpoint:  unix:///var/run/docker.sock
Status:    running
Buildkit:  v0.9.3
Platforms: linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6

inspect子命令用来检查构建起状态,使用--bootstrap参数可以启动mybuilder构建器,再次查看builder列表,mybuilder状态已经变成了running。

复制代码
$ docker buildx ls
NAME/NODE       DRIVER/ENDPOINT             STATUS  BUILDKIT PLATFORMS
mybuilder *     docker-container
  mybuilder0    unix:///var/run/docker.sock running v0.9.3   linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6
default         docker
  default       default                     running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
desktop-linux   docker
  desktop-linux desktop-linux               running 20.10.21 linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

其中PLATFORMS一列所展示的值

linux/arm64, linux/amd64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6 就是当前构建器所支持的所有平台了。

现在使用docker ps命令可以看到mybuilder构建器所对应的BuildKit容器已经启动。

复制代码
$ docker ps
CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                                NAMES
b8887f253d41   moby/buildkit:buildx-stable-1   "buildkitd"              4 minutes ago   Up 4 minutes                                        buildx_buildkit_mybuilder0

这个容器就是辅助我们构建跨平台镜像用的,不要手动删除它。

使用 builder 构建跨平台镜像

复制代码
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-go .

docker buildx build 语法跟 docker build 一样, --platform 参数表示构建镜像的目标平台, -t 表示镜像的 Tag, . 表示上下文为当前目录

唯一不通的是对--platform参数的支持,docker build的--platform参数只支持传递一个平台信息,如--platform linux/arm64,也就是一次能构建单个平台的镜像。

而使用docker buildx build构建镜像则支持同时传递多个平台信息,中间使用英文逗号分隔,这样就实现了只用一条命令便可以构建多个跨平台镜像的功能。

执行以上命令后,我们将会得到一条警告:

复制代码
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load

这条警告提示我们没有为docker-container驱动程序指定输出,生成结果将只会保留在构建缓存中,使用--push可以将镜像推送到Docker Hub远程仓库,使用--load可以将镜像保存在本地。

这是因为我们新创建的mybuilder是启动了一个容器来运行BuildKit,它并不能直接将构建好的跨平台镜像输出到本机或推送到远程,必须要用户来手动指定输出位置。

我们可以尝试指定--load将镜像保存在本地主机。

复制代码
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-go . --load
[+] Building 0.0s (0/0)
ERROR: docker exporter does not currently support exporting manifest lists

结果会得到一条错误日志。看来它并不支持将跨平台镜像输出到本地,这其实是因为传递了多个--platform的关系,如果--platform只传递了一个平台,则可以使用--load将构建好的镜像输出到本机。

那么我们就只能通过--push参数将跨平台镜像推送到远程仓库了。不过在此之前需要确保使用docker login完成登录。

复制代码
$ docker buildx build --platform linux/arm64,linux/amd64 -t jianghushinian/hello-go . --push

现在登录Docker Hub就可以看见推送上来的跨平台镜像了。

我们也可以使用imagestools来检查跨平台镜像的manifest信息,这条命令只能用来获取仓库中的image信息,本地的images无法查看。

复制代码
$ docker buildx imagetools inspect jianghushinian/hello-go
Name:      docker.io/jianghushinian/hello-go:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:51199dadfc55b23d6ab5cfd2d67e38edd513a707273b1b8b554985ff562104db

Manifests:
  Name:      docker.io/jianghushinian/hello-go:latest@sha256:8032a6f23f3bd3050852e77b6e4a4d0a705dfd710fb63bc4c3dc9d5e01c8e9a6
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/arm64

  Name:      docker.io/jianghushinian/hello-go:latest@sha256:fd46fd7e93c7deef5ad8496c2cf08c266bac42ac77f1e444e83d4f79d58441ba
  MediaType: application/vnd.docker.distribution.manifest.v2+json
  Platform:  linux/amd64

可以看到,这个跨平台镜像包含了两个目标平台的镜像,分别是linux/arm64和linux/amd64。

相关推荐
用户0328472220701 天前
如何搭建本地yum源(上)
运维
武子康1 天前
调查研究-183 Apple container:Mac 上用轻量 VM 跑 Linux 容器,Swift 会改写本地容器体验吗?
docker·容器·apple
大树884 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠4 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质4 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
Inhand陈工4 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
Alsn864 天前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker
酣大智4 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_4 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉4 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造