containerd 源码分析:kubelet 和 containerd 交互
0. 前言
Kubernetes:kubelet 源码分析之创建 pod 流程 介绍了 kubelet 创建 pod 的流程,其中介绍了 kubelet 调用 runtime cri 接口创建 pod。containerd 源码分析:启动注册流程 介绍了 containerd 作为一种行业标准的高级运行时的启动注册流程。那么,kubelet 是怎么和 containerd 交互的呢? 本文会带着这个问题分析 kubelet 和 containerd 的交互。
1. kubelet 和 containerd 交互
1.1 kubelet
如 Kubernetes:kubelet 源码分析之创建 pod 流程 分析,kubelet 调用 runtime cri 接口 /runtime.v1.RuntimeService/RunPodSandbox 创建 pod:
// kubernetes/vendor/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go
func (c *runtimeServiceClient) RunPodSandbox(ctx context.Context, in *RunPodSandboxRequest, opts ...grpc.CallOption) (*RunPodSandboxResponse, error) {
	out := new(RunPodSandboxResponse)
	err := c.cc.Invoke(ctx, "/runtime.v1.RuntimeService/RunPodSandbox", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}
kubelet 是 runtime cri 接口调用的客户端,那么容器运行时作为服务端是怎么提供服务的呢?
1.2 kubelet 和 containerd 交互流程
在介绍容器运行时提供的服务之前先看下 cri 架构图。

从图中可以看出,containerd 的 CRI 插件提供 image service 和 runtime service,负责对接 kubelet runtime cri 的接口调用,并将调用转发给 containerd。
继续,查看 containerd 的处理流程。
1.3 containerd
1.3.1 CRI Plugin
根据 cri 架构图, 从 CRI 插件入手查看 id 为 io.containerd.grpc.v1.cri 的 CRI 插件。
// containerd/plugins/cri/cri.go
func initCRIService(ic *plugin.InitContext) (interface{}, error) {
	...
    // Get runtime service.
	criRuntimePlugin, err := ic.GetByID(plugins.CRIServicePlugin, "runtime")
	if err != nil {
		return nil, fmt.Errorf("unable to load CRI runtime service plugin dependency: %w", err)
	}
    // Get image service.
	criImagePlugin, err := ic.GetByID(plugins.CRIServicePlugin, "images")
	if err != nil {
		return nil, fmt.Errorf("unable to load CRI image service plugin dependency: %w", err)
	}
    ...
    service := &criGRPCServer{
		RuntimeServiceServer: rs,
		ImageServiceServer:   is,
		Closer:               s, // TODO: Where is close run?
		initializer:          s,
	}
	if config.DisableTCPService {
		return service, nil
	}
	return criGRPCServerWithTCP{service}, nil
}
插件返回的是 criGRPCServerWithTCP 对象。其中,包括 criGRPCServer 对象。criGRPCServer 对象实现了 grpcService 接口,将调用接口的 Register 注册对象到 grpc server。
// containerd/plugins/cri/cri.go
// Register registers all required services onto a specific grpc server.
// This is used by containerd cri plugin.
func (c *criGRPCServer) Register(s *grpc.Server) error {
	return c.register(s)
}
func (c *criGRPCServer) register(s *grpc.Server) error {
	instrumented := instrument.NewService(c)
	runtime.RegisterRuntimeServiceServer(s, instrumented)
	runtime.RegisterImageServiceServer(s, instrumented)
	return nil
}
在 criGRPCServer.register 中创建 instrumentedService 对象。
type instrumentedService struct {
	c criService
}
func NewService(c criService) GRPCServices {
	return &instrumentedService{c: c}
}
instrumentedService 包括 criService 对象。实际提供 runtime service 和 image service 的就是 criService 对象。
以注册 runtime service 为例,查看 runtime.RegisterRuntimeServiceServer(s, instrumented) 做了什么。
// containerd/vendor/k8s.io/cri-api/pkg/apis/runtime/v1/api.pb.go
func RegisterRuntimeServiceServer(s *grpc.Server, srv RuntimeServiceServer) {
	s.RegisterService(&_RuntimeService_serviceDesc, srv)
}
var _RuntimeService_serviceDesc = grpc.ServiceDesc{
	ServiceName: "runtime.v1.RuntimeService",
	HandlerType: (*RuntimeServiceServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "Version",
			Handler:    _RuntimeService_Version_Handler,
		},
		{
			MethodName: "RunPodSandbox",
			Handler:    _RuntimeService_RunPodSandbox_Handler,
		},
        ...
    },
    ...
}
可以看到,注册 instrumentedService 到 grpc 中,instrumentedService 提供 runtime.v1.RuntimeService 服务,包括 kubelet 调用的 RunPodSandbox 方法。
继续看,instrumentedService 的 RunPodSandbox 做了什么。
// containerd/internal/cri/instrumented_service.go
func (in *instrumentedService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (res *runtime.RunPodSandboxResponse, err error) {
	...
	res, err = in.c.RunPodSandbox(ctrdutil.WithNamespace(ctx), r)
	return res, errdefs.ToGRPC(err)
}
instrumentedService 调用 criGRPCServer 的 RunPodSandbox 方法,实际执行的是 criGRPCServer 中的 criServer 对象:
// containerd/internal/cri/server/sandbox_run.go
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {
	...
    if err := c.sandboxService.CreateSandbox(ctx, sandboxInfo, sb.WithOptions(config), sb.WithNetNSPath(sandbox.NetNSPath)); err != nil {
		return nil, fmt.Errorf("failed to create sandbox %q: %w", id, err)
	}
	ctrl, err := c.sandboxService.StartSandbox(ctx, sandbox.Sandboxer, id)
    if err != nil {
        ...
    }
    ...
}
criService.RunPodSandbox 调用的是 sandboxService 的 CreateSandbox 和 StartSandbox 方法。
1.3.2 sanbox Plugin
sandboxService 在 cri.initCRIService 中实例化:
// containerd/plugins/cri/cri.go
func initCRIService(ic *plugin.InitContext) (interface{}, error) {
    ...
    sbControllers, err := getSandboxControllers(ic)
	if err != nil {
		return nil, fmt.Errorf("failed to get sandbox controllers from plugins %v", err)
	}
    ...
    options := &server.CRIServiceOptions{
		RuntimeService:     criRuntimePlugin.(server.RuntimeService),
		ImageService:       criImagePlugin.(server.ImageService),
		StreamingConfig:    streamingConfig,
		NRI:                getNRIAPI(ic),
		Client:             client,
		SandboxControllers: sbControllers,
	}
    ...
    s, rs, err := server.NewCRIService(options)
    ...
    service := &criGRPCServer{
		RuntimeServiceServer: rs,
		ImageServiceServer:   is,
		Closer:               s, // TODO: Where is close run?
		initializer:          s,
	}
}
首先,getSandboxControllers 获得 sandbox controllers:
// containerd/plugins/cri/cri.go
func getSandboxControllers(ic *plugin.InitContext) (map[string]sandbox.Controller, error) {
    // plugins.SandboxControllerPlugin: "io.containerd.sandbox.controller.v1"
	sandboxers, err := ic.GetByType(plugins.SandboxControllerPlugin)
	if err != nil {
		return nil, err
	}
	...
	return sc, nil
}
sandbox.Controller 是类型为 io.containerd.sandbox.controller.v1 的插件对象。将该对象作为 options 赋给 criServer:
// containerd/internal/cri/server/service.go
func NewCRIService(options *CRIServiceOptions) (CRIService, runtime.RuntimeServiceServer, error) {
	...
	c := &criService{
		...
		sandboxService:     newCriSandboxService(&config, options.SandboxControllers),
	}
    ...
}
func newCriSandboxService(config *criconfig.Config, sandboxers map[string]sandbox.Controller) *criSandboxService {
	return &criSandboxService{
		sandboxControllers: sandboxers,
		config:             config,
	}
}
criService.sandboxService.CreateSandbox 调用的是插件对象 sanbox controllers 的 CreateSandbox 方法,该方法最终调用的是 sandboxClient 的 CreateSandbox:
func (c *sandboxClient) CreateSandbox(ctx context.Context, in *CreateSandboxRequest, opts ...grpc.CallOption) (*CreateSandboxResponse, error) {
	out := new(CreateSandboxResponse)
	err := c.cc.Invoke(ctx, "/containerd.runtime.sandbox.v1.Sandbox/CreateSandbox", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}
可以看到,在 sandboxClient.CreateSandbox 中调用 containerd 提供的 containerd cri 接口 /containerd.runtime.sandbox.v1.Sandbox/CreateSandbox,该接口用来创建 sandbox,即 pod。
1.4 创建 pod 流程
根据上述分析,这里画出 kubelet 到 containerd 的交互流程图如下:

2. 小结
本文在前文 Kubernetes:kubelet 源码分析之创建 pod 流程 和 containerd 源码分析:启动注册流程 的基础上,进一步分析从 kubelet 到 containerd 的交互流程,打通了 kubelet 到 containerd 这一步。
containerd 源码分析:kubelet 和 containerd 交互的更多相关文章
- ifconfig源码分析之与内核交互数据
		<ifconfig源码分析之与内核交互数据>本文档的Copyleft归rosetta所有,使用GPL发布,可以自由拷贝.转载,转载时请保持文档的完整性.参考资料:<Linux设备驱动 ... 
- heapster源码分析——kubelet的api调用分析
		一.heapster简介 什么是Heapster? Heapster是容器集群监控和性能分析工具,天然的支持Kubernetes和CoreOS.Kubernetes有个出名的监控agent---cAd ... 
- MongoDB源码分析——mongo与JavaScript交互
		mongo与JavaScript交互 源码版本为MongoDB 2.6分支 之前已经说过mongo是MongoDB提供的一个执行JavaScript脚本的客户端工具,执行js其实就是一个js和 ... 
- kubelet源码分析——kubelet简介与启动
		kubelet是k8s集群中一个组件,其作为一个agent的角色分布在各个节点上,无论是master还是worker,功能繁多,逻辑复杂.主要功能有 节点状态同步:kublet给api-server同 ... 
- kubelet源码分析——关闭Pod
		上一篇说到kublet如何启动一个pod,本篇讲述如何关闭一个Pod,引用一段来自官方文档介绍pod的生命周期的话 你使用 kubectl 工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期 ... 
- kubelet源码分析——监控Pod变更
		前言 前文介绍Pod无论是启动时还是关闭时,处理是由kubelet的主循环syncLoop开始执行逻辑,而syncLoop的入参是一条传递变更Pod的通道,显然syncLoop往后的逻辑属于消费者一方 ... 
- scheduler源码分析——调度流程
		前言 当api-server处理完一个pod的创建请求后,此时可以通过kubectl把pod get出来,但是pod的状态是Pending.在这个Pod能运行在节点上之前,它还需要经过schedule ... 
- scheduler源码分析——preempt抢占
		前言 之前探讨scheduler的调度流程时,提及过preempt抢占机制,它发生在预选调度失败的时候,当时由于篇幅限制就没有展开细说. 回顾一下抢占流程的主要逻辑在DefaultPreemption ... 
- apiserver源码分析——启动流程
		前言 apiserver是k8s控制面的一个组件,在众多组件中唯一一个对接etcd,对外暴露http服务的形式为k8s中各种资源提供增删改查等服务.它是RESTful风格,每个资源的URI都会形如 / ... 
- apiserver源码分析——处理请求
		前言 上一篇说道k8s-apiserver如何启动,本篇则介绍apiserver启动后,接收到客户端请求的处理流程.如下图所示 认证与授权一般系统都会使用到,认证是鉴别访问apiserver的请求方是 ... 
随机推荐
- 深入了解 Python MongoDB 操作:排序、删除、更新、结果限制全面解析
			Python MongoDB 排序 对结果进行排序 使用 sort() 方法对结果进行升序或降序排序. sort() 方法接受一个参数用于"字段名",一个参数用于"方向& ... 
- Qt搜索本机网卡对应网段的在线设备
			需求:销售给我的需求是找出哪些IP是没有被占用的,所以我要先找出已经被占用的IP 项目是Qt开发的,所以在网上搜索了下,搜索到的实现方式都是:QHostInfo::lookupHost,但是这种方式, ... 
- NL2SQL基础系列(1):业界顶尖排行榜、权威测评数据集及LLM大模型(Spider vs BIRD)全面对比优劣分析[Text2SQL、Text2DSL]
			NL2SQL基础系列(1):业界顶尖排行榜.权威测评数据集及LLM大模型(Spider vs BIRD)全面对比优劣分析[Text2SQL.Text2DSL] Text-to-SQL(或者Text2S ... 
- CentOS9 \ Centos8安装MySQL 8步骤
			centos8 rpm 安装mysql8.0.28_太阳神LoveU的博客-CSDN博客 This upper link is still working for mysql 8 on the Cen ... 
- 重新点亮shell————什么是shell[一]
			前言 这里简介一下什么是shell. 写linux和shell 系列是为了后面的docker 系列的整理,本来想直接整理k8s的,但是呢,想想docker 系列整理完了的话,那么整理k8s系列就没有那 ... 
- Redis工具类,不用框架时备用
			redis.hostName=127.0.0.1 redis.port=6379 redis.database=3 redis.timeout=15000 redis.usePool=true red ... 
- 第三課:信道学习Source Connect Reader & Destinations File Writer
			第一步: 切换到主信道(Channels)界面,右键点击新建信道(New Channel) 第二步 : 下面是设置一些信道概要(Summary)信息 其中summary(概要) 界面主要包含 信道名称 ... 
- 多任务学习模型之ESMM介绍与实现
			简介:本文介绍的是阿里巴巴团队发表在 SIGIR'2018 的论文<Entire Space Multi-Task Model: An Effective Approach for Estima ... 
- R_回归模型实例一
			工作和生活中存在大量的具有相关性的事件,当找到不同变量之间的关系,我们就会用到回归分析.回归分析(Regression Analysis):是用来确定2个或2个以上变量间关系的一种统计分析方法. 在回 ... 
- 【爬虫案例】用Python爬取百度热搜榜数据!
			目录 一.爬取目标 二.编写爬虫代码 三.同步视频讲解 四.完整源码 一.爬取目标 您好,我是@马哥python说,一名10年程序猿. 本次爬取的目标是:百度热搜榜 分别爬取每条热搜的: 热搜标题.热 ... 
