云原生环境中的镜像兼容性

在电信、高性能计算或人工智能计算等行业中,系统必须高度可靠地运行并满足严格的性能标准,容器化应用通常需要特定的操作系统配置或硬件支持。要求使用特定版本的内核、其配置、设备驱动程序或系统组件是一种常见的做法。尽管存在开放容器倡议 (Open Container Initiative, OCI) 这个旨在定义容器镜像标准和规范的治理社区,但在表达此类兼容性要求方面一直存在差距。解决这个问题的需求催生了不同的提案,并最终在 Kubernetes 的节点特性发现 (Node Feature Discovery, NFD) 中得以实现。

NFD 是一个开源的 Kubernetes 项目,它能自动检测并报告集群节点的硬件和系统特性。这些信息帮助用户将工作负载调度到满足特定系统要求的节点上,这对于有严格硬件或操作系统依赖的应用尤其有用。

对镜像兼容性规范的需求

容器与宿主操作系统之间的依赖关系

容器镜像基于基础镜像构建,基础镜像提供了一个最小的运行时环境,通常是一个精简的 Linux 用户区,完全为空或无发行版 (distroless)。当应用程序需要宿主操作系统的某些特性时,兼容性问题就会出现。这些依赖关系可以体现在以下几个方面:

  • 驱动程序:宿主驱动程序版本必须与容器内库版本支持的范围相匹配,以避免兼容性问题。例如 GPU 和网络驱动程序。
  • 库或软件:容器必须附带特定版本或版本范围的库或软件,才能在环境中以最佳状态运行。高性能计算领域的例子有 MPI、EFA 或 Infiniband。
  • 内核模块或特性:必须存在特定的内核特性或模块。例如,需要支持写保护的大页错误 (write protected huge page faults),或存在 VFIO。
  • 还有更多……

虽然 Kubernetes 中的容器最可能是满足这些需求的抽象单元,但兼容性的定义可以进一步扩展,以包括其他容器技术(如 Singularity)和其他 OCI 工件(如来自 spack 二进制缓存的二进制文件)。

多云和混合云的挑战

容器化应用部署在各种 Kubernetes 发行版和云提供商上,其中不同的宿主操作系统带来了兼容性挑战。这些系统通常需要在部署工作负载之前进行预配置,或者它们是不可变的。例如,不同的云提供商会包含不同的操作系统,如:

  • RHCOS/RHEL
  • Photon OS
  • Amazon Linux 2
  • Container-Optimized OS
  • Azure Linux OS
  • 还有更多...

每种操作系统都带有独特的内核版本、配置和驱动程序,这使得对于需要特定功能的应用来说,兼容性成为一个不容忽视的问题。必须能够快速评估一个容器是否适合在任何特定环境中运行。

镜像兼容性倡议

开放容器倡议镜像兼容性工作组内,我们致力于引入一个镜像兼容性元数据的标准。一个兼容性规范将允许容器作者声明所需的宿主操作系统特性,使兼容性要求变得可发现和可编程。在 Kubernetes Node Feature Discovery 中实现的规范是讨论中的提案之一。它旨在:

  • 定义一种结构化的方式来表达 OCI 镜像清单中的兼容性。
  • 在镜像仓库中支持容器镜像的同时,也支持兼容性规范。
  • 允许在调度容器前自动验证兼容性。

这个概念此后已在 Kubernetes Node Feature Discovery 项目中实现。

在 Node Feature Discovery 中的实现

该解决方案通过 NFD 的特性和 NodeFeatureGroup API 将兼容性元数据集成到 Kubernetes 中。这个接口使用户能够根据暴露的硬件和软件特性将容器匹配到节点,从而实现智能调度和工作负载优化。

兼容性规范

兼容性规范是一个结构化的兼容性对象列表,其中包含 *节点特性组 (Node Feature Groups)*。这些对象定义了镜像的需求,并有助于对照宿主节点进行验证。特性需求通过使用 NFD 项目中可用特性列表来描述。该模式具有以下结构:

  • version (string) - 指定 API 版本。
  • compatibilities (array of objects) - 兼容性集合列表。
    • rules (object) - 指定 NodeFeatureGroup 来定义镜像需求。
    • weight (int, 可选) - 节点亲和性权重。
    • tag (string, 可选) - 分类标签。
    • description (string, 可选) - 简短描述。

一个示例如下:

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"]}

用于节点验证的客户端实现

为了简化兼容性验证,我们实现了一个客户端工具,它允许根据镜像的兼容性工件进行节点验证。在这个工作流中,镜像作者会生成一个兼容性工件,该工件通过 referrers API 指向它在仓库中描述的镜像。当需要评估一个镜像与某个宿主机的匹配度时,该工具可以发现此工件并在部署前验证镜像与节点的兼容性。该客户端可以在 Kubernetes 集群内外验证节点,从而将工具的实用性扩展到单个 Kubernetes 用例之外。未来,镜像兼容性可以在根据镜像兼容性需求创建特定工作负载配置文件方面发挥关键作用,从而帮助实现更高效的调度。此外,它还有可能在一定程度上实现自动节点配置,进一步优化资源分配并确保专业化工作负载的无缝部署。

使用示例

  1. 定义镜像兼容性元数据

    一个容器镜像可以包含元数据,用于描述其基于从节点发现的特性(如内核模块或 CPU 型号)的需求。本文前面提到的兼容性规范示例就说明了这种用例。

  2. 将工件附加到镜像

    镜像兼容性规范以 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
    
  3. 验证镜像兼容性

    附加兼容性规范后,你可以验证一个节点是否满足镜像的要求。这个验证可以使用 nfd 客户端来完成:

    nfd compat validate-node --image <image-url>
    
  4. 读取客户端的输出

    最后,你可以读取该工具生成的报告,或使用你自己的工具根据生成的 JSON 报告采取行动。

    validate-node command output

结论

通过 Node Feature Discovery 将镜像兼容性功能添加到 Kubernetes 中,凸显了在云原生环境中解决兼容性问题日益增长的重要性。这只是一个开始,因为还需要进一步的工作来将兼容性集成到 Kubernetes 内外工作负载的调度中。然而,通过将此功能集成到 Kubernetes 中,任务关键型工作负载现在可以更有效地定义和验证宿主操作系统的要求。展望未来,在 Kubernetes 生态系统中采用兼容性元数据将显著提高专业化容器应用的可靠性和性能,确保它们满足电信、高性能计算或任何需要特殊硬件或宿主操作系统配置的行业的严格要求。

参与其中

如果你有兴趣参与镜像兼容性 API 和工具的设计与开发,请加入 Kubernetes Node Feature Discovery 项目。我们随时欢迎新的贡献者。