本文使用 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.com 或 quay.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
容器需要一个可挂载且通常是可变的文件系统来运行。这个文件系统是从内容存储中的内容创建的。为了创建一个容器,必须执行以下操作:
- 镜像及其所有内容必须被加载到本地内容存储中。这通常通过从 OCI 注册表下载来完成,但您也可以直接加载内容。该内容的格式与注册表中的格式相同。
- 必须读取镜像中的层并将其应用于文件系统,创建所谓的“已提交快照”。这对于每一层依次重复。这个过程称为“解包”。
- 必须在镜像的最终内容层之上创建一个最终的可变可挂载文件系统,即“活动快照”。
现在可以创建一个容器,其根文件系统即为活动快照。
本文档的其余部分详细查看每个区域中的内容及其相互关系。
镜像格式 Image Format
注册表中的镜像通常以以下格式存储。“镜像”由一个称为描述符的 JSON 文档组成。描述符始终包含一个元素 mediaType
,它告诉我们其类型。它有两个选项之一:
- “清单”(manifest),列出用于运行镜像作为容器的配置文件的哈希值,以及创建镜像文件系统的二进制数据层
- “索引”(index),列出清单的哈希值,每个平台一个,其中平台是架构(例如 amd64 或 arm64)与操作系统(例如 linux)的组合
索引的目的是允许我们选择与目标平台匹配的清单。
要将注册表中的镜像引用(例如 redis:5.0.9
)转换为实际的磁盘存储,我们:
- 检索镜像的描述符(JSON 文档)
- 从
mediaType
确定描述符是清单还是索引:- 如果描述符是索引,查找表示我们希望运行容器的平台(架构+操作系统),使用该哈希检索清单
- 如果描述符已经是清单,则继续
- 对于清单中的每个元素 - 配置和一个或多个层 - 使用列出的哈希检索组件并保存它们
我们使用示例镜像 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
- 层 0sha256:5999b99cee8f2875d391d64df20b6296b63f23951a7d41749f028375e887cd05
- 层 1sha256:bfee6cb5fdad6b60ec46297f44542ee9d8ac8f01c072313a51cd7822df3b576f
- 层 2sha256:fd36a1ebc6728807cbb1aa7ef24a1861343c6dc174657721c496613c7b53bd07
- 层 3sha256:bb79b6b2107fea8e8a47133a660b78e3a546998fcf0427be39ac9a0af4a97e90
- 层 4sha256: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 文件。即使可以,也需要将每一层的更改应用到之前的层之上。第三,一些内容层的媒体类型,如标准容器层,不仅包括常规文件添加和修改,还包括删除。所有这些都不能被容器直接使用,容器需要一个正常的文件系统挂载。
为了使用镜像的内容,我们创建内容的快照。
该过程如下:
- 快照器从父层创建快照。在第一层的情况下,这是空的。现在这是一个“活动”快照。
- 差异应用器(diff applier),它知道层 blob 的内部格式,将层 blob 应用到活动快照。
- 快照器在差异应用后提交快照。现在这是一个“已提交”快照。
- 已提交快照用作下一层的父层。
containerd 自带多个内置快照器,默认的是 overlayfs
。您可以为每个镜像解包和创建容器选择不同的快照器。请参见 snapshotters 和 PLUGINS。
回到我们的示例,每一层将有一个对应的不可变快照层。回想一下我们的示例有 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
。
因此,步骤为:
- 将内容放入内容存储,可以通过 Pull() 或通过在 content.Store API 中加载内容。
- 解包镜像以创建每层的已提交快照,使用 image.Unpack()。或者,如果您使用 Pull(),您可以在拉取时传递一个选项以解包,使用 WithPullUnpack()。
- 使用 Prepare() 创建一个活动快照。如果您计划创建容器,可以跳过此步骤,因为您可以将其作为选项传递给下一步。
- 使用 NewContainer() 创建容器,可选择告知它使用 WithNewSnapshot() 创建快照。