前言 愿景与现实
早在1995年,就有“write once and run anywhere”(WORA,编写一次即可在任何地方运行)用于描述 Java 应用程序。时过20年,Docker 高声喊出了自己的口号——“Build Once, Run Anywhere”(一次构建,随处可用)。
愿望是美好的,然而,现实总比理想骨感。 Linux、Windows 这些不同的操作系统拥有不同的系统 API; x86、Arm、IBM PowerPC 这些不同的硬件平台的指令集不同,某些同平台的硬件甚至拥有不同的专用指令集用于加速应用。一次构建,随处可用面临着巨大的挑战,要构建能够在不同操作系统、不同硬件平台的运行的应用程序,仍然需要工程师们针对具体的操作系统和硬件平台进行海量的移植工作。
Why and How既然多平台的支持这么麻烦、充满挑战,我们是不是可以放弃支持?然而随着国产化大潮和 IoT 物联网的来临,我们编写的应用程序不仅仅会在X86服务器上运行,新时代的工程师们不得不面对更多的硬件平台,放弃多平台的支持无疑是放弃更宽广的未来。多平台的支持,势在必行。
我们正处在一个波澜壮阔的大时代中,新技术、新工具在一次次的迭代升级,不断从Proposal (提议)到 Prototype(原型),再逐渐的实用化。
虚拟化技术使得我们可以做到模拟其它硬件平台;Docker 等容器技术打破混乱,让开发、编译、运行环境一致化;Golang、Rust 这样原生支持多系统多平台的编程语言屏蔽大量底层差异,降低跨平台应用的开发难度。这一系列前人智慧火花汇聚到一起,发生了奇妙的反应——WORA 真正变的触手可及,梦想的阳光已经照进现实。
本篇章会大量分析技术原理及实现细节,对于希望快速 GET 可执行方案的同学,可以直接跳转到【可执行方案回顾】查看。
如何支持多平台要了解容器镜像是如何支持多平台的,那我们需要仔细聊聊 Manifest。使用过容器技术的同学都知道,我们运行容器所使用的镜像是由多层构成的,而这些层的清单和其它容器信息共同存放在 Manifest 当中。
Manifest我们通常使用的容器镜像是x86平台的,执行 docker manifest inspect harbor-community.tencentcloudcr.com/multi-arch/alpine 命令可以查看镜像 harbor-community.tencentcloudcr.com/multi-arch/alpine 的 Manifest 内容是一个 JSON 对象(如代码段-01所示)。各个字段的解释如下:
mediaType 字段声明这是一个V2 Manifes。
schemaVersion 版本。
config 镜像配置信息。
layers 镜像层信息。
// 代码段-01 { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1507, "digest": "sha256:f70734b6a266dcb5f44c383274821207885b549b75c8e119404917a61335981a" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 2813316, "digest": "sha256:cbdbe7a5bc2a134ca8ec91be58565ec07d037386d1f1d8385412d224deafca08" } ] }显而易见的是,Manifest 当中并没有任何字段描述镜像的平台信息。那应该怎么样支持多平台呢?
我们可以设想一个简单粗暴的,无视镜像的平台,强行把交叉编译出来的其它平台的二进制程序添加到镜像内,使用 Repository 名称或者 Tag 名称来区分不同平台的镜像,例如 coredns/coredns:coredns-arm64。在使用的时候,人工或者通过脚本判断应该拉取那个镜像。
Schema 2Ohhhhh,这当然可以跑起来,但是难免太挫了吧?事实上,早在2015年底 Docker 社区的 manifest v2.2 规格文档(也叫 Schema 2,参见 https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-2.md )中就提及了多平台镜像,该功能通过 manifest list(也叫做 fat manifest) 引用多个不同平台镜像的 Manifest 实现。
首先让我们看看 manifest 是什么样的,执行 docker manifest inspect alpine 命令可以查看Docker Hub 上的多平台镜像 alpine 的 Manifest。
// 代码段-02 { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:a15790640a6690aa1730c38cf0a440e2aa44aaca9b0e8931a9f2b0d7cc90fd65", "platform": { "architecture": "amd64", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:71465c7d45a086a2181ce33bb47f7eaef5c233eace65704da0c5e5454a79cee5", "platform": { "architecture": "arm", "os": "linux", "variant": "v6" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:c929c5ca1d3f793bfdd2c6d6d9210e2530f1184c0f488f514f1bb8080bb1e82b", "platform": { "architecture": "arm", "os": "linux", "variant": "v7" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:3b3f647d2d99cac772ed64c4791e5d9b750dd5fe0b25db653ec4976f7b72837c", "platform": { "architecture": "arm64", "os": "linux", "variant": "v8" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:90baa0922fe90624b05cb5766fa5da4e337921656c2f8e2b13bd3c052a0baac1", "platform": { "architecture": "386", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:5d950b30f229f0c53dd7dd7ed6e0e33e89d927b16b8149cc68f59bbe99219cc1", "platform": { "architecture": "ppc64le", "os": "linux" } }, { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 528, "digest": "sha256:a5426f084c755f4d6c1d1562a2d456aa574a24a61706f6806415627360c06ac0", "platform": { "architecture": "s390x", "os": "linux" } } ] }