本文使用 GPT-4o mini 机翻 containerd 文档,再进行精校,便于浏览。 原文地址:containerd/docs/content-flow.md at main · containerd/containerd · GitHub

containerd 的主要目标是创建一个系统,使内容可以用于执行容器。为了实现这一流程,containerd 需要管理内容。

本文档描述了内容如何流入 containerd、如何管理以及在流程的每个阶段内容存在的位置。我们使用一个已知的镜像 docker.io/library/redis:5.0.9 的示例来探讨内容流动。

内容区域 Content Areas

内容在 containerd 生命周期中的多个区域中存在:

  • OCI 注册表,例如 hub.docker.comquay.io
  • containerd 内容存储,位于 containerd 的本地存储空间下,例如,在标准 Linux 安装中的 /var/lib/containerd/io.containerd.content.v1.content
  • 快照,位于 containerd 的本地存储空间下,例如,在标准 Linux 安装中的 /var/lib/containerd/io.containerd.snapshotter.v1.<type>。对于 overlayfs 快照器,它位于 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs

容器需要一个可挂载且通常是可变的文件系统来运行。这个文件系统是从内容存储中的内容创建的。为了创建一个容器,必须执行以下操作:

  1. 镜像及其所有内容必须被加载到本地内容存储中。这通常通过从 OCI 注册表下载来完成,但您也可以直接加载内容。该内容的格式与注册表中的格式相同。
  2. 必须读取镜像中的层并将其应用于文件系统,创建所谓的“已提交快照”。这对于每一层依次重复。这个过程称为“解包”。
  3. 必须在镜像的最终内容层之上创建一个最终的可变可挂载文件系统,即“活动快照”。

现在可以创建一个容器,其根文件系统即为活动快照。

本文档的其余部分详细查看每个区域中的内容及其相互关系。

镜像格式 Image Format

注册表中的镜像通常以以下格式存储。“镜像”由一个称为描述符的 JSON 文档组成。描述符始终包含一个元素 mediaType,它告诉我们其类型。它有两个选项之一:

  • “清单”(manifest),列出用于运行镜像作为容器的配置文件的哈希值,以及创建镜像文件系统的二进制数据层
  • “索引”(index),列出清单的哈希值,每个平台一个,其中平台是架构(例如 amd64 或 arm64)与操作系统(例如 linux)的组合

索引的目的是允许我们选择与目标平台匹配的清单。

要将注册表中的镜像引用(例如 redis:5.0.9)转换为实际的磁盘存储,我们:

  1. 检索镜像的描述符(JSON 文档)
  2. mediaType 确定描述符是清单还是索引:
    • 如果描述符是索引,查找表示我们希望运行容器的平台(架构+操作系统),使用该哈希检索清单
    • 如果描述符已经是清单,则继续
  3. 对于清单中的每个元素 - 配置和一个或多个层 - 使用列出的哈希检索组件并保存它们

我们使用示例镜像 redis:5.0.9 来澄清该过程。

当我们第一次解析 redis:5.0.9 时,我们获得以下 JSON 文档:

{
    "manifests": [
        {
            "digest": "sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "amd64",
                "os": "linux"
            },
            "size": 1572
        },
        {
            "digest": "sha256:aeb53f8db8c94d2cd63ca860d635af4307967aa11a2fdead98ae0ab3a329f470",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "arm",
                "os": "linux",
                "variant": "v5"
            },
            "size": 1573
        },
        {
            "digest": "sha256:17dc42e40d4af0a9e84c738313109f3a95e598081beef6c18a05abb57337aa5d",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "arm",
                "os": "linux",
                "variant": "v7"
            },
            "size": 1573
        },
        {
            "digest": "sha256:613f4797d2b6653634291a990f3e32378c7cfe3cdd439567b26ca340b8946013",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "arm64",
                "os": "linux",
                "variant": "v8"
            },
            "size": 1573
        },
        {
            "digest": "sha256:ee0e1f8d8d338c9506b0e487ce6c2c41f931d1e130acd60dc7794c3a246eb59e",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "386",
                "os": "linux"
            },
            "size": 1572
        },
        {
            "digest": "sha256:1072145f8eea186dcedb6b377b9969d121a00e65ae6c20e9cd631483178ea7ed",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "mips64le",
                "os": "linux"
            },
            "size": 1572
        },
        {
            "digest": "sha256:4b7860fcaea5b9bbd6249c10a3dc02a5b9fb339e8aef17a542d6126a6af84d96",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "ppc64le",
                "os": "linux"
            },
            "size": 1573
        },
        {
            "digest": "sha256:d66dfc869b619cd6da5b5ae9d7b1cbab44c134b31d458de07f7d580a84b63f69",
            "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
            "platform": {
                "architecture": "s390x",
                "os": "linux"
            },
            "size": 1573
        }
    ],
    "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
    "schemaVersion": 2
}

上述描述符的最后部分显示 mediaType 是“manifest.list”,或在 OCI 术语中是索引。它有一个名为 manifests 的数组字段,每个元素列出一个平台及其对应的清单哈希。平台是“架构”和“操作系统”的组合。由于我们将在常见的 amd64 上运行 linux,因此我们查找 manifests 中具有以下 platform 条目的条目:

"platform": {
  "architecture": "amd64",
  "os": "linux"
}

这是列表中的第一个,它的哈希是 sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b

然后我们检索该哈希对应的项,特别是 docker.io/library/redis@sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b,这给我们提供了该镜像在 linux/amd64 上的清单:

{
    "schemaVersion": 2,
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "config": {
        "mediaType": "application/vnd.docker.container.image.v1+json",
        "size": 7648,
        "digest": "sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a"
    },
    "layers": [
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 27092228,
            "digest": "sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 1732,
            "digest": "sha256:1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 1417672,
            "digest": "sha256:5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 7348264,
            "digest": "sha256:bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 98,
            "digest": "sha256:fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07"
        },
        {
            "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
            "size": 409,
            "digest": "sha256:97481c7992ebf6f22636f87e4d7b79e962f928cdbe6f2337670fa6c9a9636f04"
        }
    ]
}

mediaType 告诉我们这是一个“清单”,并且符合正确的格式:

  • 一个 config,其哈希为 sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a
  • 一个或多个 layers;在此示例中,有 6 个层

这些元素 - 索引、清单、配置文件和每个层 - 都在注册表中单独存储,并独立下载。

内容存储 Content Store

当内容加载到 containerd 的内容存储中时,它们的存储方式与注册表非常相似。每个组件都存储在一个文件中,该文件的名称是其哈希值。

继续我们的 redis 示例,如果我们执行 client.Pull()ctr pull,我们将在内容存储中看到以下内容:

  • sha256:2a9865e55c37293b71df051922022898d8e4ec0f579c9b53a0caee1b170bc81c - 索引
  • sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b - linux/amd64 的清单
  • sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a - 配置
  • sha256:97481c7992ebf6f22636f87e4d7b79e962f928cdbe6f2337670fa6c9a9636f04 - 层 0
  • sha256:5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05 - 层 1
  • sha256:bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f - 层 2
  • sha256:fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07 - 层 3
  • sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90 - 层 4
  • sha256:1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94 - 层 5

如果我们查看内容存储,我们确实能看到这些(我进行了筛选和排序以便于阅读):

$ tree /var/lib/containerd/io.containerd.content.v1.content/blobs
/var/lib/containerd/io.containerd.content.v1.content/blobs
└── sha256
    ├── 2a9865e55c37293b71df051922022898d8e4ec0f579c9b53a0caee1b170bc81c
    ├── 9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b
    ├── 987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a
    ├── 97481c7992ebf6f22636f87e4d7b79e962f928cdbe6f2337670fa6c9a9636f04
    ├── 5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05
    ├── bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f
    ├── fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07
    ├── bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90
    └── 1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94

如果我们使用 containerd 接口,也能看到相同的内容。为了便于查看,我们再次进行了排序。

$ ctr content ls
DIGEST                                                                  SIZE    AGE             LABELS
sha256:2a9865e55c37293b71df051922022898d8e4ec0f579c9b53a0caee1b170bc81c 1.862kB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/gc.ref.content.m.0=sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b,containerd.io/gc.ref.content.m.1=sha256:aeb53f8db8c94d2cd63ca860d635af4307967aa11a2fdead98ae0ab3a329f470,containerd.io/gc.ref.content.m.2=sha256:17dc42e40d4af0a9e84c738313109f3a95e598081beef6c18a05abb57337aa5d,containerd.io/gc.ref.content.m.3=sha256:613f4797d2b6653634291a990f3e32378c7cfe3cdd439567b26ca340b8946013,containerd.io/gc.ref.content.m.4=sha256:ee0e1f8d8d338c9506b0e487ce6c2c41f931d1e130acd60dc7794c3a246eb59e,containerd.io/gc.ref.content.m.5=sha256:1072145f8eea186dcedb6b377b9969d121a00e65ae6c20e9cd631483178ea7ed,containerd.io/gc.ref.content.m.6=sha256:4b7860fcaea5b9bbd6249c10a3dc02a5b9fb339e8aef17a542d6126a6af84d96,containerd.io/gc.ref.content.m.7=sha256:d66dfc869b619cd6da5b5ae9d7b1cbab44c134b31d458de07f7d580a84b63f69
sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b 1.572kB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/gc.ref.content.config=sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a,containerd.io/gc.ref.content.l.0=sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90,containerd.io/gc.ref.content.l.1=sha256:1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94,containerd.io/gc.ref.content.l.2=sha256:5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05,containerd.io/gc.ref.content.l.3=sha256:bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f,containerd.io/gc.ref.content.l.4=sha256:fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07,containerd.io/gc.ref.content.l.5=sha256:97481c7992ebf6f22636f87e4d7b79e962f928cdbe6f2337670fa6c9a9636f04
sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a 7.648kB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/gc.ref.snapshot.overlayfs=sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd
sha256:97481c7992ebf6f22636f87e4d7b79e962f928cdbe6f2337670fa6c9a9636f04 409B    20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:d442ae63d423b4b1922875c14c3fa4e801c66c689b69bfd853758fde996feffb
sha256:5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05 1.418MB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:223b15010c47044b6bab9611c7a322e8da7660a8268949e18edde9c6e3ea3700
sha256:bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f 7.348MB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:b96fedf8ee00e59bf69cf5bc8ed19e92e66ee8cf83f0174e33127402b650331d
sha256:fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07 98B     20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:aff00695be0cebb8a114f8c5187fd6dd3d806273004797a00ad934ec9cd98212
sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90 27.09MB 19 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c
sha256:1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94 1.732kB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:832f21763c8e6b070314e619ebb9ba62f815580da6d0eaec8a1b080bd01575f7

标签 Labels

请注意,每个内容块都有几个标签。本小节描述标签。这并不是对标签的全面概述。

常见标签 Common Labels

对于从远程拉取的镜像,containerd.io/distribution.source.<registry>=[<repo/1>,<repo/2>] 标签会被添加到镜像的每个 blob,以指示其来源。

containerd.io/distribution.source.docker.io=library/redis

如果 blob 被同一注册表中的不同仓库共享,仓库名称将被附加:

containerd.io/distribution.source.docker.io=library/redis,myrepo/redis
层标签 Layer Labels

我们从层本身开始。这些只有一个标签:containerd.io/uncompressed。这些文件是经过 gzip 压缩的 tar 文件;标签的值给出了它们未压缩时的哈希。您可以通过以下操作获得相同的值:

$ cat <file> | gunzip - | sha256sum -

例如:

$ cat /var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94 | gunzip - | sha256sum -
832f21763c8e6b070314e619ebb9ba62f815580da6d0eaec8a1b080bd01575f7

这与最后一层精确对齐:

sha256:1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94 1.732kB 20 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:832f21763c8e6b070314e619ebb9ba62f815580da6d0eaec8a1b080bd01575f7
配置标签 Config Labels

我们有一个单独的配置层 sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a。它有一个以 containerd.io/gc.ref. 开头的标签,指示它是一个影响垃圾回收的标签。

在这种情况下,标签是 containerd.io/gc.ref.snapshot.overlayfs,值为 sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd

这用于将此配置与快照连接。我们将在稍后讨论快照时查看这一点。

清单标签 Manifest Labels

清单上的标签也以 containerd.io/gc.ref 开头,指示它们用于控制垃圾回收。一个清单有多个“子项”。这些通常是配置和层。我们希望确保只要镜像存在,即清单,子项就不会被垃圾回收。因此,我们有标签引用每个子项:

  • containerd.io/gc.ref.content.config 引用配置
  • containerd.io/gc.ref.content.l.<index> 引用层

在我们的示例中,清单是 sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b,标签如下:

containerd.io/gc.ref.content.config=sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6
containerd.io/gc.ref.content.l.0=sha256:97481c7992ebf6f22636f87e4d7b79e962f928cdbe6f2337670fa6c9a9636f04
containerd.io/gc.ref.content.l.1=sha256:5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05
containerd.io/gc.ref.content.l.2=sha256:bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f
containerd.io/gc.ref.content.l.3=sha256:fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07
containerd.io/gc.ref.content.l.4=sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90
containerd.io/gc.ref.content.l.5=sha256:1ed3521a5dcbd05214eb7f35b952ecf018d5a6610c32ba4e315028c556f45e94

这些正是清单的子项 - 配置和层 - 存储在我们的内容存储中。

索引标签 Index Labels

索引上的标签也以 containerd.io/gc.ref 开头,指示它们用于控制垃圾回收。一个索引有多个“子项”,即清单,每个平台一个,如上所述。我们希望确保只要索引存在,子项就不会被垃圾回收。因此,我们有标签引用每个子项,containerd.io/gc.ref.content.m.<index>

在我们的示例中,索引是 sha256:2a9865e55c37293b71df051922022898d8e4ec0f579c9b53a0caee1b170bc81c,标签如下:

containerd.io/gc.ref.content.m.0=sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b
containerd.io/gc.ref.content.m.1=sha256:aeb53f8db8c94d2cd63ca860d635af4307967aa11a2fdead98ae0ab3a329f470
containerd.io/gc.ref.content.m.2=sha256:17dc42e40d4af0a9e84c738313109f3a95e598081beef6c18a05abb57337aa5d
containerd.io/gc.ref.content.m.3=sha256:613f4797d2b6653634291a990f3e32378c7cfe3cdd439567b26ca340b8946013
containerd.io/gc.ref.content.m.4=sha256:ee0e1f8d8d338c9506b0e487ce6c2c41f931d1e130acd60dc7794c3a246eb59e
containerd.io/gc.ref.content.m.5=sha256:1072145f8eea186dcedb6b377b9969d121a00e65ae6c20e9cd631483178ea7ed
containerd.io/gc.ref.content.m.6=sha256:4b7860fcaea5b9bbd6249c10a3dc02a5b9fb339e8aef17a542d6126a6af84d96
containerd.io/gc.ref.content.m.7=sha256:d66dfc869b619cd6da5b5ae9d7b1cbab44c134b31d458de07f7d580a84b63f69

请注意,索引有 8 个子项,但它们都是针对我们以外的平台 linux/amd64,因此,只有一个子项 sha256:9bb13890319dc01e5f8a4d3d0c4c72685654d682d568350fd38a02b1d70aee6b 实际存在于我们的内容存储中。这没有问题;这只是意味着其他子项也不会被垃圾回收。由于它们不在这里,因此不会被删除。

快照 Snapshots

内容存储中的内容不能直接被容器使用。

首先,它是不可变的,这使得容器很难使用它作为容器文件系统。其次,格式本身通常是不可用的。例如,大多数容器层是 tar-gzip 格式,每个 tar-gzip 文件代表一个要应用于前一层的单层。不能简单地挂载一个 tar-gzip 文件。即使可以,也需要将每一层的更改应用到之前的层之上。第三,一些内容层的媒体类型,如标准容器层,不仅包括常规文件添加和修改,还包括删除。所有这些都不能被容器直接使用,容器需要一个正常的文件系统挂载。

为了使用镜像的内容,我们创建内容的快照。

该过程如下:

  1. 快照器从父层创建快照。在第一层的情况下,这是空的。现在这是一个“活动”快照。
  2. 差异应用器(diff applier),它知道层 blob 的内部格式,将层 blob 应用到活动快照。
  3. 快照器在差异应用后提交快照。现在这是一个“已提交”快照。
  4. 已提交快照用作下一层的父层。

containerd 自带多个内置快照器,默认的是 overlayfs。您可以为每个镜像解包和创建容器选择不同的快照器。请参见 snapshottersPLUGINS

回到我们的示例,每一层将有一个对应的不可变快照层。回想一下我们的示例有 6 层,我们预计会看到 6 个已提交的快照。输出已排序以便于查看;它与内容存储和清单本身的层相匹配。

$ ctr snapshot ls
KEY                                                                     PARENT                                                                  KIND
sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 Committed
sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 sha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 Committed
sha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 sha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca Committed
sha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca sha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 Committed
sha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c Committed
sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c                                                                         Committed

如果我们查看特定于每个快照器的快照目录,我们会看到快照本身。

# cd /var/lib/containerd
# ls io.containerd.snapshotter.v1.overlayfs/snapshots/
1  2  3  4  5  6

有 6 个快照,每个快照对应于上面 ctr snapshot ls 列表中的一个。目录本身包含实际内容:

# ls io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
# ls io.containerd.snapshotter.v1.overlayfs/snapshots/2/fs
etc  var

这些是第一层和第二层的解包和应用内容。

父层 Parents

每个快照都有一个父层,根层除外。它是一个树,或一个叠层蛋糕,从第一层开始。这与层的构建方式相匹配。

名称 Name

快照的键或名称与内容存储中的哈希不匹配。这是因为内容存储中的哈希是原始内容的哈希,在这种情况下是 tar-gzip 格式。快照将其扩展到文件系统中,使其变得有用。它也与未压缩的内容(即没有 gzip 的 tar 文件)不匹配,并在标签 containerd.io/uncompressed 中给出。

而是名称是将层应用于前一层并对其进行哈希的结果。从这个逻辑来看,树的根,即第一层,应该与第一层 blob 的未压缩值具有相同的哈希和名称。确实如此。根层是 sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90,当未压缩时,其值为 sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c,这是快照中的第一层,也是内容存储中该层的标签:

sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90 27.09MB 19 minutes      containerd.io/distribution.source.docker.io=library/redis,containerd.io/uncompressed=sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c

最后一层 Final Layer

最后一层或顶部层是您希望创建活动快照以启动容器的点。因此,我们需要跟踪它。这正是配置上放置的标签。在我们的示例中,配置位于 sha256:987b553c835f01f46eb1859bc32f564119d5833801a27b25a0ca5c6b8b6e111a,并具有标签 containerd.io/gc.ref.snapshot.overlayfs=sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd

查看我们的快照,堆栈的最后一层的值确实是:

sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 Committed

请注意,内容存储中的配置上的标签以 containerd.io/gc.ref 开头。这是一个垃圾回收标签。正是这个标签使垃圾回收器无法删除快照。由于配置对其有引用,因此顶层受到“保护”,不会被垃圾回收。此层依次依赖于下层,因此它也受到保护,直到根层或基础层。

容器 Container

在上述内容到位后,我们知道如何创建一个对容器有用的活动快照。我们只需 Prepare() 活动快照,传递一个 ID 和父层,在这种情况下是已提交快照的顶部层。

我们可以通过从同一镜像创建两个容器来查看这一点。两个容器都将在已提交快照的顶部创建活动快照。然而,我们预计仅会看到 2 个新的快照,每个快照都是活动的。已提交快照保持不变,因为它们被重用。

# ctr container create docker.io/library/redis:5.0.6 redis1
# ctr container create docker.io/library/redis:5.0.6 redis2
ctr snapshot ls
KEY                                                                     PARENT                                                                  KIND
redis1                                                                  sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd Active
redis2                                                                  sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd Active
sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 Committed
sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 sha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 Committed
sha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 sha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca Committed
sha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca sha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 Committed
sha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c Committed
sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c                                                                         Committed

相同的 6 个已提交层存在,但仅创建了 2 个新的活动快照,每个快照对应于一个容器。两者的父层都是顶层已提交快照 sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd

因此,步骤为:

  1. 将内容放入内容存储,可以通过 Pull() 或通过在 content.Store API 中加载内容。
  2. 解包镜像以创建每层的已提交快照,使用 image.Unpack()。或者,如果您使用 Pull(),您可以在拉取时传递一个选项以解包,使用 WithPullUnpack()
  3. 使用 Prepare() 创建一个活动快照。如果您计划创建容器,可以跳过此步骤,因为您可以将其作为选项传递给下一步。
  4. 使用 NewContainer() 创建容器,可选择告知它使用 WithNewSnapshot() 创建快照。