掘金 后端 ( ) • 2024-04-26 14:32

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

1. containerd 中的镜像存储

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

  1. 基于 Dockerfile 制作镜像。

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

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

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

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

1.jpg

_图 镜像从制作到启动容器前的流程_

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

2.jpg

_图 containerd 拉取镜像到准备容器 rootfs_

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 的生命周期如下图所示。

3.jpg

_图  snapshot 生命周期_

从上图可以看到:

  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# ll
drwx------ 2 root root 4096 Sep 29 20:28 io.containerd.snapshotter.v1.btrfs
drwx------ 3 root root 4096 Sep 29 20:28 io.containerd.snapshotter.v1.native
drwx------ 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# ll
total 176
drwx------ 4 root root 4096 Nov 28 16:16 1
drwx------ 4 root root 4096 Nov 28 16:17 2
drwx------ 4 root root 4096 Nov 28 16:17 3
drwx------ 4 root root 4096 Nov 28 16:17 4
drwx------ 4 root root 4096 Nov 28 16:17 5
drwx------ 4 root root 4096 Nov 28 16:17 6
... ...

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

root@zjz:~# 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

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

4.jpg 图 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-demo
c8d01e7d5537962fdc455a10723b7dcc9b7c9572539b799eb2604acdf3421b17
# 查看 snapshot
root@zjz:~# ctr snapshot ls
KEY                                                                     PARENT                                                                  KIND
redis-demo                                                              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

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

5.jpg

_图 镜像 layer 与snapshot_

接下来介绍 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 交互的逻辑如下图所示。

6.jpg

_图 containerd 与 snapshotter 交互_

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 架构图如下图所示。

7.jpg

_图 containerd 架构图\[ 图片来源https://containerd.io/\]_

在本节中我们主要关注 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 接口的详细说明

接口 说明 Stat Stat 接口会返回 active 或者 committed 状态的快照信息。可以用来判断快照父子关系,快照是否存在,以及快照的类型。 Update Update 接口更新快照的 info 信息。快照中只有允许更改的属性才可以被更新。 Usage Usage 接口返回 active 状态或 committed 状态的快照的资源使用量信息,但不包括父快照的资源使用情况。调用者要注意:该接口的调用时长取决于具体实现,不过可能与占用的资源大小成正比。调用者注意避免使用锁机制,同时可以通过实现 context 的取消方法来避免超时。 Mounts Mounts 根据 key 返回的是 active 状态的快照对应的挂载信息(即 []mount.Mount)。可以被读写层或者只读层事务来调用。只有 active 状态的快照才能使用该方法。可用于在调用 View 或 Prepare 方法之后重新挂载。 Prepare Prepare 方法会基于父快照创建一个 active 状态的快照,该快照的标识由唯一 key 确定。返回的挂载信息可以用来挂载该快照,该快照作为活动快照,里面的文件内容是可以修改的。如果提供了父快照 id,在执行了挂载之后,挂载的目的路径中会包含父快照的内容。父快照必须是 committed 状态的快照。任何对于该挂载目的路径中的内容修改都会基于父快照的内容来记录 (记录基于父快照的 diff)。默认的父快照 "",对应的是空目录。对于该快照的修改可以通过调用 Commit 方法将快照保存为 committed 状态的快照。Commit(提交)事务结束后, 需要调用 Remove 方法删除以 key 为标识的该快照。对同一个 key 多次调用 Prepare 或者 View 方法会失败。 View View 方法会和 Prepare 方法相同,只不过 view 方法不会把对快照修改提交回快照。View 会返回一个基于父快照的只读视图, acitve 快照则由给定的 key 进行个跟踪。该方法的操作与 Prepare 相同,除了返回的挂载信息设置了只读标识。任何对于底层文件系统的修改都会被忽略。具体实现上可以与 Prepare 不同,具体实现的 snapshotter 可以以更高效的方式进行。不同于 Prepare,调用 View 之后再调用 Commit,会返回错误。要收集与 key 关联的资源,Remove 调用的时候必须以 key 为参数。 Commit 调用 Commit 方法会记录当前快照和父快照之间的变更,并将变更保存在名为的快照中.该 name 可以被 snapshotter 的其他方法在随后的快照操作中使用。该方法会创建一个以  为名的 committed 状态的快照,该快照的父快照是原先 Active 状态的快照的父快照。快照提交之后,以 key 为唯一键的快照会被删除 Remove 调用 Remove 可以删除 committed 和 active 状态的快照. 所有跟这个 key 相关的资源都会被删除。如果某个快照是其他快照的父快照,则必须先删除其他子快照才能删除该快照。 Walk Walk 方法会对满足筛选条件的快照,调用 WalkFunc。如果不提供筛选条件,所有的快照都会被调用 WalkFunc.筛选条件有: name; parent;kind(active,view,committed);labels.(label) Close Close 会释放内部的所有资源。最好是在 snapshotter 生命周期结束的时候调用 Close,该方法并不是强制的。如果已经关闭,则再次调用时该方法返回 nil。 Cleanup Cleanup 为异步资源清理机制,避免同步清理时耗费时间过长的问题

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。如下图所示。

8.jpg

_图  snapshotter 准备容器 rootfs 的过程_

我们知道,镜像是有多层只读层 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 zjz
mount -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-demo
mount -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 snapshotter
io.containerd.snapshotter.v1          aufs                     linux/amd64    skip
io.containerd.snapshotter.v1          btrfs                    linux/amd64    skip
io.containerd.snapshotter.v1          devmapper                linux/amd64    error
io.containerd.snapshotter.v1          native                   linux/amd64    ok
io.containerd.snapshotter.v1          overlayfs                linux/amd64    ok
io.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.9
root@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 -h
NAME:
   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,代码如下。

# 初始化 Client
client, 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 原理剖析与实战》

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