本文内容节选自 《containerd 原理剖析与实战》,本书正参加限时优惠内购,点击阅读原文,限时 69.9 元购买。
上一篇文章《一文了解 containerd 中的 snapshot》****中,介绍了 containerd 的 snapshot 机制,了解到 containerd 通过内置的 snapshotter 比如aufs、btrfs、devmapper、native、overlayfs、zfs 等,来完成 snapshot 生命周期的管理。
接下来我们从最简单的 native snapshotter 开始,带领大家了解 snapshotter 的实现。
native snapshotter
native snapshotter 是 containerd 中最早实现的 snapshotter,native snapshotter 使用的是原生的文件系统保存 snapshot,假如一个镜像有四层 layer,每层镜像 layer 有 10 MB 的未压缩文件,那么 snapshotter 将会创建四个 snapshot,分别是 10MB、20MB、30MB、40MB,总共有 100MB大小。
换句话说,我们的镜像有 40MB,却占用了 100MB 的存储空间,存储效率确实有点低。不过对于其他 snapshotter (如 overlay,devmapper 等)来说,将会通过使用不同的策略来消除这种存储效率低下的问题。
下面通过一个镜像示例介绍 native snapshotter 原理,首先基于下面的 Dockerfile 构建一个镜像,代码如下。
# alpine image 占用存储空间比较小FROM alpine:latest# 每层分别创建 10MB 大小的文件RUN dd if=/dev/zero of=file_a bs=1024 count=10240RUN dd if=/dev/zero of=file_b bs=1024 count=10240RUN dd if=/dev/zero of=file_c bs=1024 count=10240
复制代码
基于 nerdctl 构建镜像,代码如下。
[root@zjz ~]# nerdctl build -t zhaojizhuang66/snapshots-test .
复制代码
推送镜像,代码如下。
[root@zjz ~]# nerdctl push zhaojizhuang66/snapshots-test
复制代码
通过 nerdctl 指定 native snapshotter 拉取镜像,代码如下。
[root@zjz ~/containerd]# nerdctl --snapshotter native pull zhaojizhuang66/testsnapshotter
复制代码
进入 native snapshots 对应的路径查看,代码如下。
[root@zjz ~/containerd]# cd /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots[root@zjz ~/containerd]# ls 1 2 3 4
复制代码
总共有 4 个 snapshot,查看每个 snapshot 的大小,可以看到每个 snapshot 的大小依次增加 10MB 左右。
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 1 |head -n 1total 68K# 第 2 个 snapshots 为 alpine + 10MB[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 2 |head -n 1total 11M# 第 3 个 snapshots 为 alpine + 10MB + 10MB[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 3 |head -n 1total 21M# 第 4 个 snapshots 为 alpine + 10MB + 10MB + 10MB[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 4 |head -n 1total 31M
复制代码
接下来查看每个 snapshot 中的内容。
第 1 个 snapshot,代码如下。
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ll 1total 76drwxr-xr-x 19 root root 4096 Mar 7 14:56 .drwx------ 6 root root 4096 Mar 7 14:56 ..drwxr-xr-x 2 root root 4096 Feb 11 00:45 bindrwxr-xr-x 2 root root 4096 Feb 11 00:45 devdrwxr-xr-x 17 root root 4096 Feb 11 00:45 etc... 省略 ...
复制代码
第 2 个 snapshot,代码如下。
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ll 2total 10316drwxr-xr-x 19 root root 4096 Mar 7 14:56 .drwx------ 6 root root 4096 Mar 7 14:56 ..drwxr-xr-x 2 root root 4096 Mar 7 14:56 bindrwxr-xr-x 2 root root 4096 Feb 11 00:45 devdrwxr-xr-x 17 root root 4096 Mar 7 14:56 etc-rw-r--r-- 1 root root 10485760 Mar 7 14:47 file_a... 省略 ...
复制代码
第 3 个 snapshot,代码如下。
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ll 3total 20556drwxr-xr-x 19 root root 4096 Mar 7 14:56 .drwx------ 6 root root 4096 Mar 7 14:56 ..drwxr-xr-x 2 root root 4096 Mar 7 14:56 bindrwxr-xr-x 2 root root 4096 Feb 11 00:45 devdrwxr-xr-x 17 root root 4096 Mar 7 14:56 etc-rw-r--r-- 1 root root 10485760 Mar 7 14:47 file_a-rw-r--r-- 1 root root 10485760 Mar 7 14:47 file_b... 省略 ...
复制代码
第 4 个 snapshot,代码如下。
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ll 4total 30796drwxr-xr-x 19 root root 4096 Mar 7 14:56 .drwx------ 6 root root 4096 Mar 7 14:56 ..drwxr-xr-x 2 root root 4096 Mar 7 14:56 bindrwxr-xr-x 2 root root 4096 Feb 11 00:45 devdrwxr-xr-x 17 root root 4096 Mar 7 14:56 etc-rw-r--r-- 1 root root 10485760 Mar 7 14:47 file_a-rw-r--r-- 1 root root 10485760 Mar 7 14:47 file_b-rw-r--r-- 1 root root 10485760 Mar 7 14:47 file_c... 省略 ...
复制代码
以上就是 naitve snapshotter 准备容器 rootfs 的过程。可以看到, 对于 native snapshotter 来说,多层 snapshotter 对于镜像存储来说又有些浪费的,总共 30MB 的镜像,经过 native snapshotter 解压之后,总共占用了 60MB 的存储空间。
下面看 native snapshotter 的源码可以具体实现,代码如下。
// 版本 v1.7.0// containerd/snapshots/native/native.gofunc (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { return o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts)}
func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ []mount.Mount, err error) {
// 1. 获取 parent snapshot 的目录 parent := o.getSnapshotDir(s.ParentIDs[0])
// 2. 直接 copy parent snapshot 目录中的内容到新的 snapshot 目录 s.CopyDir(dst-snapshot-path, parent, ...); // 3. 返回的挂载信息为 return []mount.Mount{ { Source: dst-snapshot-path, Type: "bind", Options: []string{"rbind","ro"}, }, }}
复制代码
查看 snapshot 对应的挂载信息,代码如下。
# 启动容器,创建 active 状态的 snapshotroot@zjz:~# ctr run --snapshotter native -d docker.io/zhaojizhuang66/testsnapshotter:latest zjz
复制代码
# 看到多了一层 名为 zjz 的 active 的 snapshotroot@zjz:~# ctr snapshot --snapshotter native lsKEY PARENT KINDsha256:7cd52847ad775a5ddc4b58326cf884beee34544296402c6292ed76474c686d39 Committedsha256:db7e45c34c1fd60255055131918550259be8d7a83e0ac953df15d9410dc07b07 sha256:7cd52847ad775a5ddc4b58326cf884beee34544296402c6292ed76474c686d39 Committedsha256:a937f098cfdf05ea5f262cbba031de305649a102fabc47014d2b062428573d42 sha256:db7e45c34c1fd60255055131918550259be8d7a83e0ac953df15d9410dc07b07 Committedsha256:77297b225cd30d2ace7f5591a7e9208263428b291fd44aac95af92f7337b342a sha256:a937f098cfdf05ea5f262cbba031de305649a102fabc47014d2b062428573d42 Committedzjz sha256:77297b225cd30d2ace7f5591a7e9208263428b291fd44aac95af92f7337b342a Active
复制代码
# 查看该 snapshot 的挂载信息root@zjz:~# ctr snapshot --snapshotter native mount /tmp zjzmount -t bind /data00/lib/containerd/io.containerd.snapshotter.v1.native/snapshots/20 /tmp -o rbind,rw
复制代码
可以看到 native snapshotter 只是通过简单的 Copy 调用,将父 snapshot 中的内容拷贝到子 snapshot 中。
native snapshotter 对于相同的内容进行了多重保存,还是有些浪费的,那么有没有其他更高效的存储方式呢?答案是肯定的。
_同时也欢迎同学们留言,__可以通过利用哪些技术,能够有效解决镜像层重复占用存储空间的问题。_后续的文章将继续介绍社区是怎么进行高效存储的。
以上内容节选自新书 《containerd 原理剖析与实战》
最后,附上本书的购买链接,新书刚刚上架原价 109,限时优惠内购 69.9 元,感兴趣的朋友可以尽快入手。内购链接点击左下角**“阅读原文**”,或者长按下面的二维码进行购买
本文使用 文章同步助手 同步
评论