本文对在x86机器上构建arm架构的image进行研究,参考文章qemu-user-static, Docker。
背景:Docker镜像技术普及之后,出现了云端和边缘端。云端主要使用Intel机器构成,底层架构多为x86_64(amd64),而边缘端都是由arm设备组成,其底层架构很多,如文章中的aarch64。云端设备资源多,功耗大,而边缘端设备资源少但功耗也小。若在边缘端生成Docker镜像文件,需要在时间和资源上做权衡。因此,云端生成边缘端镜像便成了另一种选择。
构建arm镜像的方法:
本文介绍如何在x86机器上模拟arm架构指令来构建arm架构的镜像。我们这里使用multiarch/qemu-user-static来实现在x86主机上模拟arm环境,即执行arm的指令。
下面是使用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
示例主机为x86_64架构,当在主机上运行arm架构的镜像并在该镜像上执行命令时,报错,因为x86架构解析不了arm架构的指令。但在执行qemu-user-static镜像后,重新运行arm架构的镜像便可以,因为qemu-user-static将arm架构的指令解释成x86架构的指令执行。qemu-user-static支持很多ARM架构。
qemu-user-static就是一组静态的二进制文件qemu-$arch-static,作为interpreter,来执行特定架构的可执行文件。
$ uname -m
x86_64
$ file bin/hello-aarch64
bin/hello-aarch64: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=fa19c63e3c60463e686564eeeb0937959bd6f559, for GNU/Linux 3.7.0, not stripped, too many notes (256)
$ bin/hello-aarch64
bash: bin/hello-aarch64: cannot execute binary file: Exec format error
$ qemu-aarch64-static bin/hello-aarch64
Hello World!
当qemu-user-static和binfmt_misc一起使用,便能模拟各种不同架构。
qemu-user-static 镜像
qemu-user-static是一组镜像,$version为QEMU的版本,$from_arch为host architecture,$to_arch为guest architecture。
multiarch/qemu-user-static image
multiarch/qemu-user-static:$version images
multiarch/qemu-user-static:$from_arch-$to_arch images
multiarch/qemu-user-static:$from_arch-$to_arch-$version images
multiarch/qemu-user-static:$to_arch images
multiarch/qemu-user-static:$to_arch-$version images
multiarch/qemu-user-static:register image
涉及3种文件:
multiarch/qemu-user-static:$version镜像中包含register脚本及所有二进制文件/usr/bin/qemu-$arch-static;multiarch/qemu-user-static:$to_arch中包含register脚本及$to_arch对应的二进制文件;multiarch/qemu-user-static:register中只包含register脚本。
执行container时,register脚本注册除了当前架构之外的所有支持的processors对应的entry files /proc/sys/fs/binfmt_misc/qemu-$arch。由于这些文件在主机和container中是一样的,register脚本修改主机上的文件。
/proc/sys/fs/binfmt_misc/qemu-aarch64内容如下:
enabled
interpreter /usr/bin/qemu-aarch64-static
flags: F
offset 0
magic 7f454c460201010000000000000000000200b700
mask ffffffffffffff00fffffffffffffffffeffffff
Docker command
语法如下:
$ docker run --rm --privileged multiarch/qemu-user-static [--reset][--help][-p yes][options]
$ docker run --rm --privileged multiarch/qemu-user-static:register [--reset][--help][options]
当/proc/sys/fs/binfmt_misc/qemu-$arch中存在同名的文件时,会报错"sh: write error: File exists"。--reset option表示register entry前移除/proc/sys/fs/binfmt_misc/下面的所有binfmt_misc entry files。-p yes表示在注册binfmt_misc entry时检查interpreter是否存在,若不存在,报错。由于multiarch/qemu-user-static:register镜像中不存在interpreter文件,所以不能使用-p yes。
register脚本执行完上面的操作,便根据提供的options执行QEMU'sscripts/qemu-binfmt-conf.sh, 脚本内容参见Script。该脚本用来配置binfmt_misc来使用qemu interpreter。
Usage: qemu-binfmt-conf.sh [--qemu-path PATH][--debian][--systemd CPU]
[--help][--credential yes|no][--exportdir PATH]
[--persistent yes|no][--qemu-suffix SUFFIX]
Configure binfmt_misc to use qemu interpreter
--help: display this usage
--qemu-path: set path to qemu interpreter ($QEMU_PATH)
--qemu-suffix: add a suffix to the default interpreter name
--debian: don't write into /proc,
instead generate update-binfmts templates
--systemd: don't write into /proc,
instead generate file for systemd-binfmt.service
for the given CPU. If CPU is "ALL", generate a
file for all known cpus
--exportdir: define where to write configuration files
(default: $SYSTEMDDIR or $DEBIANDIR)
--credential: if yes, credential and security tokens are
calculated according to the binary to interpret
--persistent: if yes, the interpreter is loaded when binfmt is
configured and remains in memory. All future uses
are cloned from the open file.
可以执行container中的二进制文件来获取一些信息。
$ docker run --rm -t multiarch/qemu-user-static:aarch64 /usr/bin/qemu-aarch64-static -help
usage: qemu-aarch64 [options] program [arguments...]
Linux CPU emulator (compiled for aarch64 emulation)
...
$ docker run --rm -t multiarch/qemu-user-static:aarch64 /usr/bin/qemu-aarch64-static -version
qemu-aarch64 version 4.0.0 (qemu-4.0.0-5.fc31)
Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
Kernel Support for miscellaneous Binary Formats
Linux kernel支持混合的binary formats,只需要告知binfmt_misc在调用binary时使用哪个interpreter。binfmt_misc通过使用magic byte sequence来mask文件开头的几个字节来识别binary type。参见文章4.10 version, 3.10 version。
下面介绍kernel version 4.10中如何使用该feature。不同kernel版本之间会有些区别。
- 加载binfmt_misc文建系统
mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc
-生成binfmt_misc entry file
下面命令用来注册新的binary type:
# echo ":$name:$type:$offset:$magic:$mask:$interpreter:$flags" > /proc/sys/fs/binfmt_misc/register
其中,name为/proc/sys/fs/binfmt_misc目录下创建的entry file的文件名;type为识别的类型,M
for magic andE
for extension;offset为文件中magic/mask的offset,字节为单位,默认为0;magic为binfmt_misc匹配的byte sequence;mask是optional的,与magic执行与操作,默认为oxff;interpreter为执行binary文件的程序,将binary文件作为第一个参数输入;flags用来控制interpreter的调用,optional,F表示在模拟器安装时就检查interpreter文件是否存在;
限制:
enable/disable binfmt_misc只需要更改/proc/sys/fs/binfmt_misc/status的内容为enable或disable。enable/disable某个binary type只需要在相应的entry file中修改enabled或disable。
移除指定的binary entry:
# echo -1 > /proc/sys/fs/binfmt_misc/qemu-$arch
实操
-构建aarch64架构的镜像
a. 执行命令来注册所有binary type的entry file。
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
b. 编写Dockerfile
---Dockerfile(/home/transwarp/arch/arm64v8)---
FROM arm64v8/ubuntu
RUN mkdir -p /usr/lib/hello
ENV HOME /usr/lib/hello
WORKDIR ${HOME}
RUN touch hello.log
ENV HELLO 'Hello World!'
RUN echo $HELLO > hello.log
c. 构建镜像
执行下面的docker命令来构建镜像
# 构建镜像
docker build -t arm64v8/hello_world /home/transwarp/arch/arm64v8/
# 查看镜像内容 hello.log中的内容为'Hello World!'
docker run -it arm64v8/hello_world bash
# 查看构建镜像的平台架构,注意:这里的"Architecture": "arm64"只是构建环境,x86_64架构也能模拟arm64环境
docker inspect arm64v8/hello_world
-构建amd64架构的镜像
由于我们平台本身就是amd64架构,所以本来就可以直接构建amd64架构的镜像。
a. 编写Dockerfile
---Dockerfile(/home/transwarp/arch/amd64)---
FROM 172.16.1.99/gold/ubuntu:18.04
RUN mkdir -p /usr/lib/hello
ENV HOME /usr/lib/hello
WORKDIR ${HOME}
RUN touch hello.log
ENV HELLO 'Hello World!'
RUN echo $HELLO > hello.log
b. 执行docker命令
# 构建镜像
docker build -t amd64/hello_world /home/transwarp/arch/amd64/
# 查看镜像内容 hello.log中的内容为'Hello World!'
docker run -it amd64/hello_world bash
# 查看构建镜像的平台架构
docker inspect amd64/hello_world
-amd64架构上直接构建arm架构镜像
若构建镜像的Dockerfile中没有RUN指令,在x86架构上可以不使用qemu-user-static镜像模拟arm架构来执行arm架构指令,可以直接使用docker build来生成arm架构的镜像,只要基础镜像为arm镜像。
但若Dockerfile中存在RUN命令或要执行arm架构的镜像,需要先执行multiarch/qemu-user-static镜像。
下面的示例在没有qemu-static-user镜像的主机上实验。Dockerfile中只有aarch64架构的基础镜像arm64v8/ubuntu和copy命令。
---Dockerfile(tw-node3227:/root/tmp/arm64/)---
FROM arm64v8/ubuntu
COPY ./* ./tmp
执行docker命令。
# 在x86机器上直接构建镜像
docker build -t arm64v8/hello_world3227 /root/tmp/arm64/
# 此时"Architecture": "amd64",验证这里只是构建环境的架构
docker inspect arm64v8/hello_world3227
# tag并push镜像
docker tag arm64v8/hello_world3227 172.16.1.99/tmp/hello_world3227:test
docker push 172.16.1.99/tmp/hello_world3227:test
# 执行qemu-user-static镜像的环境中运行arm镜像
docker run -it 172.16.1.99/tmp/hello_world3227:test uname -m
aarch64
运到问题
由于qemu-user-static结合binfmt_misc来实现arm架构的指令模拟,所有与Linux kernel相关。
我们遇到的问题:本地UBUNTU 16.04使用kernel 4.15.0-74-generic,可正常模拟arm架构指令;而集群中CentOS 07使用kernel 3.10.0-327.el7.x86_64,在模拟指令时出现问题,抛出“standard_init_linux.go:190: exec user process caused "no such file or directory"”错误。具体问题描述参见Issue 100.
主要问题是kernel 3.10版本上的entry file中的flags为空,不是F。
解决方法:
方法2需要现将需要的qemu-*-static下载到本地,执行qemu-user-static:register来生成entry file之后,运行ARM架构的docker命令是使用-v来将本地的qemu-*-static解析文件mount到容器中。下载地址Download。
docker run --rm -t -v $(pwd)/qemu-aarch64-static:/usr/bin/qemu-aarch64-static arm64v8/ubuntu uname -m
aarch64
TODO: 但是具体为什么3.10版本的kernel出现这个问题还需要研究。