写点什么

《containerd 系列》一文了解 containerd 中的 snapshot

  • 2024-04-26
    浙江
  • 本文字数:9761 字

    阅读完需:约 32 分钟

本文内容节选自 《containerd 原理剖析与实战》

1. containerd 中的镜像存储

一个正常的镜像从制作出来到通过容器运行时启动大概会经历如下几个步骤,如下图所示。


  1. 基于 Dockerfile 制作镜像。

  2. 推送到镜像 Registry 中,如 dockerhub 或者自建的 harbor 仓库。

  3. 从镜像 Registry 拉取到本地。

  4. 本地容器运行时管理镜像的存储,并将镜像转化为容器运行所需的 rootfs。

  5. 交付 rootfs 给容器时在启动容器前进行挂载。



<p align=center>图 镜像从制作到启动容器前的流程</p>


在上述的镜像流转过程中,containerd 参与的主要是步骤 3 、4,即拉取镜像、解压镜像、将镜像准备为容器 rootfs ,并提供 rootfs 挂载信息供后续运行容器使用,如下图所示。



<p align=center>图 containerd 拉取镜像到准备容器 rootfs </p>


containerd 中涉及镜像、容器 rootfs 持久化的主要模块主要是 ContentMetadataSnapshot。其中 metada 主要用于存储元数据,containerd 会将镜像 manifest 和镜像 layer 拉取并保存到 content 目录。


注意存储在 content 中的镜像 layer 是不可以变的,其存储格式也是没法直接使用的,常见的格式是 tar+gziptar+gzip 是没法直接挂载给容器使用。

2. containerd 中的 snapshot

因此为了使用 content 中存储的镜像层,containerd 抽象出了 snapshot (快照) 概念。


每个镜像层生成对应的一个 snapshot,同时 snapshot 有父子关系。子 snapshot 会继承 父 snapshot 中文件系统的内容,即叠加在父 snapshot 内容之上进行读写操作。


snapshot 代表的是文件系统的状态,snapshot 的生命周期中共有三种类型,committedactiveview


  1. committed: committed 状态的 snapshot 通常是由 active 状态的 snapshot 通过 Commmit 操作之后产生的。committed 状态的 snapshot 不可变。

  2. active: active 状态的 snapshot 通常是由 committed 状态的 snapshot 通过 Prepare 或 View 操作之后产生的。不同于 committed 状态,active 状态的 snapshot 是可以进行读写修改等操作的。对 active 状态的 snapshot 进行 Commit 操作会产生 committed 状态的 新 snapshot,同时会继承该 snapshot 的 parrent。

  3. view:view 状态的 snapshot 是父 snapshot 的只读视图,挂载后是不可被修改的。

2.1 snapshot 生命周期

snapshot 的生命周期如下图所示。



<p align=center>图  snapshot 生命周期</p>


从上图可以看到:


  1. 状态为 Committed 的 snapshot A0,经过 Prepare 调用后生成了 Active 状态的 snapshot a。

  2. Acitve 状态的 snapshot a 是可读写的,可以挂载到指定目录进行操作,snapshot 中的文件系统经过修改后变为 a' (并没有生成新的 snapshot a',只是相比于初始 snapshot a 发生了变化,暂且称为 a')。

  3. a' 经过 Commit 操作后,生成 Committed 状态的 snapshot A1,以 a 为名的 snapshot 则会被删除 (Remove)。A0 是 A1 的父 snapshot。

  4. Committed snapshot A0,还可以经过 View 调用后生成 view 状态的 snapshot b,snapshot b 是只读的,挂载后的文件系统不可被修改。

2.2 snapshot 是如何存储的

还是以 redis:5.0.9 为例,介绍 snapshot 是如何存储的,在 /var/lib/containerd 目录中可以看到多个 io.containerd.snapshotter.v1.<type> 命名的文件夹:


root@zjz:/var/lib/containerd# lldrwx------ 2 root root 4096 Sep 29 20:28 io.containerd.snapshotter.v1.btrfsdrwx------ 3 root root 4096 Sep 29 20:28 io.containerd.snapshotter.v1.nativedrwx------ 3 root root 4096 Nov 28 16:16 io.containerd.snapshotter.v1.overlayfs.. ...
复制代码


在 containerd 中,snapshot 的管理是由 snapshotter 来做的,containerd 中支持多种 snapshotter 插件。如 containerd 默认支持的 overlay snapshotter,它管理的 snapshotter 就保存在 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs 中。不同于 content 目录,snapshot 目录中的内容不是以 sha256 命名的,而是从 1 开始的 index 命名的:


root@zjz:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots# lltotal 176drwx------ 4 root root 4096 Nov 28 16:16 1drwx------ 4 root root 4096 Nov 28 16:17 2drwx------ 4 root root 4096 Nov 28 16:17 3drwx------ 4 root root 4096 Nov 28 16:17 4drwx------ 4 root root 4096 Nov 28 16:17 5drwx------ 4 root root 4096 Nov 28 16:17 6... ...
复制代码


snapshot 可以通过 ctr snapshot ls 查看,可以看到 snapshot 之间的 parent 关系,第一层 snapshot 的 parent 为空。


root@zjz:~# ctr snapshot lsKEY                                                                     PARENT                                                                  KINDsha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 Committedsha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 sha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 Committedsha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 sha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca Committedsha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca sha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 Committedsha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c Committedsha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c                                                                         Committed
复制代码


注意,snapshot key 中 sha256 的值并不是镜像 layer content 解压之后的 sha256,而是每一层镜像 layer content 解压后再叠加 parent snapshot 中的内容,重新计算得到的 sha256 的值。如下图所示。



图 snapshot sha256 计算方法


对于第一层 snapshot 而言(parent 为空的那层)和镜像 layer content 解压之后的 sha 256 一致(其实是上述镜像 config 文件中的 diff_id): d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c  启动 redis 容器,可以看到多了一层 active 状态的 snapshot,这层 active 的 snapshot 就是对应容器的读写层。


# 通过 ctr 启动 redis 容器root@zjz:~# ctr run -d  docker.io/library/redis:5.0.9 redis-democ8d01e7d5537962fdc455a10723b7dcc9b7c9572539b799eb2604acdf3421b17# 查看 snapshotroot@zjz:~# ctr snapshot lsKEY                                                                     PARENT                                                                  KINDredis-demo                                                              sha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd Activesha256:33bd296ab7f37bdacff0cb4a5eb671bcb3a141887553ec4157b1e64d6641c1cd sha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 Committedsha256:bc8b010e53c5f20023bd549d082c74ef8bfc237dc9bbccea2e0552e52bc5fcb1 sha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 Committedsha256:aa4b58e6ece416031ce00869c5bf4b11da800a397e250de47ae398aea2782294 sha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca Committedsha256:a8f09c4919857128b1466cc26381de0f9d39a94171534f63859a662d50c396ca sha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 Committedsha256:2ae5fa95c0fce5ef33fbb87a7e2f49f2a56064566a37a83b97d3f668c10b43d6 sha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c Committedsha256:d0fe97fa8b8cefdffcef1d62b65aba51a6c87b6679628a2b50fc6a7a579f764c                                                                         Committed
复制代码


镜像的每一层都会被创建成 committed 状态的 snapshot,committed 表示该镜像层不可变,在启动容器时,将为每个容器创建一个可读写的 active snapshot,这一层是可读写的。下图是镜像 layer 与 snapshot 的对应关系。



<p align=center>图 镜像 layer 与 snapshot </p>


接下来介绍 snapshot 的管理工具 snapshotter

3. graphdriver 与 snapshotter

在 docker 中一直使用 graphdriver 中来管理镜像的存储,但在 graphdriver 设计使用以来,引发了很多问题。于是在 containerd 从 docker 中贡献出来后,原有的 graphdriver 便重新设计,变为 snapshotter。

3.1 graphdriver 的历史

最早的 Docker 只支持 Ubuntu, 因为 Ubuntu 是唯一搭载了 Aufs 的发行版,Docker 使用 aufs 作为 镜像容器 rootfs 的 unionfs 文件系统格式,此时为了让 Docker 在老版本的内核中运行,便需要 Docker 支持持除了 aufs 之外的其他文件系统,如支持了基于 **LVM 精简卷 (Thin provisioned)**的 device mapper 。


device mapper 的出现让 Docker 在所有的内核和发行版上运行成为了可能。为了让更多的 Linux 发行版用上 Docker,Docker 创始人所罗门(solomon)设计了一个新的 API 来支持 Docker 中的多个文件系统,这个新的 API 就是 graph driver。起初 graph driver 接口非常简单,但随着时间推移,加入了越来越多的特性:


  • 构建优化,基于构建缓存加速构建过程

  • 内容可寻址。

  • 运行时由 LXC 变为 runc 。


随着这些特性的加入,graph driver 也变得越来越臃肿:


  • graph driver API 变得越来越复杂。

  • driver 中都有内置的构建优化代码。

  • driver 与容器的生命周期紧密耦合。  因此,containerd 的开发者决定重新重构 graph driver 来解决其过于复杂的问题,毕竟 containerd 作为一个新生儿并没有历史包袱。

3.2  snapshotter 的诞生

在 Docker 中,容器中使用的文件系统有两类:  覆盖文件系统 (overlay) 和 快照文件系统 (snapshot) 。AUFSOverlayFS 都是 overlay 文件系统,每一层镜像 layer 对应一个目录,通过目录为镜像中的每一层提供文件差异。snapshot 类型文件系统则包括 devicemapperbtrfszfs,快照文件系统在块级别处理文件差异。overlay 需要依赖于 ext4xfs 等现有的文件系统上,而 snapshot 文件系统只能运行在格式化好的卷上。


snapshot 文件系统相比于 overlay 文件系统而言,灵活性稍差,因为 snapshot 需要有严格的父子关系。创建子快照时必须要有一个父快照。而通常在接口设计时,优先寻找最不灵活的实现来创建接口。因此 containerd 中对接不同文件系统的 API 定义为 snapshotter


相比于 graph driver,snapshotter 并不负责 rootfs 挂载和卸载动作的实现,这样做有几个好处:


  • 调用者作为镜像构建组件或容器执行组件,可以决定何时需要挂载 rootfs;何时执行结束,以便进行卸载。

  • 在一个容器的 mount 命名空间中挂载,当容器死亡时,内核将卸载该命名空间中的所有挂载。这改善了一些 graph driver 陈旧文件句柄的问题。


snapshotter 返回的 rootfs 的挂载信息 (如 rootfs 的 path,类型等),由 containerd 决定在 containerd-shim 中挂载容器的 rootfs,并在任务执行后进行卸载。containerd 与 snapshotter 交互的逻辑如下图所示。



<p align=center>图 containerd 与 snapshotter 交互 </p>


snapshotter 是 graphdriver 的演进,以一种更加松耦合的方式提供 containerd 中容器和镜像存储的实现,同时 containerd 中也提供了 out of tree 形式的 snapshotter 插件扩展机制:proxy snapshotter, 通过 grpc 的形式对接用户自定义的 snapshotter

3.3 snapshotter 概述

通过上面的介绍我们了解了 Docker 中 graph driver 的来源以及 containerd 中 snapshotter 的创建历史,了解到 snapshotter 是 containerd 中用来准备 rootfs 挂载信息的组件。接下来就详细介绍 snapshotter 组件。


在 containerd 整体架构中,containerd 设计上为了解耦,划分成了不同的组件(Core 层的 ServiceMetadataBackend 层的 Plugin),每个组件都以插件的形式集成到 containerd 中,每种组件都由一个或多个模块之间协作完成各自的功能。containerd 架构图如下图所示。



<p align=center>图 containerd 架构图[ 图片来源https://containerd.io/] </p>


在本节中我们主要关注 Snapshots servicesnapshotter plugin 模块。

3.4 snapshotter 接口

snapshotter 的主要工作是为 containerd 运行容器准备 rootfs 文件系统。通过将镜像 layer 逐层依次解压挂载到指定目录,最终提供给 containerd 启动容器时使用。  为了管理 snapshot 的生命周期,所有的 snapshotter 都会实现以下 10 个接口:


type Snapshotter interface { Stat(ctx context.Context, key string) (Info, error) Update(ctx context.Context, info Info, fieldpaths ...string) (Info, error) Usage(ctx context.Context, key string) (Usage, error) Mounts(ctx context.Context, key string) ([]mount.Mount, error)    Prepare(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error) View(ctx context.Context, key, parent string, opts ...Opt) ([]mount.Mount, error) Commit(ctx context.Context, name, key string, opts ...Opt) error Remove(ctx context.Context, key string) error Walk(ctx context.Context, fn WalkFunc, filters ...string) error Close() error}type Cleaner interface {   Cleanup(ctx context.Context) error}
复制代码


snapshotter 接口的详细说明如下表所示。


表 snapshotter 接口的详细说明


3.5 snapshotter 准备容器 rootfs 过程

snapshotter 准备容器 rootfs 的过程中,比较关键的几个方法是 PrepareCommit 方法,接下来以具体的例子进行介绍。


snapshotter 准备目录是根据镜像 layer 一层一层准备的。例如第 1 层直接解压到指定目录作为第 1 层 snapshot;准备第 2 层镜像 layer 时,在第 1 层 snapshot 的文件系统内容之上再解压第 2 层 镜像 layer 作为第 2 层 snapshot,以此类推在准备第 n 层 snapshot 时,是在 n-1 层 snapshot 基础上解压第 n 层镜像 layer 进行实现的。


在准备 snapshot 时先通过 Prepare 创建可读写的 Active snapshot,将该 snapshot 挂载后,解压镜像到 snapshot 中,而后将该 snapshot 提交为只读的 Committed snapshot。如下图所示。



<p align=center>图  snapshotter 准备容器 rootfs 的过程</p>


我们知道,镜像是有多层只读层 layer 组成,容器 rootfs 则是由镜像只读层 layer 加一层读写层来实现的。snapshotter 的实现机制与镜像 layer 一一对应。


上图中镜像为一个 3 层 layer 的镜像,snapshotter 在准备该镜像的 snapshots 时:


  1. 首先通过 Prepare 创建一个 Active 状态的 snapshot 1',该调用返回一个空目录(以 parent 为 "")的挂载信息,挂载后为可读写的文件夹。

  2. 将第一层镜像 layer (layer0) 解压到该文件夹中,调用 Commit 生成 Committed 状态的 snapshot1,snapshot1 的 父 snapshot 为空,随后 Remove snapshot 1'。此时对 snapshot1 调用 MountPrepareView 操作返回的挂载信息挂载后,其中的文件系统内容为镜像 layer 0 解压后的内容。

  3. 通过 Prepare 以 snapshot1 为父 snapshot,创建一个 Active 状态的 snapshot 2',将 snapshot 2' 挂载后,目录中会含有第一层镜像 layer 的内容。此时将第二层镜像 layer (layer1)解压到挂载 snapshot 2' 的目录中,再次调用 Commit 生成 Committed 状态的 snapshot2。此时对 snapshot2 调用 MountPrepareView 操作返回的挂载信息挂载后,其中的文件系统内容为镜像 layer 0、镜像 layer1 依次解压后的内容。

  4. 以此类推,通过 Prepare 以 以 snapshot2 为父 snapshot,创建一个 Active 状态的 snapshot 3',再将第三层镜像 layer (layer2)解压到其中,最终生成为 Committed 状态的 snapshot3。注意,snapshot1、snapshot2、snapshot3 均是只读的,不可变的。此时对 snapshot2 调用 MountPrepareView 操作返回的挂载信息挂载后,其中的文件系统内容为镜像 layer 0、镜像 layer1 、镜像 layer2 依次解压后的内容。

  5. 在启动容器时,调用 snapshotterPrepare 接口以 snapshot3 为父 snapshot,创建一个 Active 状态的 snapshot4。此时将 Prepare 调用返回的挂载信息挂载后即是容器的 rootfs 目录。rootfs 目录中含有的内容是镜像 layer 0、1、2 依次叠加之后的内容,同时由于该 snapshot 是 Active 状态,目录是可读写的。

关于 Mount

Prepare 返回的挂载信息为 Mount 结构体,用于 Linux 挂载调用的参数,如:


mount -t <type> -o <options > <device> <mount-point>
复制代码


type Mount struct {    // 该挂载指定的文件系统类型    Type string    // 挂载的源,依赖于宿主机操作系统,可以是文件夹,也可以是一个块设备。    Source string    // Mount option,同 mount -o 指定的参数,如 ro,rw 等    Options []string    // 注意:该字段为 containerd 1.7.0 版本之后添加的 // Target 指定了一个可选的子目录作为挂载点,前提是假定父挂载中该挂载点是存在的。    Target string}
复制代码


【注意】Target 结构体是 containerd 1.7.0 之后添加的,是为了实现某个 snapshotter,具体的 issue 可以参考 Github 上的 issue[ https://github.com/containerd/containerd/issues/7839]。


可以通过 ctr snapshots mounts <target> <key> 查看某个 snapshot 对应的挂载信息,代码如下。


# native snapshot 对应的挂载信息root@zjz:~# ctr snapshot --snapshotter native mounts /tmp zjzmount -t bind /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots/19 /tmp -o rbind,rw
复制代码


# overlay snapshot 对应的挂载信息root@zjz:~# ctr snapshot mounts /tmp redis-demomount -t overlay overlay /tmp -o index=off,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/246/work,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/246/fs,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/239/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/238/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/237/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/236/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/235/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/234/fs
复制代码

4. containerd 中的 snapshotter

containerd 支持的 snapshotter 可以通过 ctr plugin ls 查看。代码如下。


root@zjz:~/zjz/container-book# ctr plugin ls |grep snapshotterio.containerd.snapshotter.v1          aufs                     linux/amd64    skipio.containerd.snapshotter.v1          btrfs                    linux/amd64    skipio.containerd.snapshotter.v1          devmapper                linux/amd64    errorio.containerd.snapshotter.v1          native                   linux/amd64    okio.containerd.snapshotter.v1          overlayfs                linux/amd64    okio.containerd.snapshotter.v1          zfs                      linux/amd64    skip
复制代码


可以发现 containerd 内置的 snapshotter 有 aufsbtrfsdevmappernativeoverlayfszfs 几种。其中 containerd 默认支持的 overlay snapshotter。同时 containerd 也支持通过自定义实现 snapshotter 插件来支持,不必重新编译 containerd。


containerd 默认支持的 snapshotteroverlay,等同于 Docker 中的 overlay2 gradriver 驱动,如何指定特定的 snapshotter 呢,接下来分别通过以下几种途径进行介绍。


  1. nerdctl

  2. ctr

  3. CRI Plugin

  4. containerd Client SDK

1. nerdctl

通过 nerdctl 拉取或推送镜像时通过指定 --snapshotter 来指定特定的 snapshotter。  指定 snapshotter 拉取镜像采用下面的命令:


nerdctl --snapshotter <snapshotter plugin name> pull <image name>
复制代码


指定 snapshotter 启动容器,采用下面的命令:


nerdctl --snapshotter <snapshotter plugin name> run <image name>
复制代码

2 ctr

指定 snapshotter 拉取镜像,代码如下。


ctr i pull --snapshotter <snapshotter plugin name>  <image name>
复制代码


指定 snapshotter 启动容器,代码如下。


ctr run -d -t --snapshotter <snapshotter plugin name>  <image name> <container name>
复制代码


还是以 redis:5.0.9 为例,以 native snapshotter plugin 拉取镜像和启动容器,代码如下。


root@zjz:~# ctr i pull --snapshotter native docker.io/library/redis:5.0.9root@zjz:~# ctr run -d -t --snapshotter native docker.io/library/redis:5.0.9 redis-test
复制代码


指定 snapshotter 操作 snapshot,代码如下。


ctr snapshot --snapshotter <snapshotter plugin name> <command>
复制代码


ctr snapshot 支持的操作如下。


root@zjz:~# ctr snapshot -hNAME:   ctr snapshots - manage snapshots

USAGE: ctr snapshots command [command options] [arguments...]

COMMANDS: commit commit an active snapshot into the provided name diff get the diff of two snapshots. the default second snapshot is the first snapshot's parent. info get info about a snapshot list, ls list snapshots mounts, m, mount mount gets mount commands for the snapshots prepare prepare a snapshot from a committed snapshot delete, del, remove, rm remove snapshots label add labels to content tree display tree view of snapshot branches unpack unpack applies layers from a manifest to a snapshot usage usage snapshots view create a read-only snapshot from a committed snapshot

OPTIONS: --snapshotter value snapshotter name. Empty value stands for the default value. [$CONTAINERD_SNAPSHOTTER] --help, -h show help
复制代码

3. CRI Plugin

对接 Kubernetes 的场景下,可以通过 containerd config 文件(/etc/containerd/config)进行配置,如下。


version = 2[plugins."io.containerd.grpc.v1.cri".containerd]  snapshotter = <snapshotter plugin name>
复制代码

4. containerd Client SDK

拉取镜像和启动容器时通过指定 option 函数选择指定的 snapshotter,代码如下。


# 初始化 Clientclient, err := containerd.New("/run/containerd/containerd.sock")

... ... # 拉取镜像image, err := client.Pull(ctx, ref, containerd.WithPullUnpack, containerd.WithPullSnapshotter("my-snapshotter"),)... ...# 启动容器container, err := client.NewContainer( ctx, "redis-test", containerd.WithNewSnapshot("snapshot-id", image), containerd.WithSnapshotter("my-snapshotter"), containerd.WithNewSpec(oci.WithImageConfig(image)),
复制代码


上面就是 containerd 中 snapshotsnapshotter 的介绍。


其中 containerd 中内置的 snapshotteraufsbtrfsdevmappernativeoverlayfszfs


针对每种 snapshotter的功能,后续将继续通过本公众号的文章依次展开介绍。


以上内容节选自新书 《containerd 原理剖析与实战》



本文使用 文章同步助手 同步

发布于: 14 分钟前阅读数: 10
用户头像

just do it 2018-09-25 加入

赵吉壮,《containerd 原理剖析与实战》作者,曾就职于华为 Cloud BU,字节跳动 Data 团队,专注于 k8s, Serverless, Go 云原生

评论

发布
暂无评论
《containerd 系列》一文了解 containerd 中的 snapshot_Kubernetes_公众号:云原生Serverless_InfoQ写作社区