containerd 的主要目标是创建一个系统,在这里 content 可以被用来执行 containers。为了执行在流上执行,containerd 需要 content 并且管理它。

这篇文章描述了 content 在 containerd 中是如何流动的,如果被管理的,它在哪里存在。我们使用镜像 docker.io/library/redis:5.0.9 作为例子来探索 content flow。

Content Areas

Content 存在于 containerd 生命周期的很多区域:

  • OCI registry,例如 hub.docker.comquay.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

创建一个容器的过程中,会发生下面这些:

  1. 镜像和他的 content 必须被载入到 content 存储中,这通常在从 OCI registry 下载镜像时发生,但是也可指直接载入 content
  2. 被提交的 snapshots 必须从镜像中的每一层 content 创建
  3. 一个活跃的 snapshot 必须在镜像的最后一层 content 的顶部被创建

一个容器现在被创建,它的 root 文件系统作为活跃的 snapshot

这篇文档剩余的部分会详细介绍在每一个区域中的 content和他们如何与其他的关联上的。

Image Format

在 registry 中的镜像通常以下面的格式存在,一个 “image” 包含一个作为 descriptor 的 JSON 文档。这个 descriptor 中有一个参数 mediaType,这个参数告诉我们它是那个类型,这里有两种选项:

  1. manifest:列出这个运行将这个镜像运行为 container 的配置文件和为镜像创建文件系统的二进制数据的 hash 值
  2. index:列出各个平台的 manifests 的哈希值,每一个平台都包含架构(e.g. amd64 和 arm64)和操作系统(e.g. linux)

index 的目的是允许选择匹配目标平台的 manifest

为了将一个镜像从 registry 转移到实际的硬盘存储:

  1. 取到镜像的 descriptor (JSON 文件)
  2. 根据 mediaType 判断这个 descriptor 是 manifest 或者是 index
    1. 如果 descriptor 是 index,找到想要运行 container 的对应平台,使用 hash 来找到 manifest
    2. 如果是 manifest,继续下一步
  3. 对于 manifest 中的每一个项目(配置和数据层),使用 hash 列表来获取组件并且保存他们

我们使用我们的例子 redis:5.0.9,来说明一下这个流程

当我们第一次解析 redis:5.0.9,会获取到下面的 JSON 文档

  1. {
  2. "manifests": [
  3. {
  4. "digest": "sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a",
  5. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  6. "platform": {
  7. "architecture": "amd64",
  8. "os": "linux"
  9. },
  10. "size": 1572
  11. },
  12. {
  13. "digest": "sha256:4ff8940144391ecd5e1632d0c427d95f4a8d2bb4a72b7e3898733352350d9ab3",
  14. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  15. "platform": {
  16. "architecture": "arm",
  17. "os": "linux",
  18. "variant": "v5"
  19. },
  20. "size": 1573
  21. },
  22. {
  23. "digest": "sha256:ce541c3e2570b5a05d40e7fc01f87fc1222a701c81f95e7e6f2ef6df1c6e25e7",
  24. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  25. "platform": {
  26. "architecture": "arm",
  27. "os": "linux",
  28. "variant": "v7"
  29. },
  30. "size": 1573
  31. },
  32. {
  33. "digest": "sha256:535ee258100feeeb525d4793c16c7e58147c105231d7d05ffc9c84b56750f233",
  34. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  35. "platform": {
  36. "architecture": "arm64",
  37. "os": "linux",
  38. "variant": "v8"
  39. },
  40. "size": 1573
  41. },
  42. {
  43. "digest": "sha256:0f3b047f2789547c58634ce88d71c7856999b2afc8b859b7adb5657043984b26",
  44. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  45. "platform": {
  46. "architecture": "386",
  47. "os": "linux"
  48. },
  49. "size": 1572
  50. },
  51. {
  52. "digest": "sha256:bfc45f499a9393aef091057f3d067ff7129ae9fb30d9f31054bafe96ca30b8d6",
  53. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  54. "platform": {
  55. "architecture": "mips64le",
  56. "os": "linux"
  57. },
  58. "size": 1572
  59. },
  60. {
  61. "digest": "sha256:3198e1f1707d977939154a57918d360a172c575bddeac875cb26ca6f4d30dc1c",
  62. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  63. "platform": {
  64. "architecture": "ppc64le",
  65. "os": "linux"
  66. },
  67. "size": 1573
  68. },
  69. {
  70. "digest": "sha256:24a15cc9366e1557db079a987e63b98a5abf4dee4356a096442f53ddc8b9c7e9",
  71. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  72. "platform": {
  73. "architecture": "s390x",
  74. "os": "linux"
  75. },
  76. "size": 1573
  77. }
  78. ],
  79. "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
  80. "schemaVersion": 2
  81. }

在 descriptor 的最下面,显示了 mediaType 为 “manifest.list”,用 OCI 术语就是 index。它有一个 数组 field 叫做 manifests,列表中的每一项都是一个 platform 和这个平台所对应的 manifest 的 hash。”platform” 是 “architecture” 和 “os” 的结合。因为我们会运行在 linux 和 amd64 生,所以需要找到一个 platform 像下面这样的 manifests

  1. "platform": {
  2. "architecture": "amd64",
  3. "os": "linux"
  4. }

这是列表中的第一个, 它的 hash 是 sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a

然后我们使用 hash 获取这个项目, docker.io/library/redis@sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a,这会给我们这个 image 在 linux/amd64 上的 manifest.

  1. {
  2. "schemaVersion": 2,
  3. "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  4. "config": {
  5. "mediaType": "application/vnd.docker.container.image.v1+json",
  6. "size": 6836,
  7. "digest": "sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6"
  8. },
  9. "layers": [
  10. {
  11. "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
  12. "size": 27098147,
  13. "digest": "sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b"
  14. },
  15. {
  16. "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
  17. "size": 1730,
  18. "digest": "sha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774"
  19. },
  20. {
  21. "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
  22. "size": 1417708,
  23. "digest": "sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac"
  24. },
  25. {
  26. "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
  27. "size": 7345094,
  28. "digest": "sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7"
  29. },
  30. {
  31. "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
  32. "size": 99,
  33. "digest": "sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6"
  34. },
  35. {
  36. "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
  37. "size": 410,
  38. "digest": "sha256:602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b"
  39. }
  40. ]
  41. }

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 index
  • sha256:a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a - the manifest for linux/amd64
  • sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6 - the config
  • sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b - layer 0
  • sha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774 - layer 1
  • sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac - layer 2
  • sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7 - layer 3
  • sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6 - layer 4
  • sha256:602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b - layer 5

如果查看 content 存储,会看到下面的这些(为了容易阅读经过了过滤和排序)

  1. $ tree /var/lib/containerd/io.containerd.content.v1.content/blobs
  2. /var/lib/containerd/io.containerd.content.v1.content/blobs
  3. └── sha256
  4. ├── 1d0b903e3770c2c3c79961b73a53e963f4fd4b2674c2c4911472e8a054cb5728
  5. ├── a5aae2581826d13e906ff5c961d4c2817a9b96c334fd97b072d976990384156a
  6. ├── df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6
  7. ├── 123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b
  8. ├── f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774
  9. ├── 66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac
  10. ├── 79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7
  11. ├── de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6
  12. └── 602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b

如果我们使用 containerd 接口也能够看到同样的东西(同样是经过处理的)

  1. ctr content ls
  2. DIGEST SIZE AGE LABELS
  3. sha256: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:24a15cc9366e1557db079a987e63b98a5abf4dee4356a096442f53ddc8b9c7e9
  4. sha256: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:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b
  5. sha256:df57482065789980ee9445b1dd79ab1b7b3d1dc26b6867d94470af969a64c8e6 6.836 kB 6 minutes containerd.io/gc.ref.snapshot.overlayfs=sha256:87806a591ce894ff5c699c28fe02093d6cdadd6b1ad86819acea05ccb212ff3d
  6. sha256:123275d6e508d282237a22fefa5aef822b719a06496444ea89efa65da523fc4b 27.1 MB 6 minutes containerd.io/uncompressed=sha256:b60e5c3bcef2f42ec42648b3acf7baf6de1fa780ca16d9180f3b4a3f266fe7bc
  7. sha256:f2edbd6a658e04d559c1bec36d838006bbdcb39d8fb9033ed43d2014ac497774 1.73 kB 6 minutes containerd.io/uncompressed=sha256:b5a8df342567aa93d568b263b25c1eaf52655f0952e1911742ffb4f7a521e044
  8. sha256:66960bede47c1a193710cf8bfa7bf5f50bc46374260923df1db1c423b52153ac 1.418 MB 6 minutes containerd.io/uncompressed=sha256:c03c7e9701eb61f1e2232f6d19faa699cd9d346207aaf4f50d84b1e37bbad3e2
  9. sha256:79dc0b596c9027416a627a6237bd080ac9d87f92b60f1ce145c566632839bce7 7.345 MB 6 minutes containerd.io/uncompressed=sha256:367024e4e00618a9ada3203b5922d3186a0aa6136a1c4cbf5ed380171e1afe48
  10. sha256:de36df38e0b6c0e7f29913c68884a0323207c07cd7c1eba71d5618f525ac2ba6 99 B 6 minutes containerd.io/uncompressed=sha256:60ef3ee42de712ef7748cc8e92192e926180b1be6fec9580933f1347fb6b2747
  11. sha256: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 值

你可以这样做获取同样的值

  1. $ cat <file> | gunzip - | sha256sum -

例如:

  1. $ cat /var/lib/containerd/io.containerd.content.v1.content/blobs/sha256/602cd484ff92015489f7b9cf9cbd77ac392997374b1cc42937773f5bac1ff43b | gunzip - | sha256sum -
  2. 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

流程就像下面这样:

  1. snapshotter 从 parent 创建 snapshot。在第一层的时候,是空白的,现在这是一个 “active” 的 snapshot