随着 IoT 的发展 ARM 平台变得越来越重要,HypriotOS 和 resinOS 可以轻松的在 ARM 设备上运行 Docker,部署各种好玩的应用,而不用在意各种系统的差异,在未来,容器技术将从服务器走近用户。
参考资料:
Setup a simple CI pipeline to build Docker images for ARM
Create and use multi-architecture docker images
Run
Docker Hub 上可以找到各种非 x86_64 平台的镜像,比如 arm32v7/python。
有树莓派并且安装好 Docker 的话,可以简单运行起来:
1 | $ uname -a |
而在 x86_64 的 Linux 环境下则会得到一段错误信息:
1 | $ uname -a |
Executable and Linkable Format
虽然 Python 是脚本语言可以跨平台运行,不过 Python 解释器是一个 ELF File,可以在 Raspbian
中使用 file
和 ldd
命令查看 arm32v7/python:3.6.5-slim-stretch
中 Python 解释器的文件信息:
1 | $ docker run --rm -ti arm32v7/python:3.6.5-slim-stretch file /usr/local/bin/python3.6 |
对比 x86_64 的 Linux 环境下的结果:
1 | $ file /usr/bin/python3.6 |
显然,在 x86_64 平台上缺少运行 arm32v7 的 Python 解释器所需要的“环境依赖”,值得庆幸的是在 Linux 上我们可以用 QEMU 来做到跨平台运行,QEMU 的 User-mode emulation
对于容器技术来说是最适合的模式。
qemu-user-static 项目已经准备好了需要静态编译 QEMU,可以在 Release 页面下载 qemu-arm-static
并复制到系统 PATH
路径中:
1 | $ curl -L -o qemu-arm-static-v2.11.1.tar.gz https://github.com/multiarch/qemu-user-static/releases/download/v2.11.1/qemu-arm-static.tar.gz |
然后找一个 armhf 架构下的 static-build ELF 文件运行一下,这里用著名的 vlmcs-armv7el-uclibc-static
做一下测试:
1 | $ file vlmcs-armv7el-uclibc-static |
binfmt_misc
参考资料:
binfmt_misc
在 x86_64 Linux 上试着将 qemu-arm-static
挂载到 arm32v7/python
中运行:
1 | $ docker run --rm -v /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static arm32v7/python:3.6.5-slim-stretch python -V |
不能运行!?再试试这个:
1 | $docker run --rm -v /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static arm32v7/python:3.6.5-slim-stretch qemu-arm-static /usr/local/bin/python3.6 -V |
实际上前面的 无法执行二进制文件: 可执行文件格式错误
与 exec format error
的错误信息是一样的。Docker 并非虚拟机,容器进程仍然是从系统主进程中 fork 出来的,内核仍然无法“理解”ARM ELF 文件。
Building ARM containers on any x86 machine, even DockerHub
On Linux, child processes are started by forking and then doing the
execve()
system call from the child process. Since QEMU merely translates system calls from the guest process to the host kernel, when the emulated/bin/sh
callsexecve("/bin/echo", ..)
, QEMU will happily pass this on to the kernel, but the kernel has no idea what to do with this file since/bin/echo
is an ARM binary!
为了让内核可以理解 ARM ELF 文件,就需要 binfmt_misc
了,确定内核开启了 binfmt_misc
,就可以手动添加:
1 | mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc |
不过,qemu-user-static 提供了一个基于 Docker 的一键解决方案:
1 | $ docker run --rm --privileged multiarch/qemu-user-static:register |
然后在 x86_64 Linux 上再次运行 arm32v7/python
容器:
1 | $ docker run --rm -v /usr/bin/qemu-arm-static:/usr/bin/qemu-arm-static arm32v7/python:3.6.5-slim-stretch python -V |
Build
在构建镜像的过程中无疑是需要有 qemu-*-static
才能执行 RUN
阶段中的命令,因此无法在 RUN
中获取。
下面是为 zerotier-one 构建 arm64v8 镜像的 Dockerfile,完整的项目地址 TomCzHen/zerotier-one。
1 | ARG BUILD_FROM=arm64v8/debian:stretch |
如果有使用 CI 平台的话,可以在构建脚本中获取,以 Travis CI为例:
1 | # get qemu-aarch64-static binary |
Ship
参考资料:
From Arm to Z: Building, Shipping, and Running a Multi-platform Docker Swarm
docker manifest
目前在 Docker Hub 上对于多平台的镜像的处理方式有下面几种:
Docker Hub 社区
基于 tag 区分
基于 image 名区分
无论采取那种方式区别,用户在获取镜像时都需要根据运行平台获取指定的镜像,其实 Docker 已经支持使用 manifest
来为用户提供透明化的服务,自动匹配对应的镜像。
在 x86_64 Linux 上尝试获取 arm32v7/python:3.6.5
镜像时会有如下提示:
1 | $ docker pull arm32v7/python:3.6.5 |
注:截至本文写作时,需要手动修改 ~/.docker/config.json
文件,添加 {"experimental":"enabled"}
为 docker-cli 开启 docker manifest
命令功能。
1 | $ docker manifest inspect python:3.6.5 |
python:3.6.5
镜像有完整 manifests
描述了镜像支持的平台信息,因此在不同平台直接执行 docker pull python:3.6.5
就会自动根据平台架构获取不同的镜像。
原文:
https://github.com/TomCzHen/blog/edit/master/content/post/cross-platform-build-docker-image.md