如何在 X86 设备上使用 Docker 构建 ARM 镜像

Category 碎碎念

最近一直在使用华为 ModelArts 的计算平台,使用这类计算平台的一般流程是先在本地用 Docker 构建镜像,再上传至云端,然后就可以在该环境下部署具体的计算作业了。使用 Docker 构建环境非常方便,基于官方或其他用户提供的基础镜像安装上自己所需要的依赖就可以直接上传使用了,完全不用跟驱动安装等等令人头疼又心累的事情打交道。

但在使用 Docker 构建镜像时,有一个挺棘手的问题:计算平台或是服务器所使用的设备一般是 ARM 架构,个人电脑使用基本上是 X86 架构。由于二者 CPU 指令集不同,尽管可以在 X86 设备上用 docker pull --platform=linux/arm64 拉取用于 ARM 设备的镜像,但无法使用 docker rundocker build 运行或是通过构建的方法修改该镜像。

qemu-user-static

去寻找 ARM 设备再使用 Docker 构建镜像就太麻烦了,幸好找到了一个工具 qemu-user-static,专门用于解决这个问题。先来看看仓库中给出的示例:

$ uname -m
x86_64

$ docker run --rm -t arm64v8/ubuntu uname -m
standard_init_linux.go:211: exec user process caused "exec format error"

$ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

$ docker run --rm -t arm64v8/ubuntu uname -m
aarch64
  • 第一行的 uname -m 用于检测宿主机的架构,终端给出的信息表明这是一台 X86 设备。
  • 第二行命令用 Docker 运行 arm64v8/ubuntu 镜像,并运行同样的 uname -m,当然由于架构不同,无法运行该镜像,给出了 standard_init_linux.go:211: exec user process caused "exec format error" 错误。在使用 Dockerfile 构建镜像时,遇到类似的 exec /bin/bash: exec format error 错误也需要考虑是不是架构的问题。
  • 运行 qemu-user-static 镜像后,arm64v8/ubuntu 就可以成功运行了,终端给出的信息表明 arm64v8/ubuntu 是一个用于 ARM 设备的镜像。

简单来说,qemu-user-static 通过 QEMU 模拟器模拟出了 ARM 设备,从而实现在 X86 设备上运行或是构建 ARM 镜像。当然,qemu-user-static 能模拟的硬件不仅限于 ARM,对于支持的硬件,官网上有更详细的介绍。

qemu-user-static 的安装和使用都可以通过以下命令完成,若本地不存在该镜像,Docker 会自动从云端拉取:

$ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

也有人会让 qemu-user-static 在后台一直运行,我嫌维护起来麻烦,就直接使用上面的命令,如果后台挂掉了,再运行一次就好。

Docker 常用命令

最后再记录几个创建环境时常用的 Docker 命令:

# 检查镜像的架构
$ docker inspect {image_name}:{tag} | grep "Architecture"

# 用终端交互模式进入镜像的 /bin/bash
$ docker run -it {image_name}:{tag} /bin/bash

# 使用当前文件夹中的 Dockerfile 构建镜像,不使用缓存并输出详细信息
$ docker build -t {image_name}:{tag} . --progress=plain --no-cache

Dockerfile 中记录了配置镜像的所有步骤,其他人也可以通过分享出去的 Dockerfile 构建相同的环境。而在撰写 Dockerfile 时,由于不熟悉基本镜像,一般都需要参考着终端给出的反馈来修改 Dockerfile 中的命令。这时候使用 docker run -it 就很方便,特别是运行 qemu-user-static 后,可以直接进入 ARM 镜像的交互终端中,一步步安装依赖后再保存命令。

上面的方法在简单的镜像中尚可,有的基本镜像做了特别复杂的操作,就算使用 qemu-user-static 也无法执行 docker run,这种情况下就必须根据 docker build 给出的错误信息修改 Dockerfile 了。在对 Dockerfile Debug 时,指定 --progress=plain --no-cache 两个参数能输出更为完整的错误。


References