揭秘!containerd 镜像文件丢失问题,竟是镜像生成惹得祸

导语

作者李志宇,腾讯云后台开发工程师,日常负责集群节点和运行时相关的工作,熟悉 containerd、docker、runc 等运行时组件。近期在为某位客户提供技术支持过程中,遇到了 containerd 镜像丢失文件问题,经过一系列分析、推断、复现、排查,最终成功找到根因并给出解决方案。现将整个详细处理过程整理成文分享出来,希望能够为大家提供一个有价值的问题处理思路以及帮助大家更好地理解相关原理。

containerd 镜像丢失文件问题说明

近期有客户反映某些容器镜像出现了文件丢失的奇怪现象,经过模拟复现汇总出丢失情况如下:

某些特定的镜像会稳定丢失文件;

“丢失”在某些发行版稳定复现,但在 ubuntu 上不会出现;

v1.2 版本的 containerd 会文件丢失,而 v1.3 不会。

通过阅读源码和文档,最终解决了这个 containerd 镜像丢失问题,并写下了这篇文章,希望和大家分享下解决问题的经历和镜像生成的原理。为了方便某些心急的同学,本文接下来将首先揭晓该问题的答案~

根因和解决方案

由于内核 overlay 模块 Bug,当 containerd 从镜像仓库下载镜像的“压缩包”生成镜像的“层”时,overlay 错误地把trusted.overlay.opaque=y这个 xattrs 从下层传递到了上层。如果某个目录设置了这个属性,overlay 则会认为这个目录是不透明的,以至于在进行联合挂载时该目录将会把下面的目录覆盖掉,进而导致镜像文件丢失的问题。

这个问题的解决方案可以有两种,一种简单粗暴,直接升级内核中 overlay 模块即可。

另外一种可以考虑把 containerd 从 v1.2 版本升级到 v1.3,原因在于 containerd v1.3 中会主动设置上述 opaque 属性,该版本 containerd 不会触发 overlayfs 的 bug。当然,这种方式是规避而非彻底解决 Bug。

snapshotter 生成镜像原理分析

虽然根本原因看起来比较简单,但分析的过程还是比较曲折的。在分享下这个问题的排查过程和收获之前,为了方便大家理解,本小节将集中讲解问题排查过程涉及到的 containerd 和 overlayfs 的知识,比较了解或者不感兴趣的同学可以直接跳过。

与 docker daemon 一开始的设计不同,为了减少耦合性,containerd 通过插件的方式由多个模块组成。结合下图可以看出,其中与镜像相关的模块包含以下几种:

enter image description here

  • metadata 是 containerd 通过 bbolt 实现的 kv 存储模块,用来保存镜像、容器或者层等元信息。比如命令行 ctr 列出所有 snapshot 或 kubelet 获取所有 pod 都是通过 metadata 模块查询的数据。

  • content 是负责保存 blob 的模块,其保存的关于镜像的内容一般分为三种:

    1. 镜像的 manifest(一个普通的 json,其中指定了镜像的 config 和镜像的 layers 数组)
    2. 镜像的 config(同样是个 json,其中指定镜像的元信息,比如启动命令、环境变量等)
    3. 镜像的 layer(tar 包,解压、处理后会生成镜像的层)
  • snapshots 是快照模块总称,可以设置使用不同的快照模块,常见的模块有 overlayfs、aufs 或 native。在 unpack 时 snapshots 会把生成镜像层并保存到文件系统;当运行容器时,可以调用 snapshots 模块给容器提供 rootfs 。

容器镜像规范主要有 docker 和 oci v1、v2 三种,考虑到这三种规范在原理上大同小异,可以参考以下示例,将 manifest 当作是每个镜像只有一份的元信息,用于指向镜像的 config 和每层 layer。其中,config 即为镜像配置,把镜像作为容器运行时需要;layer 即为镜像的每一层。

type manifest struct {
   
  c config
  layers []layer
}

镜像下载流程与图 1 中数字标注出来的顺序一致,每个步骤作用总结如下:

首先在 metadata 模块中添加一个 image,这样我们在执行 list image 时可看到这个 image。

其次是需要下载镜像,因为镜像是有 manifest、config、layers 等多个部分组成,所以先下载镜像的 manifest 并保存到 content 模块,再解析 manifest 获取 config 的地址和 layers 的地址。接下来分别把 config 和每个 layer 下载并保存到 content 模块,这里需要强调镜像的 layer 本来应该是目录,当创建容器时联合挂载到 root 下,但是为了方便网络传输和存储,这里会用 tar + 压缩的方式保存。这里保存到 content 也是不解压的。

③、④、⑤的作用关联性比较强,此处放在一起解释。snapshot 模块去 content 模块读取 manifest,找到镜像的所有层,再去 content 模块把这些层自“下”而“上”读取出来,逐一解压并加工,最后放到 snapshot 模块的目录下,像图 1 中的 1001/fs、1002/fs 这些都是镜像的层。(当创建容器时,需要把这些层联合挂载生成容器的 rootfs,可以理解成1001/fs + 1002/fs + … => 1008/work)。

整个流程的函数调用关系如下图 2,喜欢阅读源码的同学可以照着这个去看下。
enter image description here

为了方便理解,接下来用 layer 表示 snaps

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值