『containerd 系列』了解 containerd 中的 snapshotter,先从 native 开始

本文内容节选自 《containerd 原理剖析与实战》,本书正参加限时优惠内购,点击阅读原文,限时 69.9 元购买

上一篇文章《一文了解 containerd 中的 snapshot》中,介绍了containerd 的 snapshot 机制,了解到 containerd 通过内置的 snapshotter 比如aufsbtrfsdevmappernativeoverlayfszfs 等,来完成 snapshot 生命周期的管理。

接下来我们从最简单的 native snapshotter 开始,带领大家了解 snapshotter 的实现。

native snapshotter

native snapshotter 是 containerd 中最早实现的 snapshotter,native snapshotter 使用的是原生的文件系统保存 snapshot,假如一个镜像有四层 layer,每层镜像 layer 有 10 MB 的未压缩文件,那么 snapshotter 将会创建四个 snapshot,分别是 10MB20MB30MB40MB,总共有 100MB大小。

换句话说,我们的镜像有 40MB,却占用了 100MB 的存储空间,存储效率确实有点低。不过对于其他 snapshotter (如 overlaydevmapper 等)来说,将会通过使用不同的策略来消除这种存储效率低下的问题。

下面通过一个镜像示例介绍 native snapshotter 原理,首先基于下面的 Dockerfile 构建一个镜像,代码如下。

# alpine image 占用存储空间比较小
FROM alpine:latest
# 每层分别创建 10MB 大小的文件
RUN dd if=/dev/zero of=file_a bs=1024 count=10240
RUN dd if=/dev/zero of=file_b bs=1024 count=10240
RUN 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 1
total 68K
# 第 2 个 snapshots 为 alpine + 10MB
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 2 |head -n 1
total 11M
# 第 3 个 snapshots 为 alpine + 10MB + 10MB
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 3 |head -n 1
total 21M
# 第 4 个 snapshots 为 alpine + 10MB + 10MB + 10MB
[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ls -lh 4 |head -n 1
total 31M

接下来查看每个 snapshot 中的内容。

第 1 个 snapshot,代码如下。

[root@zjz /var/lib/containerd/io.containerd.snapshotter.v1.native/snapshots]# ll 1
total 76
drwxr-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 bin
drwxr-xr-x  2 root root 4096 Feb 11 00:45 dev
drwxr-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 2
total 10316
drwxr-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 bin
drwxr-xr-x  2 root root     4096 Feb 11 00:45 dev
drwxr-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 3
total 20556
drwxr-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 bin
drwxr-xr-x  2 root root     4096 Feb 11 00:45 dev
drwxr-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 4
total 30796
drwxr-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 bin
drwxr-xr-x  2 root root     4096 Feb 11 00:45 dev
drwxr-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.go
func (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 状态的 snapshot
root@zjz:~# ctr run --snapshotter native -d docker.io/zhaojizhuang66/testsnapshotter:latest zjz
# 看到多了一层 名为 zjz 的 active 的 snapshot
root@zjz:~# ctr snapshot --snapshotter native ls
KEY                                                                     PARENT                                                                  KIND
sha256:7cd52847ad775a5ddc4b58326cf884beee34544296402c6292ed76474c686d39                                                                         Committed
sha256:db7e45c34c1fd60255055131918550259be8d7a83e0ac953df15d9410dc07b07 sha256:7cd52847ad775a5ddc4b58326cf884beee34544296402c6292ed76474c686d39 Committed
sha256:a937f098cfdf05ea5f262cbba031de305649a102fabc47014d2b062428573d42 sha256:db7e45c34c1fd60255055131918550259be8d7a83e0ac953df15d9410dc07b07 Committed
sha256:77297b225cd30d2ace7f5591a7e9208263428b291fd44aac95af92f7337b342a sha256:a937f098cfdf05ea5f262cbba031de305649a102fabc47014d2b062428573d42 Committed
zjz                                                                     sha256:77297b225cd30d2ace7f5591a7e9208263428b291fd44aac95af92f7337b342a Active

# 查看该 snapshot 的挂载信息
root@zjz:~# ctr snapshot --snapshotter native mount /tmp zjz
mount -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 元

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值