• 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.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 交互的,这些会在下文中继续介绍。


  • 相关阅读:
    APICloud开源O2O商城源码
    计算机毕业设计ssm企业员工信息管理系统677du系统+程序+源码+lw+远程部署
    gRPC 高级特性之 重试机制
    01_Maven
    程序员如何优雅地解决线上问题?
    STM32控制舵机精准角度
    SARAS-Net: Scale and Relation Aware Siamese Network for Change Detection
    2022 GCPC--C. Chaotic Construction
    快应用参数传递
    Docker部署和全部命令
  • 原文地址:https://www.cnblogs.com/xingzheanan/p/18204637