arm架构和x86架构_x86机器上构建arm架构的image

本文对在x86机器上构建arm架构的image进行研究,参考文章qemu-user-static, Docker。

背景:Docker镜像技术普及之后,出现了云端和边缘端。云端主要使用Intel机器构成,底层架构多为x86_64(amd64),而边缘端都是由arm设备组成,其底层架构很多,如文章中的aarch64。云端设备资源多,功耗大,而边缘端设备资源少但功耗也小。若在边缘端生成Docker镜像文件,需要在时间和资源上做权衡。因此,云端生成边缘端镜像便成了另一种选择。

构建arm镜像的方法:

  1. 在arm架构的机器上直接docker build;
  2. 使用QEMU在x86_64主机上模拟ARM环境执行docker build。QEMU是开源的machine emulator and virtualizer。

本文介绍如何在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种文件:

  1. register script: 脚本文件,用来register binfmt_misc entries;
  2. /usr/bin/qemu-$arch-static二进制文件,存放在container中,作为interpreter文件;
  3. /proc/sys/fs/binfmt_misc/qemu-$arch文件,binfmt_misc entry files,同时存放在host以及container中,register脚本修改host上的文件。

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为识别的类型,Mfor magic andEfor extension;offset为文件中magic/mask的offset,字节为单位,默认为0;magic为binfmt_misc匹配的byte sequence;mask是optional的,与magic执行与操作,默认为oxff;interpreter为执行binary文件的程序,将binary文件作为第一个参数输入;flags用来控制interpreter的调用,optional,F表示在模拟器安装时就检查interpreter文件是否存在;

限制:

  1. register string不能超过1920字节;
  2. magic必须在文件的128字节之内,即offset+size(magic)<128;
  3. interpreter string需要小于127字节;

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。

解决方法:

  1. 安装Centos 8来升级kernel version;
  2. 手动mount qemu-aarch64-static到容器;

方法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出现这个问题还需要研究。

你可能感兴趣的:(arm架构和x86架构,docker,image,aarch64,x86_64)