蚂蚁密算开发的 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。完整调用链如下:
但 RunPodSandbox 只是 package k8s.io/cri-api/pkg/apis
定义的一个接口函数,究竟是哪个结构体实现的呢?答案是 k8s.io/cri-client
,它用来实现 cri-api。
- kubelet 首先调用
k8s.io/cri-client/pkg
的 NewRemoteRuntimeService() 初始化 CRI 客户端。 - 再调用
k8s.io/kubernetes/pkg/kubelet/kuberuntime
的 NewKubeGenericRuntimeManager() 将客户端参数kubeDeps
传递给kubeGenericRuntimeManager
结构体。
完整调用链如下:
再回到 kuscia 代码,它完全未处理 runtimeHandler
变量,导致其无法指定不同的容器运行时。一开始想直接移植 kubelet 对 runtimeHandler
变量处理,发现其还依赖 runtimeClassManager
,所以移植还有些麻烦。
再分析 kusica 绑定 cri-client 的过程,其完整调用链如下:
在 NewCRIProvider() 函数中,发现其还自己实现了一套进程 cri 接口,也就是后端不依赖 containerd,而是完全自己实现整套类似 runc 的功能。