containerd 源碼分析:kubelet 和 containerd 交互


0. 前言

Kubernetes:kubelet 源碼分析之創建 pod 流程 介紹了 kubelet 創建 pod 的流程,其中介紹了 kubelet 調用 runtime cri 接口創建 pod。containerd 源碼分析:啓動註冊流程 介紹了 containerd 作爲一種行業標準的高級運行時的啓動註冊流程。那麼,kubelet 是怎麼和 containerd 交互的呢? 本文會帶着這個問題分析 kubeletcontainerd 的交互。

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 架構圖

image

從圖中可以看出,containerd 的 CRI 插件提供 image serviceruntime service,負責對接 kubelet runtime cri 的接口調用,並將調用轉發給 containerd

繼續,查看 containerd 的處理流程。

1.3 containerd

1.3.1 CRI Plugin

根據 cri 架構圖, 從 CRI 插件入手查看 id 爲 io.containerd.grpc.v1.criCRI 插件。

// 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 serviceimage 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 方法。

繼續看,instrumentedServiceRunPodSandbox 做了什麼。

// 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 調用 criGRPCServerRunPodSandbox 方法,實際執行的是 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 調用的是 sandboxServiceCreateSandboxStartSandbox 方法。

1.3.2 sanbox Plugin

sandboxServicecri.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 controllersCreateSandbox 方法,該方法最終調用的是 sandboxClientCreateSandbox

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 流程

根據上述分析,這裏畫出 kubeletcontainerd 的交互流程圖如下:
image

2. 小結

本文在前文 Kubernetes:kubelet 源碼分析之創建 pod 流程containerd 源碼分析:啓動註冊流程 的基礎上,進一步分析從 kubeletcontainerd 的交互流程,打通了 kubeletcontainerd 這一步。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章