containerd 原始碼分析:kubelet 和 containerd 互動

lubanseven發表於2024-05-22

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 這一步。


相關文章