本文分析 containerd 列出所有镜像的分析过程,包括 ctr image 命令行以及 containerd daemon 执行 过程,也包含镜像 metadata,content 等内容。
1. 执行如下命令 ctr images list
首先分析 ctr 命令,实现在 cmd/ctr 的子命令 listCommand 实现,利用 GRPC 链接到 remote cotainerd 端请求,使用了如下两个服务,其中 imageStore 实现了文件 image/images.go 的 Store 接口,cs 实现了 content/content.go Store 接口,如下 1.1 1.2 所示:
imageStore = client.ImageService()
cs = client.ContentStore()
1.1 文件 image/image.go 接口 Store
Store 接口很好理解,增删改查的操作。
// Store and interact with images
type Store interface {
Get(ctx context.Context, name string) (Image, error)
List(ctx context.Context, filters ...string) ([]Image, error)
Create(ctx context.Context, image Image) (Image, error)
// Update will replace the data in the store with the provided image. If
// one or more fieldpaths are provided, only those fields will be updated.
Update(ctx context.Context, image Image, fieldpaths ...string) (Image, error)
Delete(ctx context.Context, name string, opts ...DeleteOpt) error
}
1.2 文件 content/content.go 接口 Store
Store 接口🈶包含四个匿名接口, Manager 提供了 content 接口, Provider 提供了指定内容的读接口, IngestManager 提供了管理 ingest 的方法, Ingester 提供了写 content 的方法。
// Store combines the methods of content-oriented interfaces into a set that
// are commonly provided by complete implementations.
type Store interface {
Manager
Provider
IngestManager
Ingester
}
2. 向 containerd 发起 list 请求
imageList, err := imageStore.List(ctx, filters...)
2.1 containerd 处理 GRPC ListImagesRequest 请求
其核心处理函数在 containerd/services.images/service.go 文件中 List 方法。
func (c *imagesClient) List(ctx context.Context, in *ListImagesRequest, opts ...grpc.CallOption) (*ListImagesResponse, error) {
out := new(ListImagesResponse)
err := c.cc.Invoke(ctx, "/containerd.services.images.v1.Images/List", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
2.2 service List 方法
看出最终调用的是 local.List 方法, 那主要分析到 local 实现也在相同目录 containerd/services.images 中。
func (s *service) List(ctx context.Context, req *imagesapi.ListImagesRequest) (*imagesapi.ListImagesResponse, error) {
return s.local.List(ctx, req)
}
2.3 结构体 local
其中又包含三个接口, store gcScheduler Publisher, local 实现了 ImagesClient 的接口
type local struct {
store images.Store
gc gcScheduler
publisher events.Publisher
}
var _ imagesapi.ImagesClient = &local{}
2.4 初始化 init 注册插件 io.containerd.service.v1
io.containerd.service.v1 需要插件 io.containerd.metadata.v1 和 io.containerd.gc.v1, 可以看出来 store 接口是插件 io.containerd.metadata.v1 实现的。 在看看 metadata 初始化插件
func init() {
plugin.Register(&plugin.Registration{
Type: plugin.ServicePlugin,
ID: services.ImagesService,
Requires: []plugin.Type{
plugin.MetadataPlugin,
plugin.GCPlugin,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
m, err := ic.Get(plugin.MetadataPlugin)
if err != nil {
return nil, err
}
g, err := ic.Get(plugin.GCPlugin)
if err != nil {
return nil, err
}
return &local{
store: metadata.NewImageStore(m.(*metadata.DB)),
publisher: ic.Events,
gc: g.(gcScheduler),
}, nil
},
})
}
2.5 插件 metadata 初始化
可以看出来使用的 db 为 bolt, 在 io.containerd.metadata.v1.bolt 目录创建了数据库文件 meta.db。
plugin.Register(&plugin.Registration{
Type: plugin.MetadataPlugin,
ID: "bolt",
Requires: []plugin.Type{
plugin.ContentPlugin,
plugin.SnapshotPlugin,
},
Config: &srvconfig.BoltConfig{
ContentSharingPolicy: srvconfig.SharingPolicyShared,
},
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
.........................
db, err := bolt.Open(path, 0644, &options)
close(doneCh)
if err != nil {
return nil, err
}
var dbopts []metadata.DBOpt
if !shared {
dbopts = append(dbopts, metadata.WithPolicyIsolated)
}
mdb := metadata.NewDB(db, cs.(content.Store), snapshotters, dbopts...)
回到正题, 还是继续看 List 流程, 可以看到调用了 store.List 方法, 实现为 boltdb 数据库。
func (l *local) List(ctx context.Context, req *imagesapi.ListImagesRequest, _ ...grpc.CallOption) (*imagesapi.ListImagesResponse, error) {
images, err := l.store.List(ctx, req.Filters...)
if err != nil {
return nil, errdefs.ToGRPC(err)
}
return &imagesapi.ListImagesResponse{
Images: imagesToProto(images),
}, nil
}
这里以 namespace 作为 bucket, 剩下的就从 boltdb 读取, key value 读取完成, 具体内容在后续 pull 镜像讲解。
func (s *imageStore) List(ctx context.Context, fs ...string) ([]images.Image, error) {
namespace, err := namespaces.NamespaceRequired(ctx)
if err != nil {
return nil, err
}
总结:
list image 依赖 metadata 数据库 boltdb 读取 key,value