OCI (容器镜像仓库) URL 支持

https://bugs.launchpad.net/ironic/+bug/2085565

问题描述

Ironic 独立用户的一个预期模式是利用容器镜像作为在不同位置之间传输镜像或镜像包的方式。Metal3 社区的其他成员也表达了相同的兴趣。

通常,这采用容器内部文件的模型,一旦容器启动或访问了容器的文件系统层内容,该文件就可用。虽然这是一种我们未来可能支持的模式,但它是一种抽象的交互模型,实际上使得访问所需文件变得更加复杂。

这种复杂性在于,在容器模型中,我们想要的文件(qcow2 或 raw 镜像文件)位于文件系统层文件(gz 压缩的 tar 格式文件)内部,然后需要映射、提取到可用的结构中,再导航到提取文件。这种方法的的主要缺点是,每次容器中的文件更新到新的容器版本时,容器的整体大小都会增加。这对于某些人来说可能不是问题,但对于其他人来说意味着不断增加的开销。

一个更好、更有效的方法是利用底层数据模型来附加单独的“层”文件,这些文件实际上是完整的二进制工件,例如代表镜像内容的 raw、vhd 或 qcow2 镜像。

这提供了一个有趣的路径,可以将层中的文件内容与可以部署的二进制工件匹配起来,这基本上是 Podman 为其他主机和虚拟机类型处理多架构容器支持的方式。

Podman 通过在元数据中使用注释来实现这一点,允许客户端识别和提取所需内容。例如,如果您要在 OSX 上启动容器,podman 工具实际上是在检索磁盘镜像。

可以通过检查 machine-os 容器的可用元数据来找到此数据和结构的示例。

skopeo inspect --raw docker://quay.io/podman/machine-os:5.3

这种方法的总体优势在于,底层“层”文件与用户所需的注释匹配,可以在整体层中的单个文件更新后直接更新。有效地将复杂性压缩到单个交付和参考格式中,并因容器构建过程而产生一些额外的复杂性。请注意,我们将在下面的高级变更建议之后深入探讨容器的整体结构建模。

为了简化整体流程和交互,我们建议支持交互模型,即能够支持具有关联匹配磁盘镜像的容器,通过增强我们的镜像处理逻辑来实现,以便根据注释访问相关工件,从而简化操作员移动镜像(这些镜像也是容器)的整体用户体验。

提议的变更

此变更建议调整 Ironic 服务的核心代码,以启用检索附加到 OCI 容器的工件。

第一个更改是修改 Ironic 的 Image Service 代码,以启用 OCI 协议映射,并附带一个了解如何进行身份验证、检索元数据并最终将所需内容下载到文件(就像 Ironic 中的任何其他镜像服务一样)的关联类。

注意

可能需要支持声明额外的注释,但这不在本次规范的具体范围之内。

第二个更改可能需要 Ironic-Python-Agent 了解如何进行身份验证到镜像仓库以检索最终工件,但最终我们希望 conductor 执行与识别磁盘镜像工件相关的所有操作。

具体来说,一种模式是将工件也使用 Zstandard 压缩。我们无法在启动时使用 Ironic-Python-Agent 支持这一点,因此我们预计此功能主要与 image_download_source 设置设置为 local 一起使用,以使 conductor 执行任何解压缩。我们不期望排除 image_download_source 设置为 http,尽管如此。如果工件使用额外的压缩进行压缩,则应该完全可行,并且现有的流程模型与执行 Swift 镜像下载时所做的事情相同。

例如,当 image_download_source 设置为 local 时,conductor 将负责从仓库检索请求的 image_source,并将经过安全检查的工件提供给 ironic-python-agent。当 image_download_source 设置为 http 时,Ironic-Python-Agent 了解的只是使用客户端代码与容器仓库交互解析的 URL。

总体目标是允许用户将 instance_info/image_source 值设置为“oci://fqdn:port/container:version-label”,这将导致检索并提取所需的“raw”或“qcow2”工件。

URL 的协议部分,特别是“oci://”,将从提供给底层工件检索工具或代码路径的 URL 中剥离,因为它仅用于 Image Service 的高级匹配。

注意

后端工具,例如 podman 和 spokeo,使用容器镜像通用库,并且 man 页面 containers-transports 的日期是 2019 年,但一些特定更新更新了。然而,最近更新的 OpenContainers distribution-spec 版本 1.1.0 的日期是 2024 年,明确指出其协议基于 docker v2 协议,并包含规范中涵盖的一些补充内容。项目明确剥离用户提供的 URL 中的前导 URL 协议格式,并允许该工具做出适当的确定,这意味着“oci://”风格的 URL 以及“docker://”风格的 URL 都是可能的。这样做最有意义。还应认识到,容器传输通用代码和相关文档尚未更新为更新的共识。

注意

在此模型中,不需要显式用户提供的校验和。当直接引用摘要清单时,清单的校验和是 URL 的一部分。此外,清单的内容包括我们将检索的二进制工件的摘要值。

注意

此功能很可能通过纯 Python 实现,并调用 HTTP 客户端库。基本协议已详细说明,并且存在示例。此外,本机 Python 对象的使用将能够进行适当的身份验证处理,以用于多用户环境,而使用 CLI 工具可能会证明过于复杂。

此外,许多仓库,例如在 OpenShift 上托管的一个仓库,明确要求用户才能访问远程容器仓库中的内容进行身份验证。此外,一些公共镜像仓库对未经验证的用户施加相当严格的速率限制。

最佳做法是支持提交“pull secret”,以允许用户以 instance_info/image_pull_secret 值的形式进行镜像检索,用于用户工件身份验证。API 表面中的现有 secret 保护代码应防止此值被 API 看到,但该值可用于建立适当的临时用户环境或简单地进行身份验证到仓库以启用元数据和工件访问。

对于服务工件,最终在安全环境上下文中作为用户工件的后备,服务需要支持使用 docker auths 配置文件格式。这将作为新的 OCI 客户端配置选项引入,允许 conductor 配置保存预共享 secret。这将利用现有的标准格式的容器 auth.json 文件。有关更多详细信息,请参阅 container-auth

为了帮助启用功能管理,还将引入一个新的配置选项,以允许操作员禁用此功能。

整体文件检索流程很可能遵循以下路径

  1. URL 将被处理以删除协议部分。

  2. 凭据将被用于促进远程仓库登录。

  3. 将检索基本镜像元数据,从而可以将元数据注释转换为识别所需的 blob。

  4. 然后将检索特定的所需 blob,例如转换为 HTTP “GET /v2//blobs/” 命令,其中 name 值是容器名称,digest 是通过元数据引用的 blob 的 sha256 校验和。对于 ironic-python-agent (IPA),检索将通过 IPA 进行,以提供直接下载 URL。

  5. 工件可能需要在解压缩之前检查压缩这可以作为整体流程之外的次要魔术字节检查和机会性解压缩来处理。

虽然不明确属于此变更的一部分,但另一个可能的未来变更是将容器作为可启动的容器镜像部署。在这种情况下,我们只是期望将整个容器设置为 image_source 参数,并且最终 deploy_interface 将了解所需的动作,就像今天的 anaconda 部署接口一样。

警告

磁盘镜像的容器工件建模示例并不能解决内核/ramdisk 工件的根本挑战。建议的代码支持检索工件,只要它们使用清单摘要 URL 引用,即 example.io/user/image@sha256:manifest_hash。添加额外的标记可能是启用检索和提取其他工件的选项,但这些选项不在本次规范的范围之内。

结构深度剖析

重要的是要注意,多架构工件链接样式采用不同的容器建模,即存在一个顶级补充工件链接。此深度剖析旨在帮助读者理解我们正在查看的 OCI 容器结构模型,并将其数据结构和建模联系起来,以帮助我们创建自己的客户端代码来支持工件检索。

例如,顶级索引数据具有第二个映射条目,第一个是传统的容器文件布局,第二个索引链接到多架构所需的映射数据。

注意

此深度剖析中的示例来自受 Podman 多架构支持启发的远程仓库的 oci://path 输出。文件内容和结构因此反映了容器的 API 可访问性。目标是帮助说明数据结构的外观以及它们之间的关系。

# cat index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:a4460c00d2244ed88eb44be9f3ffd1a3da4a4594690cc314f5b2de6cc427ed3b",
      "size": 11537
    },
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:63fb09d03efaa20f10cacd285f08befb98ae5b4decb3125d6704ca912b99f7eb",
      "size": 1930
    }
  ]
}

当我们评估第二个文件的内容时,我们会看到类似以下内容。请注意,这正在从实际示例中编辑下来,以专注于上下文交换的清晰度,通过删除带有 disktype 注释 applehvhyperv 的工件。

以下内容最好被视为指向清单的工件列表。

cat ./blobs/sha256/63fb09d03efaa20f10cacd285f08befb98ae5b4decb3125d6704ca912b99f7eb | jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:026602a096974b80497e4afba3c8cff8397dbdf46a70197e698011d338491611",
      "size": 474,
      "annotations": {
        "disktype": "qemu"
      },
      "platform": {
        "architecture": "x86_64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:6dda6fec71d06cc3d19460a4228e28aad2c9fc48ce0f7f1c4052f6c97c78b0dd",
      "size": 475,
      "annotations": {
        "disktype": "qemu"
      },
      "platform": {
        "architecture": "aarch64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:a4460c00d2244ed88eb44be9f3ffd1a3da4a4594690cc314f5b2de6cc427ed3b",
      "size": 11537,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:1ad1af838a5d3cb228288f4c0dc8d95617734d10f1c9f46eae871ecf05aaed94",
      "size": 11535,
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    }
  ]
}

在上述上下文中,这些是中间指针文件,有助于将 platformosdisktype 注释连接在一起。以下是磁盘镜像的一个示例。缺少 disktype 注释的引用只是另一个容器文件系统层引用。

当您使用格式 oci://host/container@sha256:hash 时,您直接引用一个如下所示的清单。同样重要的是,此文件没有指向先前数据结构的指针。

cat blobs/sha256/026602a096974b80497e4afba3c8cff8397dbdf46a70197e698011d338491611 |jq
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.empty.v1+json",
    "digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
    "size": 2,
    "data": "e30="
  },
  "layers": [
    {
      "mediaType": "application/zstd",
      "digest": "sha256:0bd0cc6a9a7a1656011cd813615124e44ee0fcd63ab71000ec541e1c0502c7cc",
      "size": 1059378224,
      "annotations": {
        "org.opencontainers.image.title": "podman-machine.x86_64.qemu.qcow2.zst"
      }
    }
  ]
}

关于层是压缩数据块的说明,以下文件也是如此,如下所示。

$ file ./blobs/sha256/0bd0cc6a9a7a1656011cd813615124e44ee0fcd63ab71000ec541e1c0502c7cc
./blobs/sha256/0bd0cc6a9a7a1656011cd813615124e44ee0fcd63ab71000ec541e1c0502c7cc: Zstandard compressed data (v0.8+), Dictionary ID: None
$ zstdcat ./blobs/sha256/0bd0cc6a9a7a1656011cd813615124e44ee0fcd63ab71000ec541e1c0502c7cc |file -
/dev/stdin: QEMU QCOW Image (v3), 10737418240 bytes (v3), 10737418240 bytes
$

换句话说,实际程序代码应评估所有顶级数据,寻找带有注释和 disktype 注释为 qemu 的条目,然后根据 platform 字典的 architecture 字段匹配要部署的架构来选择文件。

注意

值得注意的是,applehv 磁盘类型看起来是原始磁盘镜像,如果可用,可能适合流式传输,否则可能需要传输兼容 qemu 的 qcow2 镜像。

备选方案

支持这种模式的替代方案是,所有用户都必须提供所有工具和流程,才能将镜像提供在 HTTP(S) URL 上,但是考虑到我们今天支持 Glance 作为镜像源,这是不合逻辑的。

数据模型影响

预计此更改不会对数据模型产生影响。

状态机影响

REST API 影响

预计不会产生影响,尽管项目应该询问是否应该增加版本作为能力指示符,以告知 API 消费者,当添加了不容易发现的重大功能时,项目一直有这种习惯。

客户端 (CLI) 影响

“openstack baremetal” CLI

“openstacksdk”

RPC API 影响

驱动程序 API 影响

此功能预计将在 ironic “common” 代码中实现,该代码可供所有使用通用代码进行 URL 或对象检索的驱动程序和模块使用。因此,在这种模型中,预计不会破坏,只会增加整体功能。

Nova 驱动程序影响

Ramdisk 影响

总体而言,预计不会对 ramdisk 组成或结构产生影响。

我们需要解决的一个细节是在 image_download_source 设置为 http 时,将 pull secret 或其他 bearer token 传递给 agent 以支持经过身份验证的容器仓库工件访问。

安全影响

作为此更改的一部分,将维护围绕镜像校验和的整体合同。预计不会产生其他安全影响,但是 Ironic-Python-Agent 中的实现需要注意,将需要进行校验和操作。至少在识别正确的校验和方面,校验和解析将是 conductor 进程的责任。

提议的与 Ironic 交互的模型应该能够使校验和透明地发生,因为它们在工件检索的最低层之外执行。 底层协议建立在通过 HTTP 进行文件传输的模型之上,如果客户端能够,该模型可以原生解压缩,并且与 2024 年 qemu-img 中的安全问题截然不同,在 2024 年,磁盘镜像通过内存进行交互和流式传输,多个 qemu 插件试图访问用户提供的磁盘镜像以进行数据转换。 从符合 OCI 标准的注册表复制的整个过程将主要采用 HTTP 交互的形式,可能包括仅传输所需的工件文件。 一旦该文件处于准备好进行文件检查的状态,例如 qcow2 文件,我们预计将对其进行检查。

另一个方面是任何用户提供的拉取密钥,可能需要它来访问容器注册表。 目前,如果将其设置为具有适当名称的现有 instance_info 字段,则应使该值对 API 消费者可见。 实现者应特别注意在操作完成后清除此值。

其他最终用户影响

用户请求的 image_download_sourceswift 将需要明确拒绝,因为它是一种不兼容的配置。

可扩展性影响

预计没有。

性能影响

除非(直到)它们支持直接的 manifest 工件检索,否则工具的使用目前可能利用类似 /var/tmp 的位置来缓存容器的片段,并且可能需要检索其他工件/副本才能在预先复制操作中进行。

其他部署者影响

无。

开发人员影响

实现

负责人

主要负责人

Julia Kreger - TheJulia on IRC

其他贡献者

Steve Baker - sbaker on IRC

工作项

  • 在 Ironic 中创建新的镜像检索类

  • 添加支持潜在地提供拉取密钥以及清理拉取密钥的功能。

  • 在 Ironic 中添加单元测试

  • 在 ironic-python-agent 中探索和工作以支持此功能。

依赖项

目前,尚未发现任何依赖项。

测试

目前存在的一个未知数是“如何”我们可能能够对此更改执行 CI。 最好的途径将是与 Metal3 合作,使其测试能够走这条路径,但是 Ironic 可能需要单独添加额外的 CI 作业来促进此测试。

升级和向后兼容性

不适用。

文档影响

文档将随着此功能的更新而更新。 我们预计这将采用更多“如何从 OCI 容器部署镜像”示例文档的形式,而不是“你可能会如何做到”的形式,后者是该项目的历史文档风格。

参考资料