containerd的核心Layer层存储组件content,用于存储下载的layer层数据,大家知道镜像是分层架构,一个镜像可能有很多Layer层组成,通过各Layer堆叠形成最终的文件系统,供运行时组件调用。本节详细分析content组件。

一、content服务接口

type Store interface {
	Manager
	Provider
	IngestManager
	Ingester
}

1.Manager用于Layer层数据的删改查,对应下面四个方法。Walk方法,是查询的一种,将过滤出来的content应用WalkFunc方法。最后一个查Info,并不是读取Layer层数据,而是获取其属性信息,包括文件大小、修改时间等。

Delete(ctx context.Context, dgst digest.Digest) error
Update(ctx context.Context, info Info, fieldpaths ...string) (Info, error)
Walk(ctx context.Context, fn WalkFunc, filters ...string) error
Info(ctx context.Context, dgst digest.Digest) (Info, error)

2.Provider用户Layer层数据的查询。下面方法读取数据。

ReaderAt(ctx context.Context, desc ocispec.Descriptor) (ReaderAt, error)

3.IngestManager用于管理正在下载的Layer层数据,提供状态查询、中断等方法,上面两个方法是下载完成的Layer管理接口。

Status(ctx context.Context, ref string) (Status, error)
ListStatuses(ctx context.Context, filters ...string) ([]Status, error)
Abort(ctx context.Context, ref string) error

4.Ingester用于下载Layer层数据,提供数据存储服务。

Writer(ctx context.Context, opts ...WriterOpt) (Writer, error)

二、content组件设计

containerd以插件形式管理各个组件,content组件生效需要提供两个组件标识。

plugin.GRPCPlugin 将 content组件注册为GRPC服务,对外提供服务接口,ctr、crictl等客户端可以访问该服务。
plugin.ServicePlugin 将content组件注册为一个功能服务,提供Layer层存储服务。
plugin.MetadataPlugin 该组件是所有组件的元数据管理中心,用于存储各组件的管理数据。
plugin.ContentPlugin content组件的存储实现服务,真正实现存储逻辑的。

他们几个的关系:
1)客户端调用从GRPCPlugin组件访问Content服务,GRPCPlugin加载ServicePlugin并调用其提供的服务。
2)ServicePlugin 调用MetadataPlugin,调用其提供的服务。
3)MetadataPlugin 调用ContentPlugin,调用其提供的存储服务。

containerd的源代码在这方面确实有点混乱,这几个组件很多方法名称相同,连IDE也无法正确分辨。

三、ContentPlugin

1. 存储目录定义

定义存储结构,组件包含两类数据,一类是正在下载的临时目录,一类是下载完成、验证完成的Layer层真实存储目录。

1)下载完成的Layer层存储目录:/var/lib/containerd/io.containerd.content.v1.content/blobs 2)Layer层临时存储目录:/var/lib/containerd/io.containerd.content.v1.content/ingest

2. Layer层存储服务

2.1)获取Layer层下载的Writer句柄

IngesterWriter(ctx context.Context, opts ...WriterOpt) (Writer, error) 实现。

1)存储Layer层数据,传入该Layer层的唯一标识,以及根据Manifest获取的Desc信息,Desc信息包含期望下载的Digest。
2)ingest目录其{namespace/id/ref}文件夹下,定义两个子文件 ref文件、data文件,ref文件存储Layer层的唯一标识,data文件Layer层数据。
3) 根据data文件是否有内容,判断是重新写入还是接着上次的offset继续写入。将data文件的WriteIO句柄封装到返回值Writer服务中。

2.2)Writer服务

1)Writer数据写入服务,这个好理解,写入Layer层数据到data文件
2)Digest获取写入数据的Hash值
3)Status获取已经写入数据状态,包括写入Size、Offset,以及期望的Digest等。
4)Truncate文件内容清空,Reset文件的Offset等。
5)Commit通知文件写入完毕,可以进行后续处理,例如将ingest文件存储到blobs目录。

四、MetadataPlugin

客户端真正的访问顺序,从MetadataPluginContentPlugin,在调用ContentPlugin存储Layer数据前,需要MetadataPlugin 先行对元数据进行处理。

1. 存储

metadata存储数据库:bolt metadata存储目录:/var/lib/containerd/io.containerd.metadata.v1.bolt

2. 常用的存储字段

1)该Layer层存储完毕,记录元数据

Key:v1/{namespace}/content/blob/{digest}
Key1: createdat
Key2:updatedat
Key3:size

2)content的lease信息,下载完成后设置

Key: v1/{namespace}/leases/{leaseID}/content/{Digest}
Value: nil

3)正在下载的ref记录

Key:v1/{namespace}/content/ingests/{ref}
Key1:ref Value:{namespace}/{boltID}{ref}
Key2:expireat Value:{deadline time}
Key3:expected Value:{Digest}

4)下载环节设置,GC使用

Key: v1/{namespace}/leases/{leaseID}/ingests/{ref}
Value: nil

五、GarbageCollect 垃圾回收

content垃圾回收用于删除无用的content。

遍历所有的content存储,在v1/{namespace}/content/blob/v1/{namespace}/content/ingests存在的content保留,其他删除。