容器 15:同一镜像名称怎样指向多种 CPU 制品
同一 tag 不能让 x86 指令在 ARM CPU 上原生执行。10 的 index能够把 platform 描述符指向不同的 manifest:选择具体平台时,客户端取得对应的 config/layer 并执行该平台的程序。平台选择成功只是内容匹配;不保证内核 ABI、库或应用本身都可运行。
把构建和运行拆开
在本机生成两套平台元数据并不证明拥有两套机器码。原生构建、交叉编译与仿真构建分别对构建时使用的 CPU/用户空间提出不同条件;通过 QEMU user-mode 仿真运行的性能不能当作原生 ARM 性能。对解释型应用也不能因为 probe_app.py 是同一份文本就断言镜像完全相同:最终基础镜像中的 Python 二进制、库和平台标记仍可能不同。index manifest、每平台 manifest digest 和实际内核及 CPU 架构应一起保存。
一个名字会解析到哪个对象
OCI image-spec v1.1.1 的 image index 是高于单平台 manifest 的对象,其 manifests 数组包含描述符;每条 descriptor 可以带 platform 的 os、architecture,以及可选的 variant、os.version、os.features。镜像提供者可以直接提供单平台 manifest,而非必须有 index,因此“同一个名字查不到 index”不能直接归为镜像损坏。如果客户端取得 index,须按所需操作系统和 CPU 等条件选择相应 manifest,再依据该 manifest 取得 config/layers;从名为 example:latest 的 tag 到索引 digest,再到选中的 manifest digest,是两个独立关联步骤。只记录 tag 和“部署成功”无法复现到底选了哪一份二进制。
当多个条目都可能匹配,规范对第一个匹配项有推荐性规则;具体客户端如何处理其他约束还需查其固定版本实现,不能根据数组只含两项的示意图写成“所有运行时永远选择第一项”。platform 信息本身也可能是发布者自填的元数据,不会替客户端证明层里的 ELF 真正为这个架构编译。先核对 index 选出的 descriptor、manifest 的 config 声明,再在安全环境读取可执行文件格式及实际请求结果;这三个层次只能逐层相互支持,不能凭第一层构造一个“arm64 已原生运行”的结论。
Python 源码相同不等于镜像字节相同
本系列的 probe-app 是同一份 Python 文本,在 x86_64 与 ARM64 镜像里可以复制相同应用字节;但基础镜像中的 /usr/local/bin/python3、动态链接器、系统库及所选包会因平台而变,manifest 中的 base layers、config 的 architecture 字段和最终摘要可能不同。若两边运行的 Python 解释器 ABI 不合适,应用脚本未变仍可能启动失败;若探针调用平台相关的库或本地扩展,单靠脚本文本相同还不足以预测行为相同。即使解释器成功启动,容器进程与宿主共享对应节点的内核,不能从 image index 推断 Windows image 能在 Linux 内核上直接运行,或者反过来。
因此多平台验收要同时保留 index digest、每个平台所选 manifest digest、base image 的平台和摘要、运行时返回的容器 ID/PID,以及 /identity 响应。少了实际第二个平台节点或受控仿真环境时,只能检查 image layout 的声明,不能把一个 x86_64 主机上生成的两份 JSON platform 字段填作“amd64 和 arm64 均已运行”。特别是例子 oci_layout.py 只按当前 platform.machine() 生成单架构配置,其索引条目只有一份;它可教会读者追踪 descriptor,但根本不负责多架构编译。
三种构建方式的前提不同
官方多平台构建文档列出仿真、多原生节点和交叉编译三条常见路径。仿真让某些非本机架构程序通过配置好的 QEMU 等机制运行,能帮助构建脚本执行,但与原生处理器的启动和性能表现不同;并非“打开一个开关 CPU 就变成 ARM”。多原生节点要求实际可控的另一架构节点,保留两侧构建工具、内核和目标基础镜像版本,不能拿远端客户端的 uname -m 当第二节点证据。交叉编译需要语言工具链正确输出目标平台二进制,且 Dockerfile 明确区分构建平台与目标平台;编译命令成功但输出仍是宿主架构可执行文件,最终镜像标为 arm64 仍可能得到 exec format error。
本系列的探针如果只复制 Python 源文件,跨架构的关键很可能落在基础镜像解释器,而不是脚本自身;若改为编译 C 版 probe,则必须连同编译工具链的目标、链接器与生成的 ELF machine 信息一起取证。只有创建了针对目标平台的制品并在该平台执行,才能测服务行为。即便 QEMU 路径成功执行程序,也必须在记录中标明“仿真执行”,不能将吞吐或延迟混入原生性能章节 35 的数字。当前仓库没有 ARM 节点、QEMU 证明或双架构构建器,这三条路径均未在本篇实测。
区分没有匹配项与已经开始 exec
负例一是在只提供 linux/amd64 的 index 上要求 linux/arm64,客户端在选择 manifest 阶段应发现没有合适的平台条目;它甚至不必请求 amd64 的所有层。负例二是 index 谎称 linux/arm64,实际层里只有 amd64 ELF:客户端可能认为平台匹配、下载和解包都成功,直到 exec 时报格式或依赖错误。两种故障的错误发生位置不同,不能把“no matching manifest”与“exec format error”当成同一种镜像拉取失败。补跑实验应使用自建测试引用并记录选择、下载、解包、exec 的每一步,而不是去篡改共享 registry 的平台标签。
此外,还要确认执行环境:在 x86_64 Linux 节点指定 arm64 image,若运行时自动配置了仿真,即使镜像仍是 arm64,进程可能顺利工作,不能据此断言处理器原生支持 arm64。反过来,在真正的 ARM 节点若选择了错误 base 镜像,其启动失败也不等于 ARM 系统不支持 Python。保存 uname -m 的采集位置、实际 ELF machine、builder 输出的平台声明和是否通过仿真,是识别这类混淆的最小证据集合。
在现有环境能验证与不能验证的部分
可静态阅读 OCI index 的 platform 字段与本机生成的一份 manifest,源码归档也可做内容哈希核对;这仅证明文件结构可读、不是第二套机器码或跨平台服务测试。要完成本篇的计划验收,需专用环境中的真实第二平台、两套可追踪制品、分别实际启动并请求探针;失败对照也要在自建镜像上完成,并保留清理记录。若用户只有当前云端的 x86_64 Linux、没有 runtime 或第二平台,结论仍是 NOT_RUN,不在材料中用模型演算或搜索摘要假装有两套结果。
用同一请求做平台对照
有了受控 amd64 与 arm64 两台节点后,先固定 probe_app.py 内容哈希,并各自使用记录过 digest 的 Python 基础镜像构建应用。将两份单平台 manifest 作为同一 index 的子描述符后,保留 index 原始 JSON、字节摘要与每个子 manifest digest;此时即便 tag 相同,请求 index 的客户端在两台机器上也可能选出不同的子摘要。在各节点记录 uname -m、运行时上报的实际镜像摘要、容器 ID、宿主 PID,并对两份进程各自发送一次 /identity 请求。只有在响应确实与对应进程和 digest 关联后,才能说“同一逻辑程序在两个平台按各自制品运行”;仅有构建器输出两条绿色日志无法证明节点真的拉取并执行了正确条目。
这个对照还应核对输出的 app 代码确实相同。若两份 image 的 /app/probe_app.py 文件 hash 不同,响应差异可能来自源文件或构建 context,而不是 CPU 平台;若脚本 hash 相同但 Python 版本不同,也不能将差异机械归因于架构。两台节点的就绪延迟、端口映射和执行环境独立记录,避免一边请求的是宿主 loopback、另一边请求容器内 loopback。测试结束只清理本次自建的两个容器、两个标签与临时状态文件,并验证两侧无剩余进程;一台清理通过不能代替另一台的清理记录。
对索引缺项的边界检查
故意只发布一份 amd64 manifest 的私有测试 tag,再向客户端要求 arm64,记录具体返回是选择阶段报无匹配、内容查询报缺失,还是客户端尝试使用已有本地镜像。仅在索引里改 platform 标签而不替换层,不能把结果当成成功构建 arm64;如果客户端下载了伪装制品,继续记录实际 ELF 和执行失败位置,恰好证明元数据与机器码需要交叉检查。模拟失败也不能“按经验”补一个标准错误字符串:不同 Docker/BuildKit/containerd 版本可能格式不同,只有原始命令和退出码是真正的本次证据。现阶段缺专用第二平台,因此两类负例均保留在补跑说明而非实验结果。
此方法也适用于非 CPU 条件:同一 index 还可能对 OS、OS 版本或其他平台要求给出不同 descriptor,但 Windows 容器有自己的内核和基础镜像适配问题,不是给 Linux index.json 加一条 os=windows 就能执行。遇到“平台不匹配”,先核对索引描述与目标节点环境,再核对镜像层里的实际程序及依赖,最后检查运行时 exec 的日志;不要为修复一个缺少 ARM 二进制的制品而随意改写 OCI 平台元数据。
最后限定“跨平台镜像完成”的含义:拥有两条 manifest 指针是索引结构检查,按两条指针分别下载并核算 digest 是分发检查,而应用在两个不同平台各自成功处理同一探针请求才是运行检查。仿真运行应在运行证据中单列,不能替代本机第二架构节点的性能测试;如果缺硬件或构建权限,就注明具体 NOT_RUN 和验证所需的运行命令,不从单平台静态检查跨过这些层次。
记录里的 digest 要附带它指向的对象类型;index digest、各平台 manifest digest 和层 digest 不可只用一个“镜像 ID”覆盖。查到 digest 对应正确 blob 后仍要验证进程实际使用的镜像引用,才能把选平台决策与服务结果连起来。这个证据对后续平台差异、交叉构建和故障恢复同样适用。
正例:在可控 VM 与预先准备的另一架构节点中,固定基础镜像 digest 对照,分别构建两个平台、验证 index 指向的 descriptor 与真实运行的 /identity 请求;失败案例让节点请求一个 index 没提供的 platform,区分“无可选 manifest”和已经选择镜像但发生 exec format error。补跑入口要求记录原生、交叉、仿真具体策略,源码包延用 13 的同一探针。当前只有 x86_64 Linux,且无构建器、第二平台;索引与执行对照均 NOT_RUN,不能拿 10 的静态 index.json 冒充跨平台构建完成。
| 现象 | 查什么 | 边界 |
|---|---|---|
| 错平台执行失败 | index 平台、实际 ELF、节点架构 | tag 相同不保证兼容 |
| 仿真能运行 | 仿真器与原生结果分别记录 | 不混算性能 |
| 只有一个平台 | index/manifest 项目数 | 单个 manifest 不等于多架构 |
练习
- 先预测同一 tag 在 amd64 和 arm64 会选哪一个 manifest;在可控两架构环境读 index、目标 manifest 与各自的 ELF 元信息。
- 先预测强制选择不存在的平台会失败在哪一层;记录解析错误与真实
exec format error的差异,不能在只有本机一套环境时宣称两边已实测。
系列总目录:从进程隔离到运行时与编排。





