首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

多平台Docker镜像构建教程

Adrian Mouat被誉为Docker Captain,他是Container Solutions 公司的首席科学家。目前,他正开发 Trow,这是一个容器镜像注册中心,用于安全管理 Kubernetes 集群中的镜像流。

当前,Docker镜像已经成为测试和部署新的第三方软件的标准工具。Adrian是开源Trow注册中心的主要开发者,而Docker镜像则是人们安装该工具的主要方式。如果他不提供镜像,其他人最终也会推出他们自己的镜像,这样会导致重复工作,并产生维护问题。

默认情况下,我们创建的Docker镜像运行在linux/amd64平台上。它适用于大多数的开发机器和云提供商,但却忽略其他平台的用户。这个群体很庞大——想想基于树莓派的家庭实验室、生产物联网设备的公司、运行在IBM大型机上的组织以及使用低功耗arm64芯片的云。

一般来说,这些平台的用户通常会构建自己的镜像或寻找其他解决方案。

那么,你该如何为这些平台构建镜像?最明显的方法是在目标平台上构建镜像。这适用于很多情况。但是如果你的目标是s390x,我希望你有可以使用的IBM大型机。更常见的平台,比如树莓派、物联网设备通常电量有限,速度慢或无法构建镜像。

我们该怎么做?有两个选项:1.目标平台仿真,2.交叉编译。有趣的是,我发现有种方法可以将这两个选项结合的效果最好。

仿真

让我们从第一个选项——仿真开始。有一个很不错的项目叫QEMU,它可以模拟很多平台。随着最近buildx的预览,将QEMU用于Docker变得更加容易。

QEMU集成依赖于一个Linux内核特性,该特性有个稍显神秘的名字binfmt_misc handler。当Linux遇到其无法识别的可执行文件格式(例如,一个用于不同体系结构的文件格式)时,它将使用该处理程序检查是否配置了什么“用户空间应用程序”来处理该格式(例如,模拟器或VM)。如果有,它将把可执行文件传递给该应用程序。

为实现这一点,我们需要在内核中注册我们关注的平台。如果你正在使用Docker Desktop,那么对于大多数常见平台,你就无需做这项工作。如果你正使用Linux,你可以通过运行最新的docker/binfmt镜像,以与Docker Desktop相同的方式注册处理程序,例如:

docker run --privileged --rm docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64

完成此操作后,你可能需要重启Docker。如果你想要对自己想注册的平台有更多控制或想使用更高深莫测的平台(例如PowerPC),请查看qus项目

Buildx有两种不同的用法,但最简单的方法可能是在Docker CLI上启用实验性特性(如果你还没有这样做的话),编辑~/.docker/config.json文件,使其包含以下内容:

{
    ...
     "experimental": “enabled”
}

你现在应该能运行docker buildx ls,并得到类似以下的输出:

$ docker buildx ls
NAME/NODE     DRIVER/ENDPOINT             STATUS   PLATFORMS
default       docker                               
  default     default                     running  linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6

让我们为另一个平台构建一个镜像,从Dockerfile开始:

FROM debian:buster
CMD uname -m

如果我们正常构建,运行以下命令:

$ docker buildx build -t local-build .
…
$ docker run --rm local-build
x86_64

但是,如果我们显式指定构建针对的平台,则执行以下命令:

$ docker buildx build --platform linux/arm/v7 -t arm-build .
…
$ docker run --rm arm-build
armv7l

成功!我们已经成功地在x86_64笔记本上构建和运行了armv7镜像,并且只做了很少工作。这种技术很有效,但对于更复杂的构建,你可能会发现它运行太慢,或者遇到QEMU中的Bug。在这些情况下,有必要研究一下是否可以交叉编译你的镜像。

交叉编译

一些编译器能为foreign platforms生成二进制代码,最著名的包括Go和Rust。通过Trow注册中心项目,我们发现,交叉编译是为其他平台创建镜像最快、最可靠的方法。例如,这里是Trow armv7镜像的Dockerfile。最重要的一行是:

RUN cargo build --target armv7-unknown-linux-gnueabihf -Z unstable-options --out-dir ./out 

它明确告诉Rust,我们希望二进制文件在哪个平台上运行。然后,我们可以使用多级构建将这个二进制文件复制到目标体系结构的基本镜像中(如果是静态编译,也能使用scratch),这样就完成了。然而,对于Trow注册中心,我想在最终的镜像中设置更多东西,所以最后阶段实际上开始于:

FROM --platform=linux/arm/v7 debian:stable-slim 

因此,我实际上混合使用了仿真和交叉编译——交叉编译用来创建二进制文件,仿真用来运行和配置最终的镜像。

清单列表

在上面关于仿真的建议中,你可能已经注意到:我们使用--platform参数来设置构建平台,但是我们在FROM行中将镜像指定为debian:buster。这看起来似乎没有意义——平台当然依赖于基本镜像以及它是如何构建的,而不是用户之后的决定。

这样做是因为Docker使用了一种叫做清单列表的东西。对于给定镜像,这些列表包含指向不同体系结构镜像的指针。因为官方的debian镜像有一个定义好的清单列表,当我在笔记本上拉取这个镜像时,我会自动获得amd64镜像,当我在树莓派上拉取它时,我将得到armv7镜像。

为了让用户满意,我们可以为自己的镜像创建清单。如果我们回到前面的例子,首先我们需要重新构建并将镜像推送到一个镜像库:

$ docker buildx build --platform linux/arm/v7 -t amouat/arch-test:armv7 .
…
$ docker push amouat/arch-test:armv7
…
$ docker buildx build -t amouat/arch-test:amd64 .
…
$ docker push amouat/arch-test:amd64

接下来,我们创建一个清单列表指向这两个单独的镜像,并推送它们:

$ docker manifest create amouat/arch-test:blog amouat/arch-test:amd64 amouat/arch-test:armv7
Created manifest list docker.io/amouat/arch-test:blog
$ docker manifest push amouat/arch-test:blog
sha256:039dd768fc0758fbe82e3296d40b45f71fd69768f21bb9e0da02d0fb28c67648

现在,Docker将拉取并运行适合当前平台的镜像:

$ docker run amouat/arch-test:blog
Unable to find image 'amouat/arch-test:blog' locally
blog: Pulling from amouat/arch-test
Digest: sha256:039dd768fc0758fbe82e3296d40b45f71fd69768f21bb9e0da02d0fb28c67648
Status: Downloaded newer image for amouat/arch-test:blog
x86_64

有树莓派的读者可以试着运行这个镜像,并确认它确实能在那个平台上工作! 回顾一下:并不是Docker镜像的所有用户都运行amd64。使用buildx和QEMU,只需额外少量工作就可以为这些用户提供支持。

英文原文:

Multi-Platform Docker Builds

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/V53GydmjmO5lUmo4iLWC
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券