containerd 源码分析:启动注册流程
0. 前言
containerd 是一个行业标准的容器运行时,其强调简单性、健壮性和可移植性。本文将从 containerd 的代码结构入手,查看 containerd 的启动注册流程。
1. 启动注册流程
1.1 containerd
首先以调试模式运行 containerd:
// containerd/cmd/containerd/main.go
package main
import (
...
_ "github.com/containerd/containerd/v2/cmd/containerd/builtins"
)
...
func main() {
app := command.App()
if err := app.Run(os.Args); err != nil {
fmt.Fprintf(os.Stderr, "containerd: %s\n", err)
os.Exit(1)
}
}
在启动 containerd 时,导入匿名包 github.com/containerd/containerd/v2/cmd/containerd/builtins 注册插件。
接着,进入 command.App():
// containerd/cmd/containerd/server/server.go
func App() *cli.App {
app := cli.NewApp()
app.Name = "containerd"
...
app.Action = func(context *cli.Context) error {
...
go func() {
defer close(chsrv)
server, err := server.New(ctx, config)
if err != nil {
select {
case chsrv <- srvResp{err: err}:
case <-ctx.Done():
}
return
}
...
}()
...
}
}
这里省略了一系列初始化过程,重点在 server.New(ctx, config)。
// containerd/cmd/containerd/server/server.go
func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
...
// 将插件加载到 loaded 中
loaded, err := LoadPlugins(ctx, config)
if err != nil {
return nil, err
}
...
serverOpts := []grpc.ServerOption{
grpc.StatsHandler(otelgrpc.NewServerHandler()),
grpc.ChainStreamInterceptor(
streamNamespaceInterceptor,
prometheusServerMetrics.StreamServerInterceptor(),
),
grpc.ChainUnaryInterceptor(
unaryNamespaceInterceptor,
prometheusServerMetrics.UnaryServerInterceptor(),
),
}
...
var (
grpcServer = grpc.NewServer(serverOpts...)
tcpServer = grpc.NewServer(tcpServerOpts...)
grpcServices []grpcService
tcpServices []tcpService
ttrpcServices []ttrpcService
s = &Server{
prometheusServerMetrics: prometheusServerMetrics,
grpcServer: grpcServer,
tcpServer: tcpServer,
ttrpcServer: ttrpcServer,
config: config,
}
...
)
...
// 遍历插件
for _, p := range loaded {
...
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 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)
}
...
}
// 注册插件服务
for _, service := range grpcServices {
if err := service.Register(grpcServer); err != nil {
return nil, err
}
}
for _, service := range ttrpcServices {
if err := service.RegisterTTRPC(ttrpcServer); err != nil {
return nil, err
}
}
for _, service := range tcpServices {
if err := service.RegisterTCP(tcpServer); err != nil {
return nil, err
}
}
...
}
server.New 是 containerd 运行的主逻辑。
首先,将注册的插件加载到 loaded,接着遍历 loaded。通过 result := p.Init(initContext) 获取插件的实例。
以 io.containerd.grpc.v1.containers 插件为例,查看 p.Init 是如何获取插件对象的。
// containerd/vendor/github.com/containerd/plugin/plugin.go
func (r Registration) Init(ic *InitContext) *Plugin {
// 调用注册插件的 InitFn 函数
p, err := r.InitFn(ic)
return &Plugin{
Registration: r,
Config: ic.Config,
Meta: *ic.Meta,
instance: p,
err: err,
}
}
// containerd/plugins/services/containers/service.go
func init() {
registry.Register(&plugin.Registration{
Type: plugins.GRPCPlugin,
ID: "containers",
Requires: []plugin.Type{
plugins.ServicePlugin,
},
// 执行 InitFn 返回 service 对象
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
i, err := ic.GetByID(plugins.ServicePlugin, services.ContainersService)
if err != nil {
return nil, err
}
return &service{local: i.(api.ContainersClient)}, nil
},
})
}
获取到插件实例后,根据插件类型注册插件实例以提供对应的(grpc/ttrpc/tcp)服务。
1.2 注册插件
注册插件是通过 init 机制实现的。在 main 中导入 github.com/containerd/containerd/v2/cmd/containerd/builtins 包。
builtins 包导入包含 init 的插件包实现插件注册。以 cri 插件为例:
// containerd/cmd/containerd/builtins/cri.go
package builtins
import (
_ "github.com/containerd/containerd/v2/plugins/cri"
...
)
// containerd/plugins/cri/cri.go
package cri
...
// Register CRI service plugin
func init() {
defaultConfig := criconfig.DefaultServerConfig()
registry.Register(&plugin.Registration{
Type: plugins.GRPCPlugin,
ID: "cri",
Requires: []plugin.Type{
...
},
Config: &defaultConfig,
ConfigMigration: func(ctx context.Context, configVersion int, pluginConfigs map[string]interface{}) error {
...
},
InitFn: initCRIService,
})
}
在 init 中通过 registry.Register 注册插件:
package registry
...
var register = struct {
sync.RWMutex
r plugin.Registry
}{}
// Register allows plugins to register
func Register(r *plugin.Registration) {
register.Lock()
defer register.Unlock()
register.r = register.r.Register(r)
}
可以看到插件注册的过程实际是将插件结构体 plugin.Registration 注册到 register.plugin.Registry 的过程。
register.plugin.Registry 实际是一个包含 Registration 的切片。
package plugin
type Registry []*Registration
1.3 查看插件
使用 ctr 查看 containerd 注册的插件,ctr 是 containerd 官方提供的命令行工具。如下:
# ctr plugins ls
TYPE ID PLATFORMS STATUS
io.containerd.image-verifier.v1 bindir - ok
io.containerd.internal.v1 opt - ok
...
2. 小结
本文主要介绍了 containerd 的启动注册插件流程。当然,插件的类型众多,插件是如何工作的,插件之间如何交互,kubernetes 又是怎么和 containerd 交互的,这些会在下文中继续介绍。
containerd 源码分析:启动注册流程的更多相关文章
- Spring Cloud Eureka源码分析 --- client 注册流程
Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的.使用轮询负载算法的负载均衡器. 在应用启动后,将会向Eureka Serve ...
- apiserver源码分析——启动流程
前言 apiserver是k8s控制面的一个组件,在众多组件中唯一一个对接etcd,对外暴露http服务的形式为k8s中各种资源提供增删改查等服务.它是RESTful风格,每个资源的URI都会形如 / ...
- Symfony2源码分析——启动过程2
文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...
- quartz2.x源码分析——启动过程
title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...
- Zookeeper 源码分析-启动
Zookeeper 源码分析-启动 博客分类: Zookeeper 本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...
- nodejs的Express框架源码分析、工作流程分析
nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...
- openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)
这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的f ...
- Okhttp源码分析--基本使用流程分析
Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Re ...
- mysql源码分析-启动过程
mysql源码分析-启动过程 概要 # sql/mysqld.cc, 不包含psi的初始化过程 mysqld_main: // 加载my.cnf和my.cnf.d,还有命令行参数 if (load_d ...
- Symfony2源码分析——启动过程1
本文通过阅读分析Symfony2的源码,了解Symfony2启动过程中完成哪些工作,从阅读源码了解Symfony2框架. Symfony2的核心本质是把Request转换成Response的一个过程. ...
随机推荐
- 寻找OpenHarmony「锦鲤」|万元豪礼+技术干货全是你的!
开源项目 OpenHarmony 是每个人的 OpenHarmony 战"码"先锋第二期蓄力出发! 同时,我们也推出了全网寻找开源锦鲤的活动 只为每一位参与OpenHarmony开 ...
- 一文了解网络编程之走进TCP三次握手和HTTP那些你不知道的事
受到很多引人入胜的标题党的影响,我终于决定,要起一个比他们还标题党的题目,打不过还不能加入吗,嘿嘿. 网络编程一直是我的弱项,其实归根结底还是我太懒了,一看到那个osi七层模型,TCP/IP模型还有那 ...
- 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit
1.问题描述 升级到4.0.0.59版本后,通过pushService.getToken获取华为的token时报如下错误:Illegal application identity. 解决方案 Mate ...
- DS-Net:可落地的动态网络,实际加速1.62倍,快改造起来 | CVPR 2021 Oral
论文提出能够适配硬件加速的动态网络DS-Net,通过提出的double-headed动态门控来实现动态路由.基于论文提出的高性能网络设计和IEB.SGS训练策略,仅用1/2-1/4的计算量就能达到静态 ...
- Ansible 学习笔记 - 批量巡检站点 URL 状态
前言 不拖泥带水,不东拉西扯. 速战速决,五分钟学到一个工作用得上的技巧. 通过一个个具体的实战案例,来生动演示 Ansible 的用法. 需求 我需要定期巡检或定时监控我公司的所有站点的首页的可用性 ...
- .NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul
随着Aspire发布preview5的发布,Microsoft.Extensions.ServiceDiscovery随之更新, 服务注册发现这个属于老掉牙的话题解决什么问题就不赘述了,这里主要讲讲M ...
- 润乾报表如何从 mongodb 中取数
MongoDB 属于 NoSql 中的基于分布式文件存储的文档型数据库,是非关系数据库当中功能最丰富,最像关系数据库的.它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较 ...
- aop 阶段性概况
前言 对aop进行一个阶段性的总结. 正文 首先什么是aop呢? 那么首先看aop的解决什么样的问题. public class Program { public static void Main(s ...
- Flask搭建APP统一管理平台
主页效果: 1.从数据库中获取所有APP的信息,每个卡片上展示APP名称.bundle id.版本构建历史记录,系统类型等构建信息 2.支持查询筛选,模糊查询 3.点击历史记录跳转APP历史记录详情页 ...
- 元素类型 “item” 相关联的 “name” 属性值不能包含 ‘<’ 字符
Android构建时报错: app:lintVitalRelease[Fatal Error] :3:214: 与元素类型 "item" 相关联的 "name" ...