最新在信创项目中,经常需要构建支持amd64和arm64架构的镜像,而有的场景在同一个 Kubernetes 集群中的节点是混合架构的,也就是说,其中某些节点的 CPU 架构是 x86 的,而另一些节点是 ARM 的。为了让我们的镜像在这样的环境下运行,一种最简单的做法是根据节点类型为其打上相应的标签,然后针对不同的架构构建不同的镜像,比如 demo:v1-amd64 和 demo:v1-arm64,然后还需要写两套 YAML:一套使用 demo:v1-amd64 镜像,并通过 nodeSelector 选择 x86 的节点,另一套使用 demo:v1-arm64 镜像,并通过 nodeSelector 选择 ARM 的节点。很显然,这种做法不仅非常繁琐,而且管理起来也相当麻烦,如果集群中还有其他架构的节点,那么维护成本将成倍增加。

你可能知道,每个 Docker 镜像都是通过一个 manifest 来描述的,manifest 中包含了这个镜像的基本信息,包括它的 mediaType、大小、摘要以及每一层的分层信息等。可以使用 docker manifest inspect 查看某个镜像的 manifest 信息:

可以加上 --verbose 查看更详细的信息,包括该 manifest 引用的镜像标签和架构信息:

我们一般不会直接使用 manifest,而是通过标签来关联它,方便人们使用。从上面的输出结果可以看出,该 manifest 通过 docker.io/aneasystone/hello-actuator:v1 这个镜像标签来关联,支持的平台是 linux/amd64,该镜像有四个分层,另外注意这里的 mediaType 字段,它的值是 application/vnd.docker.distribution.manifest.v2+json,表示这是 Docker 镜像格式(如果是 application/vnd.oci.image.manifest.v1+json 表示 OCI 镜像)。

可以看出这个镜像标签只关联了一个 manifest ,而一个 manifest 只对应一种架构;如果同一个镜像标签能关联多个 manifest ,不同的 manifest 对应不同的架构,那么当我们通过这个镜像标签启动容器时,容器引擎就可以自动根据当前系统的架构找到对应的 manifest 并下载对应的镜像。实际上这就是 多架构镜像( multi-arch images ) 的基本原理,我们把这里的多个 manifest 合称为 manifest list( 在 OCI 规范中被称为 image index ),镜像标签不仅可以关联 manifest,也可以关联 manifest list。

可以使用 docker manifest inspect 查看某个多架构镜像的 manifest list 信息:

这里的 alpine:3.17 就是一个多架构镜像,从输出结果可以看到 mediaType 是 application/vnd.docker.distribution.manifest.list.v2+json,说明这个镜像标签关联的是一个 manifest list,它包含了多个 manifest,支持 amd64、arm/v6、arm/v7、arm64、i386、ppc64le、s390x 多个架构。我们也可以直接在 Docker Hub 上看到这些信息:

alpine-image.png

很显然,在我们这个混合架构的 Kubernetes 集群中,这个镜像是可以直接运行的。我们也可以将我们的应用构建成这样的多架构镜像,那么在这个 Kubernetes 集群中就可以自由地运行我们自己的应用了,这种方法比上面那种为每个架构构建一个镜像的方法要优雅得多。

那么,我们要如何构建这样的多架构镜像呢?一般来说,如果你使用 Docker 作为你的构建工具,通常有两种方法:docker manifest 和 docker buildx

使用 docker manifest 创建多架构镜像

docker build 是最常用的镜像构建命令,首先,我们创建一个 Dockerfile 文件,内容如下:

然后使用 docker build 构建镜像:

这样一个简单的镜像就构建好了,镜像能正常运行。不过这样构建的镜像有一个问题,Docker Engine 是根据当前我们的系统自动拉取基础镜像的,我的系统是 x86 的,所以拉取的 alpine:3.17 镜像架构是 linux/amd64 的:

如果要构建其他架构的镜像,可以有三种办法。第一种是最原始的方法,Docker 官方为每种 不同的架构创建了不同的独立账号,比如下面是一些常用的账号:

所以我们就可以通过 amd64/alpine 和 arm64v8/alpine 来拉取相应架构的镜像,我们对 Dockerfile 文件稍微修改一下:

然后使用 --build-arg 参数来构建不同架构的镜像:

不过从 2017 年 9 月开始,一个镜像可以支持多个架构了,这种方法就渐渐不用了。第二种办法就是直接使用 alpine:3.17 这个基础镜像,通过 FROM 指令的 --platform 参数,让 Docker Engine 自动拉取特定架构的镜像。我们新建两个文件 Dockerfile-amd64 和 Dockerfile-arm64Dockerfile-amd64 文件内容如下:

Dockerfile-arm64 文件内容如下:

然后使用 docker build 再次构建镜像即可:

注意这里的 --pull 参数,强制要求 Docker Engine 拉取基础镜像,要不然第二次构建时会使用第一次的缓存,这样基础镜像就不对了。

第三种方法不用修改 Dockerfile 文件,因为 docker build 也支持 --platform 参数,我们只需要像下面这样构建镜像即可:


在执行 docker build 命令时,可能会遇到下面这样的报错信息:

据 这里 的信息,修改 Docker Daemon 的配置文件,将 buildkit 设置为 false 即可:

构建完不同架构的镜像后,我们就可以使用 docker manifest 命令创建 manifest list,生成自己的多架构镜像了。由于目前创建 manifest list 必须引用远程仓库中的镜像,所以在这之前,我们需要先将刚刚生成的两个镜像推送到镜像仓库中:

然后使用 docker manifest create 创建一个 manifest list,包含我们的两个镜像:

最后将该 manifest list 也推送到镜像仓库中就大功告成了:

如果推送时报错说没有manifest,则直接使用下面命令创建即可:

 

可以使用 docker manifest inspect 查看这个镜像的 manifest list 信息:

也可以在 Docker Hub 上看到这个镜像的架构信息:

demo-image.png

使用 docker buildx 创建多架构镜像

从上一节可以看出,使用 docker manifest 来构建多架构镜像的步骤大致分为以下四步:

  1. 使用 docker build 依次构建每个架构的镜像;
  2. 使用 docker push 将镜像推送到镜像仓库;
  3. 使用 docker manifest create 创建一个 manifest list,包含上面的每个镜像;
  4. 使用 docker manifest push 将 manifest list 推送到镜像仓库;

每次构建多架构镜像都要经历这么多步骤还是非常麻烦的,这一节将介绍一种更方便的方式,使用 docker buildx 来创建多架构镜像。

buildx 是一款 Docker CLI 插件,它对 Moby BuildKit 的构建功能进行了大量的扩展,同时在使用体验上还保持和 docker build 一样,用户可以很快上手。如果你的系统是 Windows 或 MacOS,buildx 已经内置在 Docker Desktop 里了,无需额外安装;如果你的系统是 Linux,可以使用 DEB 或 RPM 包的形式安装,也可以手工安装,具体安装步骤参考 官方文档

使用 docker buildx 创建多架构镜像只需简单一行命令即可:

不过第一次执行这行命令时会报下面这样的错:ERROR: multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. “docker buildx create –use”)

这是因为 buildx 默认使用的 构建器( builder ) 驱动是 docker driver,它不支持同时构建多个 platform 的镜像,我们可以使用 docker buildx create 创建其他驱动的构建器( 关于 buildx 的四种驱动以及它们支持的特性可以 参考这里 ):

构建:

报错:ERROR: failed to solve: ycr.yyiuap.com/os/openeuler:20.03-sp2: failed to do request: Head “https://ycr.yyiuap.com/v2/os/openeuler/manifests/20.03-sp2”: dial tcp: lookup ycr.yyiuap.com on 10.3.15.14:53: no such host

原因:用docker buildx构建时DNS解析不到ycr的内网私有域名, 可以删除buildx构建器然后重建, 使构建 时使用主机网络:

 

再次尝试构建, 报错:ERROR: failed to solve: ycr.yyiuap.com/os/openeuler:20.03-sp2: failed to do request: Head “https://ycr.yyiuap.com/v2/os/openeuler/manifests/20.03-sp2”: x509: certificate signed by unknown authority

分析:docker daemon虽然已经配置 –insecure-registry=0.0.0.0/0,但是 buildx构建时需要单独配置一下:

The TOML file used to configure the buildkitd daemon settings has a short list of global settings followed by a series of sections for specific areas of daemon configuration.

The file path is /etc/buildkit/buildkitd.toml for rootful mode, ~/.config/buildkit/buildkitd.toml for rootless mode.

 

Categories: KUBERNETES

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *