[containerd] 初始化流程概览

1. 环境

  • containerd版本:v1.7.2,containerd debug搭建教程链接
  • 操作系统:Ubuntu22.04

2. 初始化流程

  containerd的入口为:cmd/containerd/main.go,如下:

func main() {// TODO 实例化containerdapp := command.App()// 实际上这里的app是一个命令行工具封装的,app.Run的运行也是固定的,主要是为了执行app.Action,所以只需要重点分析app.Action干了啥if err := app.Run(os.Args); err != nil {fmt.Fprintf(os.Stderr, "containerd: %s\n", err)os.Exit(1)}
}

  这里的App实际上一个命令行工具的封装,这里执行app.Run的时候实际上执行的时AppAction方法,因此我们需要关心的是containerd是如何实现这个Action

  不过,话说回来,在如今cobra大行其道的时候,containerd居然会使用urfave这个命令行工具,这个工具相比于cobra有何优略势以后倒是可以研究下。

  containerd实现的App有很多细节我们并不需要关心,这里我们重点关心containerd是如何实现Action方法的,毕竟,containerd开始运行后,第一时间就是执行Action方法

func App() *cli.App {... // 省略不需要太关心的代码app.Action = func(context *cli.Context) error {var (start       = time.Now()signals     = make(chan os.Signal, 2048)serverC     = make(chan *server.Server, 1)ctx, cancel = gocontext.WithCancel(gocontext.Background())config      = defaultConfig())defer cancel()// Only try to load the config if it either exists, or the user explicitly// told us to load this6 path.configPath := context.GlobalString("config") // 获取配置文件路径_, err := os.Stat(configPath)if !os.IsNotExist(err) || context.GlobalIsSet("config") {if err := srvconfig.LoadConfig(configPath, config); err != nil {return err}}// Apply flags to the config 解析/etc/containerd/config.toml配置文件到config对象当中if err := applyFlags(context, config); err != nil {return err}if config.GRPC.Address == "" {return fmt.Errorf("grpc address cannot be empty: %w", errdefs.ErrInvalidArgument)}if config.TTRPC.Address == "" {// If TTRPC was not explicitly configured, use defaults based on GRPC.config.TTRPC.Address = config.GRPC.Address + ".ttrpc"config.TTRPC.UID = config.GRPC.UIDconfig.TTRPC.GID = config.GRPC.GID}// Make sure top-level directories are created early. 确保一些目录必须存在if err := server.CreateTopLevelDirectories(config); err != nil {return err}// Stop if we are registering or unregistering against Windows SCM. 仅和Windows有关stop, err := registerUnregisterService(config.Root)if err != nil {logrus.Fatal(err)}if stop {return nil}done := handleSignals(ctx, signals, serverC, cancel) // 处理退出信号// start the signal handler as soon as we can to make sure that// we don't miss any signals during bootsignal.Notify(signals, handledSignals...)// cleanup temp mountsif err := mount.SetTempMountLocation(filepath.Join(config.Root, "tmpmounts")); err != nil {return fmt.Errorf("creating temp mount location: %w", err)}// unmount all temp mounts on boot for the serverwarnings, err := mount.CleanupTempMounts(0)if err != nil {log.G(ctx).WithError(err).Error("unmounting temp mounts")}for _, w := range warnings {log.G(ctx).WithError(w).Warn("cleanup temp mount")}log.G(ctx).WithFields(log.Fields{"version":  version.Version,"revision": version.Revision,}).Info("starting containerd")type srvResp struct {s   *server.Servererr error}// run server initialization in a goroutine so we don't end up blocking important things like SIGTERM handling// while the server is initializing.// As an example, opening the bolt database blocks forever if a containerd instance// is already running, which must then be forcibly terminated (SIGKILL) to recover.chsrv := make(chan srvResp)go func() {defer close(chsrv)// TODO 这里干了啥?server, err := server.New(ctx, config)if err != nil {select {case chsrv <- srvResp{err: err}:case <-ctx.Done():}return}// Launch as a Windows Service if necessary 这里主要是在适配windows,直接忽略if err := launchService(server, done); err != nil {logrus.Fatal(err)}select {case <-ctx.Done():server.Stop()case chsrv <- srvResp{s: server}:}}()var server *server.Serverselect { // 等待Containerd Server初始化完成case <-ctx.Done():return ctx.Err()case r := <-chsrv:if r.err != nil {return r.err}server = r.s}// We don't send the server down serverC directly in the goroutine above because we need it lower down.select { // TODO 这里为啥这么写,没看懂上面的注释case <-ctx.Done():return ctx.Err()case serverC <- server:}// 开启containerd的debug功能,开启后可以通过/debug/vars, /debug/pprof这样的URL查看containerd部分数据if config.Debug.Address != "" {var l net.Listenerif isLocalAddress(config.Debug.Address) {if l, err = sys.GetLocalListener(config.Debug.Address, config.Debug.UID, config.Debug.GID); err != nil {return fmt.Errorf("failed to get listener for debug endpoint: %w", err)}} else {if l, err = net.Listen("tcp", config.Debug.Address); err != nil {return fmt.Errorf("failed to get listener for debug endpoint: %w", err)}}serve(ctx, l, server.ServeDebug)}// containerd的指数据if config.Metrics.Address != "" {l, err := net.Listen("tcp", config.Metrics.Address)if err != nil {return fmt.Errorf("failed to get listener for metrics endpoint: %w", err)}serve(ctx, l, server.ServeMetrics)}// setup the ttrpc endpoint 创建containerd.sock.ttrpc文件tl, err := sys.GetLocalListener(config.TTRPC.Address, config.TTRPC.UID, config.TTRPC.GID)if err != nil {return fmt.Errorf("failed to get listener for main ttrpc endpoint: %w", err)}serve(ctx, tl, server.ServeTTRPC)if config.GRPC.TCPAddress != "" {l, err := net.Listen("tcp", config.GRPC.TCPAddress)if err != nil {return fmt.Errorf("failed to get listener for TCP grpc endpoint: %w", err)}serve(ctx, l, server.ServeTCP)}// setup the main grpc endpoint 创建container.sock文件l, err := sys.GetLocalListener(config.GRPC.Address, config.GRPC.UID, config.GRPC.GID)if err != nil {return fmt.Errorf("failed to get listener for main endpoint: %w", err)}serve(ctx, l, server.ServeGRPC)readyC := make(chan struct{})go func() {server.Wait()close(readyC)}()select {case <-readyC:if err := notifyReady(ctx); err != nil {log.G(ctx).WithError(err).Warn("notify ready failed")}// containerd成功启动log.G(ctx).Infof("containerd successfully booted in %fs", time.Since(start).Seconds())<-donecase <-done:}return nil}return app
}

  如上所示,这里我们忽略的一些无关紧要的代码,重点关心Action的实现。

  通过分析,我们发现,Action方法主要是做了如下一些操作:

  • 1、加载containerd的配置,并校验某些配置的值,如果没有指定containerd配置文件的位置,那么containerd默认的配置文件为/etc/containerd/config.toml
  • 2、创建containerdroot目录以及state目录;实际上,所谓的root目录,指的是containerd保存元数据的位置,譬如镜像、运行时数据、快照等等,默认root目录就是/var/lib/containerd。而所谓的state目录则是存放containerd socket文件的目录,该目录的默认值为:/run/containerd
  • 3、监听SIGPIPE, SIGUSR1, SIGTERM, SIGINT信号
  • 4、清理临时目录
  • 5、实例化containerd server,这个就是我们的重点,稍后我们着重分析
  • 6、根据配置暴露debug接口,开启后可以通过/debug/vars, /debug/pprof这样的URL查看containerd部分数据
  • 7、根据配置暴露metric指标
  • 8、运行GRPC, TCP, TTRPC服务

  解析来我们继续分析containerd server是如何实例化的。

func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {// 主要是为了设置OOM参数以及Cgroupif err := apply(ctx, config); err != nil {return nil, err}// 设置超时参数,这里使用一个Map来保存for key, sec := range config.Timeouts {d, err := time.ParseDuration(sec)if err != nil {return nil, fmt.Errorf("unable to parse %s into a time duration", sec)}timeout.Set(key, d)}// TODO 加载插件plugins, err := LoadPlugins(ctx, config)if err != nil {return nil, err}// TODO StreamProcessor是啥玩意?for id, p := range config.StreamProcessors {diff.RegisterProcessor(diff.BinaryHandler(id, p.Returns, p.Accepts, p.Path, p.Args, p.Env))}// TODO 增加了GRPC Server Option参数serverOpts := []grpc.ServerOption{grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(otelgrpc.StreamServerInterceptor(),grpc_prometheus.StreamServerInterceptor,streamNamespaceInterceptor,)),grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(otelgrpc.UnaryServerInterceptor(),grpc_prometheus.UnaryServerInterceptor,unaryNamespaceInterceptor,)),}// 设置GRPC可以消息的最大阈值if config.GRPC.MaxRecvMsgSize > 0 {serverOpts = append(serverOpts, grpc.MaxRecvMsgSize(config.GRPC.MaxRecvMsgSize))}// 设置GRPC发送消息的最大阈值if config.GRPC.MaxSendMsgSize > 0 {serverOpts = append(serverOpts, grpc.MaxSendMsgSize(config.GRPC.MaxSendMsgSize))}// 实例化TTRPCServer,所谓的TTRPC,实际上就设置GRPC ober TLSttrpcServer, err := newTTRPCServer()if err != nil {return nil, err}tcpServerOpts := serverOpts// 设置TLS证书if config.GRPC.TCPTLSCert != "" {log.G(ctx).Info("setting up tls on tcp GRPC services...")tlsCert, err := tls.LoadX509KeyPair(config.GRPC.TCPTLSCert, config.GRPC.TCPTLSKey)if err != nil {return nil, err}tlsConfig := &tls.Config{Certificates: []tls.Certificate{tlsCert}}if config.GRPC.TCPTLSCA != "" {caCertPool := x509.NewCertPool()caCert, err := os.ReadFile(config.GRPC.TCPTLSCA)if err != nil {return nil, fmt.Errorf("failed to load CA file: %w", err)}caCertPool.AppendCertsFromPEM(caCert)tlsConfig.ClientCAs = caCertPooltlsConfig.ClientAuth = tls.RequireAndVerifyClientCert}tcpServerOpts = append(tcpServerOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))}// grpcService allows GRPC services to be registered with the underlying servertype grpcService interface {Register(*grpc.Server) error}// tcpService allows GRPC services to be registered with the underlying tcp servertype tcpService interface {RegisterTCP(*grpc.Server) error}// ttrpcService allows TTRPC services to be registered with the underlying servertype ttrpcService interface {RegisterTTRPC(*ttrpc.Server) error}var (grpcServer = grpc.NewServer(serverOpts...)tcpServer  = grpc.NewServer(tcpServerOpts...)grpcServices  []grpcServicetcpServices   []tcpServicettrpcServices []ttrpcServices = &Server{grpcServer:  grpcServer,tcpServer:   tcpServer,ttrpcServer: ttrpcServer,config:      config,}// TODO: Remove this in 2.0 and let event plugin crease itevents      = exchange.NewExchange()initialized = plugin.NewPluginSet()required    = make(map[string]struct{}))for _, r := range config.RequiredPlugins {required[r] = struct{}{}}for _, p := range plugins {id := p.URI()reqID := idif config.GetVersion() == 1 {reqID = p.ID}log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id)initContext := plugin.NewContext(ctx,p,initialized,config.Root,config.State,)initContext.Events = eventsinitContext.Address = config.GRPC.AddressinitContext.TTRPCAddress = config.TTRPC.AddressinitContext.RegisterReadiness = s.RegisterReadiness// load the plugin specific configuration if it is providedif p.Config != nil {// 反序列化当前插件的配置pc, err := config.Decode(p)if err != nil {return nil, err}initContext.Config = pc}// 执行插件的InitFn函数,并实例化插件实体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).WithError(err).WithField("type", p.Type).Infof("skip loading plugin %q...", id)} else {log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id)}if _, ok := required[reqID]; ok {return nil, fmt.Errorf("load required plugin %s: %w", id, err)}continue}// 每删除一个插件,都需要从required中删除此插件delete(required, reqID)// 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)}// 如果插件加载完成,但是还有必要的插件没有加载,那么只能退出containerd的初始化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}}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}}return s, nil
}

  以上代码就是containerd server初始化逻辑,主要做了这么几个事情:

  • 1、根据containerd的配置,设置OOM以及Cgroup参数
  • 2、设置超时参数,主要设置了
    • io.containerd.timeout.task.state = 2s
    • io.containerd.timeout.bolt.open = 0s
    • io.containerd.timeout.metrics.shimstats = 2s
    • io.containerd.timeout.shim.cleanup = 5s
    • io.containerd.timeout.shim.load = 5s
    • io.containerd.timeout.shim.shutdown = 3s
  • 3、加载插件
    • 其一是动态加载plugin_dir目录中包含的插件,实际上追踪进去你会发现,注释会提示containerd 1.8以前都不会支持动态加载插件,估计这个特性还在开发当中。
    • 其二是加载content插件,这个插件的具体作用我们以后会分析,看了containerd的同学估计会对这个插件有点印象,擦测这个插件是实现ContentService的关键,以后在分析
    • 其三是加载代理插件,containerd的代理插件具体作用不得而知,以后在分析吧,今天我们先看个整体流程,毕竟我也是初学者。
    • 实际上通过IDEA debug源码的时候,你会发现,containerd最终会注册50个插件。然鹅,在debug的时候根本就没有看到注册的代码,最终跟踪下来,你会发现,这些插件除了content插件,其余的插件都是各个插件在自己的Init函数当汇总注册的,containerd已启动的时候就会注册这些插件
  • 4、处理stream process配置,这玩意具体作用现在我也不知道,后续在分析吧。
  • 5、根据之前注册的插件根据插件的配置实例化插件,如果有任何必须的插件没有初始化,就认为containerd初始化失败
  • 6、注册服务

  以上就是containerd的总体初始化流程,今天只是看了一个大概,其中还有很多不懂的地方,后续我们再各个击破。

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

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

相关文章

webstorm配置less转译

Program中路径如果识别不到 项目文件\node_modules.bin\lessc

springCloud Eureka注册中心配置详解

1、创建一个springBoot项目 2、在springBoot项目中添加SpringCloud依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>…

【MySQL数据库备份】

目录 一、概述 二、数据备份的重要性 1. 数据保护 2. 灾难恢复 3. 数据完整性 4. 合规性要求 三、造成数据丢失的原因 1. 硬件故障 2. 人为错误 3. 黑客攻击 4. 自然灾害 5. 软件故障 四、备份类型 1.物理与逻辑角度 1.1 概念 1.2 物理备份 1.2.1 冷备份 1.2…

建造者模式

1.概念 建造者模式是一种创建型设计模式&#xff0c;它允许我们一步一步的来构造对象&#xff0c;其实就是将创建对象的一个过程拆分成了很多个小步骤&#xff0c;常见主要是在一个A类的基础上&#xff0c;加多一个Builder的类&#xff0c;然后属性和A类的一样&#xff0c;我们…

gdb调试时查看汇编代码

在gdb中查看汇编代码&#xff0c;可以使用display命令或x命令。 以下是一个示例程序&#xff0c;我们以它为例来演示如何在gdb中查看汇编代码。 #include <stdio.h> int main() { int a 10; int b 20; int c a b; printf("c %d\n", c); return 0;…

Flutter:滑动面板

前言 无意中发现了这个库&#xff0c;发现现在很多app中都有类似的功能。以手机b站为例&#xff0c;当你在看视频时&#xff0c;点击评论&#xff0c;视频会向上偏移&#xff0c;下方划出评论界面。 sliding_up_panel SlidingUpPanel是一个Flutter插件&#xff0c;用于创建滑…

Stable Diffusion 硬核生存指南:WebUI 中的 VAE

本篇文章聊聊 Stable Diffusion 生态中呼声最高、也是最复杂的开源模型管理图形界面 “stable-diffusion-webui” 中和 VAE 相关的事情。 写在前面 Stable Diffusion 生态中有一个很重要的项目&#xff0c;它对于 SD 生态繁荣做出的贡献可以说居功至伟&#xff0c;自去年八月…

13. Mybatis-Plus

目录 1. MyBatis-Plus 简介 2. 新建项目 3. 添加依赖 4. 配置数据库 5. 编码 1. MyBatis-Plus 简介 通过官网&#xff1a;MyBatis-Plus MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个 MyBatis (opens new window)的增强工具&#xff0c;在 MyB…

W2NER详解

论文&#xff1a;https://arxiv.org/pdf/2112.10070.pdf 代码&#xff1a;https://github.com/ljynlp/W2NER 文章目录 W2NER介绍模型架构解码 源码介绍数据输入格式模型代码 参考资料 W2NER 介绍 W2NER模型&#xff0c;将NER任务转化预测word-word&#xff08;备注&#xff…

Gateway结合nacos(lb://xxx)无效问题

Gateway结合nacos无效 版本如下&#xff1a; com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2021.0.1.0 org.springframework.cloud:spring-cloud-starter-gateway:3.1.1 配置如下&#xff1a; server:port: 7000 spring:application:name: springCloudGa…

微信小程序生成带参数的二维码base64转png显示

getQRCode() {var that this;wx.request({url: http://localhost:8080/getQRCode?ID 13,header: {content-type: application/json},method: POST,responseType: arraybuffer,//将原本按文本解析修改为arraybuffersuccess(res) {that.setData({getQRCode: wx.arrayBufferToB…

django Ajax--前后端数据交互

一.Django的Ajax和JavaScript的Ajax Django的Ajax和JavaScript的Ajax实质上是指同一种技术&#xff0c;即异步JavaScript和XML&#xff08;Asynchronous JavaScript and XML&#xff09;。它允许在不刷新整个页面的情况下&#xff0c;通过前后端之间的异步交互来获取或发送数据…

string【2】模拟实现string类

string模拟实现 引言&#xff08;实现概述&#xff09;string类方法实现默认成员函数构造函数拷贝构造赋值运算符重载析构函数 迭代器beginend 容量size、capacity、emptyreserveresize 访问元素operator[] 修改insert插入字符插入字符串 appendpush_backoperatoreraseclearswa…

uni-app在小米手机上运行【步骤细节】

注意细节重点&#xff1a; 1.手机使用数据线与电脑连接&#xff0c;手机连接模式必须是传输文件模式 2.手机必须打开开发者模式 3.打开开发者模式后&#xff0c;仔细浏览并调整USB调试权限&#xff0c;重点打开USB是否允许安装按钮&#xff01;&#xff01;&#xff01; 操作步…

Mybatis-Plus分页功能

1、创建springboot项目 2、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId&…

RWEQ模型参量提取

土壤风蚀是一个全球性的环境问题。中国是世界上受土壤风蚀危害最严重的国家之一&#xff0c;土壤风蚀是中国干旱、半干旱及部分湿润地区土地荒漠化的首要过程。中国风蚀荒漠化面积达160.74104km2&#xff0c;占国土总面积的16.7%&#xff0c;严重影响这些地区的资源开发和社会经…

windows环境下,安装elasticsearch

目录 前言准备安装 jdk 安装nodejsElasticSearch下载ElasticSearch-head 下载 安装ElasticSearch安装ElasticSearch-head插件设置用户名密码访问ElasticSearch 默认用户名和密码参考 前言 win10elasticsearch 8.9.0 准备 安装 jdk ElasticSearch 是基于lucence开发的&#…

文本比较工具

1. Beyond Compare。 下载&#xff1a; https://www.scootersoftware.com/download https://www.scootersoftware.com/files/BCompare-4.4.6.27483.exe 最好用的文本比较工具&#xff0c;支持。 2. 如果安装了Visual Studio&#xff08;比如&#xff1a;Microsoft Visual Stud…

MATLAB | 如何绘制这样的描边散点图?

part.-1 前前言 最近略忙可能更新的内容会比较简单&#xff0c;见谅哇&#xff0c;今日更新内容&#xff1a; part.0 前言 看到gzhBYtools科研笔记(推荐大家可以去瞅瞅&#xff0c;有很多有意思的图形的R语言复现&#xff01;&#xff01;)做了这样一张图&#xff1a; 感觉很…

docker简单web管理docker.io/uifd/ui-for-docker

要先pull这个镜像docker.io/uifd/ui-for-docker 这个软件默认只能使用9000端口&#xff0c;别的不行&#xff0c;因为作者在镜像制作时已加入这一层 刚下下来镜像可以通过docker history docker.io/uifd/ui-for-docker 查看到这个端口已被 设置 如果在没有设置br0网关时&…