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.Newcontainerd 运行的主逻辑。

首先,将注册的插件加载到 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 注册的插件,ctrcontainerd 官方提供的命令行工具。如下:

# 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 源码分析:启动注册流程的更多相关文章

  1. Spring Cloud Eureka源码分析 --- client 注册流程

    Eureka Client 是一个Java 客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的.使用轮询负载算法的负载均衡器. 在应用启动后,将会向Eureka Serve ...

  2. apiserver源码分析——启动流程

    前言 apiserver是k8s控制面的一个组件,在众多组件中唯一一个对接etcd,对外暴露http服务的形式为k8s中各种资源提供增删改查等服务.它是RESTful风格,每个资源的URI都会形如 / ...

  3. Symfony2源码分析——启动过程2

    文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...

  4. quartz2.x源码分析——启动过程

    title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...

  5. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  6. nodejs的Express框架源码分析、工作流程分析

    nodejs的Express框架源码分析.工作流程分析 1.Express的编写流程 2.Express关键api的使用及其作用分析 app.use(middleware); connect pack ...

  7. openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

    这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的f ...

  8. Okhttp源码分析--基本使用流程分析

    Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Re ...

  9. mysql源码分析-启动过程

    mysql源码分析-启动过程 概要 # sql/mysqld.cc, 不包含psi的初始化过程 mysqld_main: // 加载my.cnf和my.cnf.d,还有命令行参数 if (load_d ...

  10. Symfony2源码分析——启动过程1

    本文通过阅读分析Symfony2的源码,了解Symfony2启动过程中完成哪些工作,从阅读源码了解Symfony2框架. Symfony2的核心本质是把Request转换成Response的一个过程. ...

随机推荐

  1. 寻找OpenHarmony「锦鲤」|万元豪礼+技术干货全是你的!

    开源项目 OpenHarmony 是每个人的 OpenHarmony 战"码"先锋第二期蓄力出发! 同时,我们也推出了全网寻找开源锦鲤的活动 只为每一位参与OpenHarmony开 ...

  2. 一文了解网络编程之走进TCP三次握手和HTTP那些你不知道的事

    受到很多引人入胜的标题党的影响,我终于决定,要起一个比他们还标题党的题目,打不过还不能加入吗,嘿嘿. 网络编程一直是我的弱项,其实归根结底还是我太懒了,一看到那个osi七层模型,TCP/IP模型还有那 ...

  3. 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit

    1.问题描述 升级到4.0.0.59版本后,通过pushService.getToken获取华为的token时报如下错误:Illegal application identity. 解决方案 Mate ...

  4. DS-Net:可落地的动态网络,实际加速1.62倍,快改造起来 | CVPR 2021 Oral

    论文提出能够适配硬件加速的动态网络DS-Net,通过提出的double-headed动态门控来实现动态路由.基于论文提出的高性能网络设计和IEB.SGS训练策略,仅用1/2-1/4的计算量就能达到静态 ...

  5. Ansible 学习笔记 - 批量巡检站点 URL 状态

    前言 不拖泥带水,不东拉西扯. 速战速决,五分钟学到一个工作用得上的技巧. 通过一个个具体的实战案例,来生动演示 Ansible 的用法. 需求 我需要定期巡检或定时监控我公司的所有站点的首页的可用性 ...

  6. .NET服务发现(Microsoft.Extensions.ServiceDiscovery)集成Consul

    随着Aspire发布preview5的发布,Microsoft.Extensions.ServiceDiscovery随之更新, 服务注册发现这个属于老掉牙的话题解决什么问题就不赘述了,这里主要讲讲M ...

  7. 润乾报表如何从 mongodb 中取数

    MongoDB 属于 NoSql 中的基于分布式文件存储的文档型数据库,是非关系数据库当中功能最丰富,最像关系数据库的.它支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较 ...

  8. aop 阶段性概况

    前言 对aop进行一个阶段性的总结. 正文 首先什么是aop呢? 那么首先看aop的解决什么样的问题. public class Program { public static void Main(s ...

  9. Flask搭建APP统一管理平台

    主页效果: 1.从数据库中获取所有APP的信息,每个卡片上展示APP名称.bundle id.版本构建历史记录,系统类型等构建信息 2.支持查询筛选,模糊查询 3.点击历史记录跳转APP历史记录详情页 ...

  10. 元素类型 “item” 相关联的 “name” 属性值不能包含 ‘<’ 字符

    Android构建时报错: app:lintVitalRelease[Fatal Error] :3:214: 与元素类型 "item" 相关联的 "name" ...