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. Native Rawfile开发指导

      场景介绍 开发者可以通过本指导了解在HarmonyOS应用中,如何使用Native Rawfile接口操作Rawfile目录和文件.功能包括遍历.打开.搜索.读取和关闭Rawfile. 接口说明 ...

  2. 【鸿蒙生态千帆起】HarmonyOS系统级地图与位置服务,赋能广大开发者

     在"与HarmonyOS同行,开放生态,共赢未来"为主题的HUAWEI Developer Day(简称HDD)沙龙中,Petal Maps为开发者们带来了在HarmonyOS下 ...

  3. Spring Cloud Bus:消息总线

    Spring Cloud Bus:消息总线 SpringCloud学习教程 SpringCloud Spring Cloud Bus 使用轻量级的消息代理来连接微服务架构中的各个服务,可以将其用于广播 ...

  4. szfpga 详细:高云1N1开发板高云gowin软件使用教程

      1.概述 国产FPGA是最近几年起来的产品,具有性价比高特点.高云FPGA,大多用在LED,电机控制,PLC设备上. 高云1N1开发板采用GW1N-LV1QN48C6/I5 FPGA器件.具有低功 ...

  5. kkfileview搭建实战

    kkfileview可以与nginx搭建的文件服务器配合实现预览工作,也可以通过自身的文件系统机制免搭建nginx文件服务器来实现预览工作. nginx 创建nginx # 创建初始容器,获得容器内部 ...

  6. 我自己的JdbcTemplate

    import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import jav ...

  7. Linux下的常见基本指令

    pwd //显示当前用户所在的路径 ls //显示当前路径下的文件名或者目录名称 ls-l //显示当前路径下的文件或者目录的更详细的属性信息 cd 一个目录路径 //进入一个目录,进去后,可以用pw ...

  8. css添加属性,让浏览器检查无法选中元素

    1.表现 浏览器直接选中元素的时候,仅能直接选中整个body,想要找到具体元素,需要自己手动寻找,没太大实际作用,仅仅让不懂的人不能简简单单的直接定位元素然后修改里面的内容 pointer-event ...

  9. 让微服务开源更普惠,阿里云微服务引擎MSE全球开服

    ​简介:MSE 于2020年10月在国内开启商业化服务,目前已吸引近万客户使用,用于在云上更低成本构建.更稳定运行微服务架构.此次,MSE 向阿里云国际站开放服务,旨在帮助更多客户享受到更加普惠的微服 ...

  10. es实战-使用IK分词器进行词频统计

    ​简介:通过IK分词器分词并生成词云. 本文主要介绍如何通过 IK 分词器进行词频统计.使用分词器对文章的词频进行统计,主要目的是实现如下图所示的词云功能,可以找到文章内的重点词汇.后续也可以对词进行 ...