在多平台构建Docker image
写在前面
Docker从2013年在GitHub开源之后,就开始飞速发展。现在已经成为程序员不可或缺的技术,在开发中避免了“这个在我电脑上能跑啊”的尴尬,运维工程师能快速地在服务器上部署应用。有了Ansible之后,更是能达到万箭齐发的效果。docker的镜像通常是build在AMD64架构里,但是对于常出现在嵌入式系统(比如树莓派)里的ARM64架构docker的镜像又要怎么构建呢?或者是在目标平台上构建,但是这种IoT的单片机的性能又十分堪忧。还有别的什么方法吗?
解决方案
一般来说有两种选择:
- 在性能好些的电脑上直接仿真出来目标平台的架构
- 就是用交叉编译cross-complie
仿真
有一个项目叫做QEMU,它可以仿真出非常多的平台。在docker 的19.03版本之后,有个docker buildx功能。这个功能集成了QEMU的特性,使docker的镜像可以在其他的平台上构建。这个功能不需要你修改你的dockerfile,它可以自动检测出可以用的架构。当它需要对不同的架构运行一个二进制文件时,它会自己从binfmt_misc 处理器中已经注册的架构去加载对应的二进制文件。当然,我们需要手动在binfmt_misc处理器里去注册我们想要的架构的。
交叉编译
第二种方法就是交叉编译,对于做过嵌入式的同学cross compile应该不会觉得陌生。简单来说,交叉编译就是可以生成和编译器所在的架构不一样的在其他架构上能运行的可执行文件。就相当于在一个AMD64的架构电脑上编译一个能在ARM64上跑的程序。对于树莓派来说,如果想直接在上面写代码,要么需要弄一个vncviewer,要么就是直接命令行通过文本编辑器vim之类的。但是一旦项目大起来,就很麻烦。所以一般都会选择Eclipse或者VS Code这样的IDE。想让在其他平台写的代码在ARM上跑起来,就需要交叉编译。和仿真器相比,这样做的话简直不要消耗什么系统资源。
实际使用
上面说,docker在19.03版本之后添加了一个实验性的功能:docker buildx.
通过这个命令,可以直接在build的时候指明要在什么平台上构建image。
为了方便,可以直接运行docker的特权容器。
docker run --privileged --rm docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64
这个docker image里面已经有一些常见的架构。另外还有一些架构,可以通过qus projectj再加入。
通过 ls -al /proc/sys/fs/binfmt_misc/ 指令,我们能看到这个binfmt_misc的服务已经开启了
$ ls -al /proc/sys/fs/binfmt_misc/
total 0
drwxr-xr-x 2 root root 0 Aug 28 15:38 .
dr-xr-xr-x 1 root root 0 Aug 28 15:38 ..
-rw-r--r-- 1 root root 0 Aug 28 15:38 jar
-rw-r--r-- 1 root root 0 Aug 28 15:38 python2.7
-rw-r--r-- 1 root root 0 Aug 28 15:38 python3.5
-rw-r--r-- 1 root root 0 Sep 6 14:23 qemu-aarch64
-rw-r--r-- 1 root root 0 Sep 6 14:23 qemu-arm
-rw-r--r-- 1 root root 0 Sep 6 14:23 qemu-ppc64le
-rw-r--r-- 1 root root 0 Sep 6 14:23 qemu-riscv64
-rw-r--r-- 1 root root 0 Sep 6 14:23 qemu-s390x
--w------- 1 root root 0 Aug 28 15:38 register
-rw-r--r-- 1 root root 0 Aug 28 15:38 status
在通过 cat /proc/sys/fs/binfmt_misc/qemu-aarch64 看看我们的想要的处理目标平台的handler有没有启用
$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
enabled
interpreter /usr/bin/qemu-aarch64
flags: OCF
offset 0
magic 7f454c460201010000000000000000000200b7
mask ffffffffffffff00fffffffffffffffffeffff
随后需要新实例化一个docker builder,我们给它起名叫testbuilder
$ docker buildx create --name testbuilder
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
testbuilder docker-container
testbuilder0 unix:///var/run/docker.sock inactive
default * docker
default default running linux/amd64, linux/386
能看到现在有了两个docker builder,但是我们新建的builder还处于inactive的状态。
通过以下这个命令,可以构建启动新的docker builder
$ docker buildx inspect --bootstrap
[+] Building 9.8s (1/1) FINISHED
=> [internal] booting buildkit 9.8s
=> => pulling image moby/buildkit:buildx-stable-1 8.5s
=> => creating container buildx_buildkit_testbuilder0 1.3s
Name: testbuilder
Driver: docker-container
Nodes:
Name: testbuilder0
Endpoint: unix:///var/run/docker.sock
Status: running
Platforms: linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
我们在github随便找一个docker项目,给它pull下来。然后使用以下指令,就可以给多个平台build docker image了。最后我们通过最后的--push 把构建好的docker image 上传到docker hub。
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t ajeetraina/docker-cctv-raspbian . --push
最后可以在终端设备上直接把想要的docker image 拉下来,就可以运行了。
最后
这篇文章梳理了,如何在任意一台架构的机器上构建docker image。使用docker的试验功能buildx后,无需对Dockerfile进行更改,就可以为其他平台构建image。通过--platform指明目标平台就好。当然,这个--platform可以集成到manifest.yaml文件中,方便在工程里直接构建。
引用
- https://www.docker.com/blog/multi-platform-docker-builds/
- https://juejin.im/post/6844904008801320967
- https://blog.lyle.ac.cn/2020/04/14/transparently-running-binaries-from-any-architecture-in-linux-with-qemu-and-binfmt-misc/
- https://collabnix.com/building-arm-based-docker-images-on-docker-desktop-made-possible-using-buildx/