containerd 的主要目标是创建一个系统,在这里 content 可以被用来执行 containers。为了执行在流上执行,containerd 需要 content 并且管理它。
这篇文章描述了 content 在 containerd 中是如何流动的,如果被管理的,它在哪里存在。我们使用镜像 docker.io/library/redis:5.0.9 作为例子来探索 content flow。
Content Areas
Content 存在于 containerd 生命周期的很多区域:
- OCI registry,例如 hub.docker.com 和 quay.io
- Containerd content 存储,在 containerd 的本地存储区域,例如标准的 linux 安装情况下,它会在
/var/lib/containerd/io.containerd.content.v1.content - snapshots,在 containerd 的本地存储区域,例如标准的 Linux 安装情况下
/var/lib/containerd/io.containerd.snapshotter.v1.<type>,对于一个 overlayfs snapshotter,它将会在/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs
创建一个容器的过程中,会发生下面这些:
- 镜像和他的 content 必须被载入到 content 存储中,这通常在从 OCI registry 下载镜像时发生,但是也可指直接载入 content
- 被提交的 snapshots 必须从镜像中的每一层 content 创建
- 一个活跃的 snapshot 必须在镜像的最后一层 content 的顶部被创建
一个容器现在被创建,它的 root 文件系统作为活跃的 snapshot
这篇文档剩余的部分会详细介绍在每一个区域中的 content和他们如何与其他的关联上的。
Image Format
在 registry 中的镜像通常以下面的格式存在,一个 “image” 包含一个作为 descriptor 的 JSON 文档。这个 descriptor 中有一个参数 mediaType,这个参数告诉我们它是那个类型,这里有两种选项:
- manifest:列出这个运行将这个镜像运行为 container 的配置文件和为镜像创建文件系统的二进制数据的 hash 值
- index:列出各个平台的 manifests 的哈希值,每一个平台都包含架构(e.g. amd64 和 arm64)和操作系统(e.g. linux)
index 的目的是允许选择匹配目标平台的 manifest
为了将一个镜像从 registry 转移到实际的硬盘存储:
- 取到镜像的 descriptor (JSON 文件)
- 根据
mediaType判断这个 descriptor 是 manifest 或者是 index- 如果 descriptor 是 index,找到想要运行 container 的对应平台,使用 hash 来找到 manifest
- 如果是 manifest,继续下一步
- 对于 manifest 中的每一个项目(配置和数据层),使用 hash 列表来获取组件并且保存他们
我们使用我们的例子 redis:5.0.9,来说明一下这个流程
当我们第一次解析 redis:5.0.9,会获取到下面的 JSON 文档
{"manifests": [{"digest": "sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "amd64","os": "linux"},"size": 1572},{"digest": "sha256:4ff8940144391ecd5e1632d0c427d95f4a8d2bb4a72b7e3898733352350d9ab3","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "arm","os": "linux","variant": "v5"},"size": 1573},{"digest": "sha256:ce541c3e2570b5a05d40e7fc01f87fc1222a701c81f95e7e6f2ef6df1c6e25e7","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "arm","os": "linux","variant": "v7"},"size": 1573},{"digest": "sha256:535ee258100feeeb525d4793c16c7e58147c105231d7d05ffc9c84b56750f233","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "arm64","os": "linux","variant": "v8"},"size": 1573},{"digest": "sha256:0f3b047f2789547c58634ce88d71c7856999b2afc8b859b7adb5657043984b26","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "386","os": "linux"},"size": 1572},{"digest": "sha256:bfc45f499a9393aef091057f3d067ff7129ae9fb30d9f31054bafe96ca30b8d6","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "mips64le","os": "linux"},"size": 1572},{"digest": "sha256:3198e1f1707d977939154a57918d360a172c575bddeac875cb26ca6f4d30dc1c","mediaType": "application/vnd.docker.distribution.manifest.v2+json","platform": {"architecture": "ppc64le","os": "linux"},"size": 1573},{"digest": "sha256:24a15cc9366e1557db079a987e63b98a5abf4dee4356a096442f53ddc8b9c7e9","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}
在 descriptor 的最下面,显示了 mediaType 为 “manifest.list”,用 OCI 术语就是 index。它有一个 数组 field 叫做 manifests,列表中的每一项都是一个 platform 和这个平台所对应的 manifest 的 hash。”platform” 是 “architecture” 和 “os” 的结合。因为我们会运行在 linux 和 amd64 生,所以需要找到一个 platform 像下面这样的 manifests。
"platform": {"architecture": "amd64","os": "linux"}
这是列表中的第一个, 它的 hash 是 sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a
然后我们使用 hash 获取这个项目, docker.io/library/redis@sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a,这会给我们这个 image 在 linux/amd64 上的 manifest.
{"schemaVersion": 2,"mediaType": "application/vnd.docker.distribution.manifest.v2+json","config": {"mediaType": "application/vnd.docker.container.image.v1+json","size": 6836,"digest": "sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6"},"layers": [{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 27098147,"digest": "sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b"},{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 1730,"digest": "sha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774"},{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 1417708,"digest": "sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac"},{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 7345094,"digest": "sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7"},{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 99,"digest": "sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6"},{"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip","size": 410,"digest": "sha256:602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b"}]}
mediaType 告诉我们这是一个 manifest, 它符合下面的格式
config, hash 是sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6- 一个或者多个
layers, 在这个示例中有liuge
这些项目中的每一个 index, manifests, 配置文件和每一层, 都分散的存储在 registry 中, 也被分别下载
Content Store
当 content 被载入到 containerd 的 content 存储中,它存储的方式与 registry 非常相似。每一个组件都存储在以 hash 命名的文件中。
继续我们的 redis 示例,如果使用 client.Pull() 或者 ctr pull,在 content store 中会得到下面的
sha256:1d0b903e3770c2c3c79961b73a53e963f4fd4b2674c2c4911472e8a054cb5728- the indexsha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a- the manifest for linux/amd64sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6- the configsha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b- layer 0sha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774- layer 1sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac- layer 2sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7- layer 3sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6- layer 4sha256:602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b- layer 5
如果查看 content 存储,会看到下面的这些(为了容易阅读经过了过滤和排序)
$ tree /var/lib/containerd/io.containerd.content.v1.content/blobs/var/lib/containerd/io.containerd.content.v1.content/blobs└── sha256├── 1d0b903e3770c2c3c79961b73a53e963f4fd4b2674c2c4911472e8a054cb5728├── a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a├── df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6├── 123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b├── f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774├── 66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac├── 79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7├── de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6└── 602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b
如果我们使用 containerd 接口也能够看到同样的东西(同样是经过处理的)
ctr content lsDIGEST SIZE AGE LABELSsha256:1d0b903e3770c2c3c79961b73a53e963f4fd4b2674c2c4911472e8a054cb5728 1.862 kB 6 minutes containerd.io/gc.ref.content.0=sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a,containerd.io/gc.ref.content.1=sha256:4ff8940144391ecd5e1632d0c427d95f4a8d2bb4a72b7e3898733352350d9ab3,containerd.io/gc.ref.content.2=sha256:ce541c3e2570b5a05d40e7fc01f87fc1222a701c81f95e7e6f2ef6df1c6e25e7,containerd.io/gc.ref.content.3=sha256:535ee258100feeeb525d4793c16c7e58147c105231d7d05ffc9c84b56750f233,containerd.io/gc.ref.content.4=sha256:0f3b047f2789547c58634ce88d71c7856999b2afc8b859b7adb5657043984b26,containerd.io/gc.ref.content.5=sha256:bfc45f499a9393aef091057f3d067ff7129ae9fb30d9f31054bafe96ca30b8d6,containerd.io/gc.ref.content.6=sha256:3198e1f1707d977939154a57918d360a172c575bddeac875cb26ca6f4d30dc1c,containerd.io/gc.ref.content.7=sha256:24a15cc9366e1557db079a987e63b98a5abf4dee4356a096442f53ddc8b9c7e9sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a 1.572 kB 6 minutes containerd.io/gc.ref.content.2=sha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774,containerd.io/gc.ref.content.3=sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac,containerd.io/gc.ref.content.4=sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7,containerd.io/gc.ref.content.5=sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6,containerd.io/gc.ref.content.6=sha256:602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b,containerd.io/gc.ref.content.0=sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6,containerd.io/gc.ref.content.1=sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4bsha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6 6.836 kB 6 minutes containerd.io/gc.ref.snapshot.overlayfs=sha256:87806a591ce894ff5c699c28fe02093d6cdadd6b1ad86819acea05ccb212ff3dsha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b 27.1 MB 6 minutes containerd.io/uncompressed=sha256:b60e5c3bcef2f42ec42648b3acf7baf6de1fa780ca16d9180f3b4a3f266fe7bcsha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774 1.73 kB 6 minutes containerd.io/uncompressed=sha256:b5a8df342567aa93d568b263b25c1eaf52655f0952e1911742ffb4f7a521e044sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac 1.418 MB 6 minutes containerd.io/uncompressed=sha256:c03c7e9701eb61f1e2232f6d19faa699cd9d346207aaf4f50d84b1e37bbad3e2sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7 7.345 MB 6 minutes containerd.io/uncompressed=sha256:367024e4e00618a9ada3203b5922d3186a0aa6136a1c4cbf5ed380171e1afe48sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6 99 B 6 minutes containerd.io/uncompressed=sha256:60ef3ee42de712ef7748cc8e92192e926180b1be6fec9580933f1347fb6b2747sha256:602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b 410 B 6 minutes containerd.io/uncompressed=sha256:bab68e5155b7010010964bf3aadc30e4a9c625701314ff6fa3c143c72f0aeb9c
Labels
content 的每一块都有数个 labels,这个分块是用来介绍 labels 的,但这不是一个对于 labels 的全部概览
Layer Labels
我们以 layers 自身开始,这些都只含有一个 label: containerd.io/uncompressed,这些文件是 gzipped tar 文件,label 在解压的时候提供 hash 值
你可以这样做获取同样的值
$ cat <file> | gunzip - | sha256sum -
例如:
$ cat /var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b | gunzip - | sha256sum -bab68e5155b7010010964bf3aadc30e4a9c625701314ff6fa3c143c72f0aeb9c
Config Labels
我们有一个单独的 config layer,sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6, 它有一个以 cpntainerd.io/gc.ref 为前缀的 label,这个标签标示它影响垃圾回收
示例,label 为 containerd.io/gc.ref.snapshot.overlayfs 并且有值 sha256:87806a591ce894ff5c699c28fe02093d6cdadd6b1ad86819acea05ccb212ff3d
这被用来将这个 config 连接到 snapshot,我们一会讨论 snapshot 时,就能看到它。
Manifest Labels
Index Labels
Snapshots
在 content 存储中的 content 时不可变的,但是也有一些格式是不可用的,例如大多数 container layers 是 tar-gzip 格式,不能简单的挂在 tar-gzip 文件,即使可以,我们也希望 content 是不可变的。
为了使用它,我们为 content 创建了 snapshot
流程就像下面这样:
- snapshotter 从 parent 创建 snapshot。在第一层的时候,是空白的,现在这是一个 “active” 的 snapshot
