containerd源代码分析: 整体架构

本文从代码的大的整体组织上来熟悉containerd项目
containerd项目总的说是一个cs模式的原生控制台程序组。containerd作为服务端来接收处理client的各种请求,如常用的拉取推送镜像,创建查询停止容器,生成快照,发送消息等。client/server之间通过grpc和ttrpc框架进行交互。
我们可以先看一下contanerd源代码中的cmd下文件夹如图:

image.png
每一个目录都会生成一个二进制文件:
containerd 为服务主程序。
containerd-shim-runc-v2: 为主程序和runtime程序之间交互的垫片二进制
contianerd-stress:不是实际生产使用的程序,而是为了对containerd进行压力测试使用
ctr: 是containerd的客户端二进制,可以发送各种命令请求。上一个用法图:

image.png

我们看到了项目的最终输出的物理文件。那么具体的交互逻辑或者说流程是什么样的。其实每个具体的功能都是通过各个相应的插件来完成。containerd背后有各种标准如oci、cni、csi等,采用插件的形式方便了各个供应商扩展自己的功能。我们先从静态代码上梳理一下.在项目的core目录下包含了containerd实现的大模块,如容器、内容、差异、镜像、元数据存储、租约、指标、挂载点、镜像注册中心、快照、沙箱、运行时。

image.png
我们以content模块为例将进行探索。
在core/content/content.go中抽象出来了content的接口类型如:

type Store interface {ManagerProviderIngestManagerIngester
}

这个是个接口聚合,每个都可以展开成一个具体接口或者接口组合。
如:

// InfoProvider provides info for content inspection.
type InfoProvider interface {// Info will return metadata about content available in the content store.//// If the content is not present, ErrNotFound will be returned.Info(ctx context.Context, dgst digest.Digest) (Info, error)
}

我们刚才说每个功能都是由插件实现(插件会在server启动时加载,先埋下伏笔)那么进入plugins/content/local/store.go,可以看到它实现了上面的InfoProvider接口

func (s *store) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {p, err := s.blobPath(dgst)if err != nil {return content.Info{}, fmt.Errorf("calculating blob info path: %w", err)}fi, err := os.Stat(p)if err != nil {if os.IsNotExist(err) {err = fmt.Errorf("content %v: %w", dgst, errdefs.ErrNotFound)}return content.Info{}, err}var labels map[string]stringif s.ls != nil {labels, err = s.ls.Get(dgst)if err != nil {return content.Info{}, err}}return s.info(dgst, fi, labels), nil
}

Manager的接口也被实现了,这里不列出了。现在是实现有了。插件在哪里使用它呢,通过在鼠标右键->查找用法(IDEA+go插件环境)找到
cmd/containerd/server/server.go文件中的

func LoadPlugins(ctx context.Context, config *srvconfig.Config) ([]plugin.Registration, error) {// load all plugins into containerd//  .............// load additional plugins that don't automatically register themselvesregistry.Register(&plugin.Registration{Type: plugins.ContentPlugin,ID:   "content",InitFn: func(ic *plugin.InitContext) (interface{}, error) {root := ic.Properties[plugins.PropertyRootDir]ic.Meta.Exports["root"] = rootreturn local.NewStore(root)},})//....................
}

第11行,return local.NewStore(root) 对store进行了实例化。
插件类型为plugins.ContentPlugin,id为content.到此完成了插件对接口实现的包装和注册。
plugins/content/local/store.go对store的实现可以在本地直接调用。没有涉及到客户端client发送请求调用。
客户端请求的插件同样可以在上述的loadplugins函数中找到

clients := &proxyClients{}
for name, pp := range config.ProxyPlugins {var (t plugin.Typef func(*grpc.ClientConn) interface{}address = pp.Addressp       v1.Platformerr     error)switch pp.Type {//........case string(plugins.ContentPlugin), "content":t = plugins.ContentPluginf = func(conn *grpc.ClientConn) interface{} {return csproxy.NewContentStore(csapi.NewContentClient(conn))}//......```
registry.Register(&plugin.Registration{Type: t,ID:   name,InitFn: func(ic *plugin.InitContext) (interface{}, error) {ic.Meta.Exports = exportsic.Meta.Platforms = append(ic.Meta.Platforms, p)conn, err := clients.getClient(address)if err != nil {return nil, err}return f(conn), nil},
})

第1行声明了客户端proxyClients,第17行创建了生成store的函数体。在第29行对proxyClients实例化创建了到server的连接,并在第33行调用前面声明的函数体完成初始化的逻辑,同时在第23行也实现了对插件的注册。
在这个函数里还进行了snapshotsandboxdiff插件的注册。
如果再进一步看下第18行的代码发现它是调用core/content/proxy/content_store.go中的函数func NewContentStore(client contentapi.ContentClient) content.Store{...}
可以在plugins/services/content/service.go中找到具体的grpc plugin content

func init() {registry.Register(&plugin.Registration{Type: plugins.GRPCPlugin,ID:   "content",Requires: []plugin.Type{plugins.ServicePlugin,},InitFn: func(ic *plugin.InitContext) (interface{}, error) {cs, err := ic.GetByID(plugins.ServicePlugin, services.ContentService)if err != nil {return nil, err}return contentserver.New(cs.(content.Store)), nil},})
}

可以看到在第6行又依赖了plugins.ServicePlugin插件类型。serviceplugin类型在plugins/services/content/store.go文件中可以找到

func init() {registry.Register(&plugin.Registration{Type: plugins.ServicePlugin,ID:   services.ContentService,Requires: []plugin.Type{plugins.EventPlugin,plugins.MetadataPlugin,},InitFn: func(ic *plugin.InitContext) (interface{}, error) {m, err := ic.GetSingle(plugins.MetadataPlugin)if err != nil {return nil, err}ep, err := ic.GetSingle(plugins.EventPlugin)if err != nil {return nil, err}s, err := newContentStore(m.(*metadata.DB).ContentStore(), ep.(events.Publisher))return s, err},})
}

id为services.ContentService的插件。并且调用插件返回content.store,并在第13行作为参数传入contentserver的new构造函数创建contentserver实例。
contentserver主要完成的接收grpc的请求然后调用store的实现。
如info功能的业务逻辑如下:

func (s *service) Info(ctx context.Context, req *api.InfoRequest) (*api.InfoResponse, error) {dg, err := digest.Parse(req.Digest)if err != nil {return nil, status.Errorf(codes.InvalidArgument, "%q failed validation", req.Digest)}bi, err := s.store.Info(ctx, dg)if err != nil {return nil, errdefs.ToGRPC(err)}return &api.InfoResponse{Info: infoToGRPC(bi),}, nil
}

由于使用了grpc的通讯框架,content的协议定义文件为api/services/content/v1/content.proto,里面定义了消息格式

message InfoRequest {string digest = 1;
}message InfoResponse {Info info = 1;
}

和服务接口

service Content {// Info returns information about a committed object.//// This call can be used for getting the size of content and checking for// existence.rpc Info(InfoRequest) returns (InfoResponse);// ......}

生成的go grpc实现的文件为:api/services/content/v1/content_grpc.pb.go 其中info功能的服务功能如下:

func _Content_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {in := new(InfoRequest)if err := dec(in); err != nil {return nil, err}if interceptor == nil {return srv.(ContentServer).Info(ctx, in)}info := &grpc.UnaryServerInfo{Server:     srv,FullMethod: "/containerd.services.content.v1.Content/Info",}handler := func(ctx context.Context, req interface{}) (interface{}, error) {return srv.(ContentServer).Info(ctx, req.(*InfoRequest))}return interceptor(ctx, in, info, handler)
}

在第7行、第14行调用了上述插件中的contentserver的info实现。
此handler也封装到了Content_ServiceDesc结构中然后通过此文件中的

func RegisterContentServer(s grpc.ServiceRegistrar, srv ContentServer) {s.RegisterService(&Content_ServiceDesc, srv)
}

函数封装了contentserver,此函数也被plugins/services/content/contentserver/contentserver.go中的

func (s *service) Register(server *grpc.Server) error {api.RegisterContentServer(server, s)return nil
}

调用,此函数在插件加载完成后又被server注册到本地缓存中具体见文章开始的cmd/containerd/server/server.go中的new函数代码段:

for _, p := range loaded {id := p.URI()log.G(ctx).WithFields(log.Fields{"id": id, "type": p.Type}).Info("loading plugin")var mustSucceed int32initContext := plugin.NewContext(ctx,initialized,map[string]string{plugins.PropertyRootDir:      filepath.Join(config.Root, id),plugins.PropertyStateDir:     filepath.Join(config.State, id),plugins.PropertyGRPCAddress:  config.GRPC.Address,plugins.PropertyTTRPCAddress: config.TTRPC.Address,},)initContext.RegisterReadiness = func() func() {atomic.StoreInt32(&mustSucceed, 1)return s.RegisterReadiness()}// load the plugin specific configuration if it is providedif p.Config != nil {pc, err := config.Decode(ctx, id, p.Config)if err != nil {return nil, err}initContext.Config = pc}result := p.Init(initContext)if err := initialized.Add(result); err != nil {return nil, fmt.Errorf("could not add plugin result to plugin set: %w", err)}instance, err := result.Instance()if err != nil {if plugin.IsSkipPlugin(err) {log.G(ctx).WithFields(log.Fields{"error": err, "id": id, "type": p.Type}).Info("skip loading plugin")} else {log.G(ctx).WithFields(log.Fields{"error": err, "id": id, "type": p.Type}).Warn("failed to load plugin")}if _, ok := required[id]; ok {return nil, fmt.Errorf("load required plugin %s: %w", id, err)}// If readiness was registered during initialization, the plugin cannot failif atomic.LoadInt32(&mustSucceed) != 0 {return nil, fmt.Errorf("plugin failed after registering readiness %s: %w", id, err)}continue}delete(required, id)// check for grpc services that should be registered with the serverif src, ok := instance.(grpcService); ok {grpcServices = append(grpcServices, src)}if src, ok := instance.(ttrpcService); ok {ttrpcServices = append(ttrpcServices, src)}if service, ok := instance.(tcpService); ok {tcpServices = append(tcpServices, service)}s.plugins = append(s.plugins, result)
}
if len(required) != 0 {var missing []stringfor id := range required {missing = append(missing, id)}return nil, fmt.Errorf("required plugin %s not included", missing)
}// register services after all plugins have been initialized
for _, service := range grpcServices {if err := service.Register(grpcServer); err != nil {return nil, err}
}

第一行loaded表示所以加载后的插件,在29行初始化插件,第34行得到cotnent.store接口的实例,第54行把实例放到缓存grpcservices中,
最后在第75行中调用contentserver的register函数。
上面主要是grpc server端的服务逻辑。客户端的使用逻辑可以在
core/content/proxy/content_store.go文件中找到,看info函数代码:

func (pcs *proxyContentStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {resp, err := pcs.client.Info(ctx, &contentapi.InfoRequest{Digest: dgst.String(),})if err != nil {return content.Info{}, errdefs.FromGRPC(err)}return infoFromGRPC(resp.Info), nil
}

在第2行中调用grpc client代理的info方法向服务器发送请求。

项目中调用的地方不止一处。由下图可见在ctr客户端的cmd/ctr/commands/content/content.go文件中也有使用

image.png

// Nothing updated, do no clear
if len(paths) == 0 {info, err = cs.Info(ctx, info.Digest)
} else {info, err = cs.Update(ctx, info, paths...)
}

如第3行的info和第5行的update均是grpc通信示例。
至此从迷宫一样的代码中梳理出了一个骨架结构,是否可以学到一些设计思想呢?snapshot、diff、sandbox模块的逻辑类似content,具体的细节功能不再展开,后面将就容器的创建流程在对代码进行梳理。不对的地方,请不吝批评指正!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/764978.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

XS2105S,IEEE 802.3af 兼容、用电设备接口控制器集成功率 MOSFET V0.5

XS2105S 为用电设备(PD)提供符合以太网供电(PoE)系统 IEEE802.3af 标准的完整接口。XS2105S 为 PD 提供检测信号、分级信号以及带有浪涌电流控制的 集成隔离功率开关。发生浪涌期间,XS2105S 将电流限 制在 180mA 以内,直到隔离功率 MOSFET 完全开启后切 …

【Linux命令】查看内存占用情况(mem, swap)

1. 方法1(top) # top2.方法2(free) # free -h3. 方法3(swapon) # swapon -s

GraalVM详细安装及打包springboot、java、javafx使用教程(打包springboot3篇)

前言 在当前多元化开发环境下,Java作为一种广泛应用的编程语言,其应用部署效率与灵活性的重要性日益凸显。Spring Boot框架以其简洁的配置和强大的功能深受开发者喜爱,而JavaFX则为开发者提供了构建丰富桌面客户端应用的能力。然而&#xff…

什么是虚拟继承

由于C支持多继承&#xff0c;除了public、protected和private三种继承方式外&#xff0c;还支持虚拟&#xff08;virtual&#xff09;继承&#xff0c;举个例子&#xff1a; #include <iostream> using namespace std;class A {}; class B : virtual public A {}; class…

Vue模块化开发步骤—遇到的问题—解决办法

目录 1.npm install webpack -g 2.npm install -g vue/cli-init 3.初始化vue项目 4.启动vue项目 Vscode初建Vue时几个需要注意的问题-CSDN博客 1.npm install webpack -g 全局安装webpack 直接命令提示符运行改指令会报错&#xff0c;operation not permitted 注意&#…

第一部分:岗位认知

一、谈谈你对大学教师岗位的认识。&#xff08;了解&#xff09; 我想用三种身份来概括我对大学老师的认识&#xff1a;知识的传授者、生命的塑造者、学问的探求者。 &#xff08;一&#xff09;知识的传授者 韩愈曾说&#xff1a;“师者&#xff0c;所以传道授业解惑也。”教师…

算法设计与分析-分支限界——沐雨先生

&#xff08;1&#xff09;抓奶牛问题描述&#xff1a; 农夫约翰被告知逃跑的奶牛的位置&#xff0c;并且要求立即去抓住它。约翰开始的位置在数轴上位置 N &#xff08; 0 ≤ N ≤ 100) &#xff0c;而奶牛的位置在同样一个数轴上的 K (0 ≤ K ≤ 100) 。约翰有两种移动方式&…

Windows下同时安装多个版本的JDK并配置环境变量

说明&#xff1a;这里安装的JDK版本为1.8和17 JDK下载 官方地址: https://www.oracle.com/java/ 我这里下载的是exe安装包 安装这里就不阐述了&#xff0c;安装方法都是一样的。 系统环境变量配置 1、首先新建JDK1.8和17的JAVA_HOME&#xff0c;他们的变量名区分开&#xff…

Disruptor概览

版本&#xff1a;3.4.2 使用案例 初始化 Disruptor<T> disruptor new Disruptor<>(T::new, RING_BUFFER_SIZE,(Runnable r) -> new Thread(r, "MY-DISRUPTOR-THREAD"),ProducerType.MULTI,new SleepingWaitStrategy(50, TimeUnit.MICROSECONDS.to…

只看到真人版《武庚纪》的顶级特效?那你亏大了!

“一不留神就看6集”&#xff0c;一看一个不吱声&#xff0c;相信看过《烈焰》&#xff08;原名&#xff1a;武庚纪&#xff09;的观众或多或少都有和笔者一样的感受。 与其他国产剧不同的是&#xff0c;《烈焰》改编自动画《武庚纪》&#xff0c;“漫改”让他的人物装造更贴近…

基于python+vue超市在线销售系统的设计与实现flask-django-php-nodejs

根据此问题&#xff0c;研发一套超市在线销售系统&#xff0c;既能够大大提高信息的检索、变更与维护的工作效率&#xff0c;也能够方便信息系统的管理运用&#xff0c;从而减少信息管理成本&#xff0c;提高效率。 该超市在线销售系统采用B/S架构、并采用python语言以及django…

【Python 滑块不同的操作】对滑块进行处理,列如切割、还原、去除、无脑识别距离等等

文章日期&#xff1a;2024.03.23 使用工具&#xff1a;Python 类型&#xff1a;图片滑块验证的处理&#xff08;不限于识别距离&#xff09; 使用场景&#xff1a;&#xff1f; 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&a…

Python计算机二级选择易错题(三)

选择题第02&#xff0c;03&#xff0c;04套 题目来源&#xff1a;python计算机二级真题&#xff08;选择题&#xff09; - 知乎 选择题第02套 选择题第03套 选择题第04套 time()获取当前时间&#xff0c;即计算机内部时间&#xff0c;浮点数&#xff1b;import time库&#x…

用户多部门切换部门,MySQL根据多个部门id递归获取所有上级(祖级)、获取部门的全路径(全结构名称)

背景 之前做过的项目&#xff0c;都是一个用户就一个部门的&#xff0c;现在碰到个一个用户在多个部门的需求&#xff0c;而且需要可以切换不同部门查看不同数据。 就比如说一个大公司下面有多个子公司&#xff0c;每个子公司有好多部门、子部门等等&#xff0c;然后有部分用…

【赠书第21期】游戏力:竞技游戏设计实战教程

文章目录 前言 1 竞技游戏设计的核心要素 1.1 游戏机制 1.2 角色与技能 1.3 地图与环境 2 竞技游戏设计的策略与方法 2.1 以玩家为中心 2.2 不断迭代与优化 2.3 营造竞技氛围与社区文化 3 实战案例分析 4 结语 5 推荐图书 6 粉丝福利 前言 在数字化时代的浪潮中&…

Rust之构建命令行程序(五):环境变量

开发环境 Windows 11Rust 1.77.0 VS Code 1.87.2 项目工程 这次创建了新的工程minigrep. 使用环境变量 我们将通过添加一个额外的功能来改进minigrep:一个不区分大小写的搜索选项&#xff0c;用户可以通过环境变量打开该选项。我们可以将此功能设置为命令行选项&#xff0c;…

uniapp(vue3) H5页面连接打印机并打印

一、找到对应厂商打印机的驱动并在windows上面安装。查看是否安装完成可以在&#xff1a;控制面板->查看设备和打印机&#xff0c;找到对应打印机驱动是否安装完成 二、打印机USB连接电脑 三、运行代码调用浏览器打印&#xff0c;主要使用的是window.print()功能。下面使用…

前端学习笔记 | Node.js

一、Node.js入门 1、什么是Node.js 定义&#xff1a;是跨平台JS运行环境&#xff08;可以独立执行JS的环境&#xff09;作用&#xff1a; 编写数据接口&#xff0c;提供网页资源功能等等前端工程化&#xff1a;为后续学Vue和React等框架做铺垫 2、Node.js为何能执行JS&#xff…

python分类信息服务平台移动端的设计与实现flask-django-php-nodejs

分类信息服务平台设计的目的是为用户提供活动信息、活动记录等方面的平台。 与PC端应用程序相比&#xff0c;分类信息服务平台的设计主要面向于移动端&#xff0c;旨在为管理员和用户、商铺提供一个分类信息服务平台。用户可以通过Android及时查看活动信息等。 分类信息服务平台…

IDEA调优-四大基础配置-编码纵享丝滑

文章目录 1.JVM虚拟机选项配置2.多线程编译速度3.构建共享堆内存大小4.关闭不必要的插件 1.JVM虚拟机选项配置 -Xms128m -Xmx8192m -XX:ReservedCodeCacheSize1024m -XX:UseG1GC -XX:SoftRefLRUPolicyMSPerMB50 -XX:CICompilerCount2 -XX:HeapDumpOnOutOfMemoryError -XX:-Omi…