Kubernetes RuntimeClass 与 CRI 的分析

蚂蚁密算开发的 kuscia 是一款基于 k3s 的调度系统。在使用过程中发现其无法指定不同的容器运行时。初步分析,kuscia 的 server 节点使用的是开源 k3s 二进制,agent 节点使用的是蚂蚁修改过的 kubelet 实现,一起放在 kuscia 中打包的。怀疑蚂蚁修改的 kubelet 不支持识别 runtimeClass 导致该问题。

首先通过大模型生成通过 cri 接口调用 containerd 的样例代码:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"google.golang.org/grpc"
	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
)

func main() {
	// 创建 gRPC 客户端连接
	conn, err := grpc.Dial("unix:///run/containerd/containerd.sock", grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Failed to connect to containerd socket: %v", err)
	}
	defer conn.Close()

	// 创建 CRI RuntimeService 客户端
	runtimeClient := runtimeapi.NewRuntimeServiceClient(conn)

	// 创建 Pod Sandbox
	podConfig := &runtimeapi.PodSandboxConfig{
		Metadata: &runtimeapi.PodSandboxMetadata{
			Name:      "example-pod",
			Namespace: "default",
			Uid:       "unique-id",
			Attempt:   1,
		},
		LogDirectory: "/var/log/pods",
	}
	req := &runtimeapi.RunPodSandboxRequest{
		Config: podConfig,
		RuntimeHandler: "",
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	resp, err := runtimeClient.RunPodSandbox(ctx, req)
	if err != nil {
		log.Fatalf("Failed to create Pod Sandbox: %v", err)
	}
	podID := resp.PodSandboxId
	fmt.Printf("Pod Sandbox created with ID: %s\n", podID)

	// 创建容器
	containerConfig := &runtimeapi.ContainerConfig{
		Metadata: &runtimeapi.ContainerMetadata{
			Name: "example-container",
		},
		Image: &runtimeapi.ImageSpec{
			Image: "nginx:latest",
		},
		Command:   []string{"nginx", "-g", "daemon off;"},
		LogPath:   "nginx.log",
		Resources: &runtimeapi.LinuxContainerResources{},
	}
	createContainerReq := &runtimeapi.CreateContainerRequest{
		PodSandboxId:  podID,
		Config:        containerConfig,
		SandboxConfig: podConfig,
	}
	containerResp, err := runtimeClient.CreateContainer(ctx, createContainerReq)
	if err != nil {
		log.Fatalf("Failed to create container: %v", err)
	}
	containerID := containerResp.ContainerId
	fmt.Printf("Container created with ID: %s\n", containerID)

	// 启动容器
	startReq := &runtimeapi.StartContainerRequest{ContainerId: containerID}
	_, err = runtimeClient.StartContainer(ctx, startReq)
	if err != nil {
		log.Fatalf("Failed to start container: %v", err)
	}
	fmt.Println("Container started successfully")
}

可以发现创建容器时与 containerd 的主要通过三个接口通信: RunPodSandbox(), CreateContainer(), StartContainer()。这三个接口均在 cri-api 中定义。

查看 kubelet 相关函数的实现,发现其在 createPodSandbox() 函数中,将容器运行时 runtimeHandler 通过 CRI 接口传递给 containerd。完整调用链如下:

main.main()
app.NewKubeletCommand()
app.Run()
app.run()
app.RunKubelet()
app.startKubelet()
kubelet.Kubelet.Run()
kubelet.Kubelet.syncLoop()
kubelet.Kubelet.syncLoopIteration()
kubelet.Kubelet.HandlePodUpdates()
kubelet.podWorkers.UpdatePod()
kubelet.podWorkers.podWorkerLoop()
kubelet.podSyncerFuncs.SyncPod()
kubelet.Kubelet.SyncPod()
kuberuntime.kubeGenericRuntimeManager.SyncPod()
kuberuntime.kubeGenericRuntimeManager.createPodSandbox()
cri.PodSandboxManager.RunPodSandbox()
kubeGenericRuntimeManager.startContainer()
cri.ContainerManager.CreateContainer()
cri.ContainerManager.StartContainer()

RunPodSandbox 只是 package k8s.io/cri-api/pkg/apis 定义的一个接口函数,究竟是哪个结构体实现的呢?答案是 k8s.io/cri-client,它用来实现 cri-api。

  1. kubelet 首先调用 k8s.io/cri-client/pkgNewRemoteRuntimeService() 初始化 CRI 客户端。
  2. 再调用 k8s.io/kubernetes/pkg/kubelet/kuberuntimeNewKubeGenericRuntimeManager() 将客户端参数 kubeDeps 传递给 kubeGenericRuntimeManager 结构体。

完整调用链如下:

main.main()
app.NewKubeletCommand()
app.Run()
app.run()
kubelet.PreInitRuntimeService(kubeDeps)
cri.NewRemoteRuntimeService(kubeDeps)
app.RunKubelet(kubeDeps)
app.createAndInitKubelet(kubeDeps)
kubelet.NewMainKubelet(kubeDeps)
kuberuntime.NewKubeGenericRuntimeManager(kubeDeps)

再回到 kuscia 代码,它完全未处理 runtimeHandler 变量,导致其无法指定不同的容器运行时。一开始想直接移植 kubelet 对 runtimeHandler 变量处理,发现其还依赖 runtimeClassManager,所以移植还有些麻烦。

再分析 kusica 绑定 cri-client 的过程,其完整调用链如下:

main.main()
start.Start()
start.kusciaModuleManager.Start()
start.kusciaModuleManager.runModule()
modules.Run()
commands.RunRootCommand()
provider.containerRuntimeFactory.BuildPodProvider()
pod.NewCRIProvider()
remoteRuntimeService = remote.NewRemoteRuntimeService()
kuberuntime.NewManager(remoteRuntimeService)

NewCRIProvider() 函数中,发现其还自己实现了一套进程 cri 接口,也就是后端不依赖 containerd,而是完全自己实现整套类似 runc 的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值