云原生环境中的镜像兼容性
在电信、高性能或 AI 计算等必须高度可靠且满足严格性能标准的行业中,容器化应用通常需要特定的操作系统配置或硬件支持。 通常的做法是要求使用特定版本的内核、其配置、设备驱动程序或系统组件。 尽管存在开放容器倡议 (OCI) 这样一个定义容器镜像标准和规范的治理社区, 但在表达这种兼容性需求方面仍存在空白。为了解决这一问题,业界提出了多个提案,并最终在 Kubernetes 的节点特性发现 (NFD) 项目中实现了相关功能。
NFD 是一个开源的 Kubernetes 项目,能够自动检测并报告集群节点的硬件和系统特性。 这些信息帮助用户将工作负载调度到满足特定系统需求的节点上,尤其适用于具有严格硬件或操作系统依赖的应用。
镜像兼容性规范的需求
容器与主机操作系统之间的依赖关系
容器镜像是基于基础镜像构建的,基础镜像提供了最小的运行时环境,通常是一个精简的 Linux 用户态环境, 有时甚至是完全空白或无发行版的。 当应用需要来自主机操作系统的某些特性时,就会出现兼容性问题。这些依赖可能表现为以下几种形式:
驱动程序: 主机上的驱动程序版本必须与容器内的库所支持的版本范围相匹配,以避免兼容性问题,例如 GPU 和网络驱动。
库或软件: 容器必须包含某个库或软件的特定版本或版本范围,才能在目标环境中以最优方式运行。 高性能计算方面的示例包括 MPI、EFA 或 Infiniband。
内核模块或特性: 必须存在特定的内核特性或模块,例如对写入保护巨页错误的支持,或存在对 VFIO 的支持。
以及其他更多形式...
虽然在 Kubernetes 中容器是这些需求最常见的抽象单位,但兼容性的定义可以进一步扩展,包括 Singularity 等其他容器技术以及来自 spack 二进制缓存的二进制文件等 OCI 工件。
多云与混合云的挑战
容器化应用被部署在各种 Kubernetes 发行版和云平台上,而不同的主机操作系统带来了兼容性挑战。 这些操作系统通常需要在部署工作负载之前预配置,或者它们是不可变的。 例如,不同云平台会使用不同的操作系统,包括:
- RHCOS/RHEL
- Photon OS
- Amazon Linux 2
- Container-Optimized OS
- Azure Linux OS
- 等等...
每种操作系统都具有独特的内核版本、配置和驱动程序,对于需要特定特性的应用来说,兼容性问题并不简单。 因此必须能够快速评估某个容器镜像是否适合在某个特定环境中运行。
镜像兼容性倡议
OCI 镜像兼容性工作组正在推动引入一个镜像兼容性元数据的标准。 此规范允许容器作者声明所需的主机操作系统特性,使兼容性需求可以被发现和编程化处理。 目前已在 Kubernetes 的 Node Feature Discovery 中实现了其中一个被讨论的提案,其目标包括:
- 在 OCI 镜像清单中定义一种结构化的兼容性表达方式。
- 支持在镜像仓库中将兼容性规范与容器镜像一同存储。
- 在容器调度之前实现兼容性自动验证。
这个理念目前已在 Kubernetes 的 Node Feature Discovery 项目中落地。
在 Node Feature Discovery 中的实现
这种解决方案通过 NFD 的特性机制和 NodeFeatureGroup API 将兼容性元数据集成到 Kubernetes 中。 此接口使用户可以根据硬件和软件暴露的特性将容器与节点进行匹配,从而实现智能调度与工作负载优化。
兼容性规范
兼容性规范是一个结构化的兼容性对象列表,包含 Node Feature Groups。 这些对象定义了镜像要求,并支持与主机节点进行验证。特性需求通过 NFD 项目提供的特性列表进行描述。此模式的结构如下:
version(字符串)— 指定 API 版本。
compatibilities(对象数组)— 兼容性集合列表。
- rules(对象)— 指定 NodeFeatureGroup 来定义镜像要求。
- weight(整数,可选)— 节点亲和性权重。
- tag(字符串,可选)— 分类标记。
- description(字符串,可选)— 简短描述。
示例如下:
version: v1alpha1
compatibilities:
- description: "My image requirements"
rules:
- name: "kernel and cpu"
matchFeatures:
- feature: kernel.loadedmodule
matchExpressions:
vfio-pci: {op: Exists}
- feature: cpu.model
matchExpressions:
vendor_id: {op: In, value: ["Intel", "AMD"]}
- name: "one of available nics"
matchAny:
- matchFeatures:
- feature: pci.device
matchExpressions:
vendor: {op: In, value: ["0eee"]}
class: {op: In, value: ["0200"]}
- matchFeatures:
- feature: pci.device
matchExpressions:
vendor: {op: In, value: ["0fff"]}
class: {op: In, value: ["0200"]}
节点验证的客户端实现
为了简化兼容性验证, 我们实现了一个客户端工具, 可以根据镜像的兼容性工件进行节点验证。在这个流程中,镜像作者会生成一个兼容性工件, 并通过引用者(Referrs) API 将其指向镜像所在的仓库。当需要评估某个镜像是否适用于某个主机节点时, 此工具可以发现工件并在部署前验证镜像对节点的兼容性。 客户端可以验证 Kubernetes 集群内外的节点,扩大了其应用范围。 未来,镜像兼容性还可能在基于镜像要求创建特定工作负载配置文件中发挥关键作用,有助于提升调度效率。 此外,还可能实现一定程度上的节点自动配置,进一步优化资源分配并确保特种工作负载的顺利部署。
使用示例
定义镜像兼容性元数据
一个容器镜像可以包含元数据, 基于节点所发现的特性(如内核模块或 CPU 型号)描述其需求。 上文所述的兼容性规范示例即体现了这种用法。
将工件挂接到镜像上
镜像兼容性规范以 OCI 工件的形式存储。 你可以使用 oras 工具将元数据挂接到你的容器镜像上。 镜像仓库只需支持 OCI 工件,不必支持任意类型。 请注意,容器镜像和工件必须存储在同一个镜像仓库中。 使用以下命令将工件挂接到镜像上:
oras attach \ --artifact-type application/vnd.nfd.image-compatibility.v1alpha1 <image-url> \ <path-to-spec>.yaml:application/vnd.nfd.image-compatibility.spec.v1alpha1+yaml
验证镜像兼容性
在挂接兼容性规范之后,你可以验证某个节点是否满足镜像的运行要求。这种验证可以通过 nfd 客户端来完成:
nfd compat validate-node --image <镜像地址>
读取客户端的输出
你可以阅读工具生成的报告,也可以使用你自己的工具解析生成的 JSON 报告并做出决策。
总结
通过 Node Feature Discovery 将镜像兼容性引入 Kubernetes,突显了在云原生环境中解决兼容性问题的重要性。 这只是一个起点,未来仍需进一步将兼容性深度集成到 Kubernetes 内外的工作负载调度中。 然而,借助这一功能,关键任务型工作负载现在可以更高效地定义和验证其对主机操作系统的要求。 展望未来,兼容性元数据在 Kubernetes 生态系统中的广泛采用将显著提升专用容器化应用的可靠性与性能, 确保其能够满足电信、高性能计算等行业对硬件或主机系统配置的严格要求。
加入我们
如果你有兴趣参与镜像兼容性 API 和工具的设计与开发,欢迎加入 Kubernetes Node Feature Discovery 项目。我们始终欢迎新的贡献者加入。