Skip to content

CubeShim:Shim v2 协议实现

CubeShim 是 CubeSandbox 的容器运行时 shim 层,实现了 containerd 的 Shim v2 协议。它作为 containerd 与底层 hypervisor 之间的桥接层——containerd 通过 tRPC(gRPC 风格的 RPC)下发容器生命周期指令,CubeShim 将这些指令翻译为 hypervisor 操作和 agent 调用。

整体架构

Shim v2 协议要点

containerd 的 Shim v2 协议定义了一套标准的 Task 生命周期接口。每个 shim 进程对应一个 sandbox,通过标准的 tRPC 协议与 containerd 通信。

入口:main.rs

Shim 的入口在 shim/src/main.rs,核心非常简洁:

rust
// shim/src/main.rs — 关键代码
fn main() {
    let c = Config {
        no_reaper: true,
        no_setup_logger: true,
        no_sub_reaper: true,
        ..Default::default()
    };
    // ...
    runtime.block_on(shim_run::<Service>("io.containerd.cube.rs", Some(c)));
}

containerd 根据启动参数中的 action 决定行为:

  • start:fork 出新的 shim 进程,返回 tRPC socket 地址
  • delete:清理 shim 资源,杀死对应 VM
  • 空 action:作为长驻 shim 进程运行,注册 TaskService

Shim trait 实现

Service 结构体实现了 containerd_shim::Shim trait(shim/src/service/srv.rs):

rust
#[async_trait]
impl Shim for Service {
    type T = TaskService;  // Task 服务的实际类型

    async fn new(...) -> Self { /* 解析 id、namespace、debug 标志 */ }

    async fn start_shim(&mut self, opts: StartOpts) -> Result<String, Error> {
        // spawn 新进程,写入 socket 地址文件
        let address = spawn(opts, &grouping, Vec::new()).await?;
        fs::write(ADDRESS_FILE, address.as_bytes())?;
        Ok(address)
    }

    async fn delete_shim(&mut self) -> Result<api::DeleteResponse, Error> {
        // 杀死 shim 进程,清理 sandbox 资源
        tools::signal(shim_pid, Some(Signal::SIGKILL));
        utils::Utils::clean_sandbox_resource(&self.id);
        Ok(api::DeleteResponse::new())
    }

    async fn create_task_service(&self, publisher: RemotePublisher) -> Self::T {
        TaskService::new(self.id, self.ns, self.debug, self.exit, publisher).await
    }
}

Task API 实现

TaskService 实现了 containerd Shim v2 定义的完整 Task 接口(shim/src/service/task_srv.rs),包括:

方法功能
create创建 sandbox(首次)+ 创建容器
start启动容器或 exec 进程
wait等待容器/exec 进程退出
delete删除容器
kill向容器发送信号
pause / resume暂停/恢复 VM
exec在容器中执行新进程
connect重新连接已有的 shim
shutdown关闭 sandbox

CreateSandbox / CreateContainer 流程

这是最核心的流程。当 containerd 首次请求创建容器时,CubeShim 需要先启动 VM(CreateSandbox),然后再在 VM 内创建容器(CreateContainer)。

关键步骤详解

1. Sandbox 初始化(sb.init(spec)

首次 create 请求到来时,SandBox 从 OCI Spec 的 annotation 中解析出完整的 VM 配置:

rust
// sandbox/sb.rs
pub fn init(&mut self, spec: Spec) -> CResult<()> {
    self.spec = spec;
    self.conf = config::Config::new(self.spec.annotations())?;  // 解析 annotation
    let mut vm_dir = PathBuf::from(utils::VM_PATH);
    vm_dir.push(self.id.clone());
    stdfs::create_dir_all(vm_dir)?;
    self.inited = true;
    Ok(())
}

config::Config 从 annotation 中解析出 vCPU、内存、网络接口、virtiofs、pmem、disk 等所有 VM 资源配置。

2. VM 启动(sb.start_vm()

rust
// sandbox/sb.rs
async fn start_vm(&mut self) -> CResult<bool> {
    let mut ch = self.ch.as_mut().unwrap().lock().await;
    ch.launch_vmm().await?;  // 创建 VmmInstance

    // 尝试从 snapshot 恢复,或者全新启动
    if self.by_snapshot() {
        match self.restore_vm().await { ... }
    }
    if !snapshot {
        self.boot_vm().await?;  // prepare_resource → create_vm → boot_vm
    }

    // 等待 vsock 就绪
    let ev = ch.wait_notify(Duration::from_nanos(10_000_000_000)).await?;
    // 期望收到 VsockServerReady 事件
    Ok(snapshot)
}

3. 连接 Agent

VM 启动后,CubeShim 通过 vsock 连接到 guest 内的 agent:

rust
// sandbox/sb.rs
async fn connect_agent(&mut self) -> CResult<()> {
    let conn = AsyncUtils::connect_agent(&self.id).await?;
    let client = agent_ttrpc::AgentServiceClient::new(conn.clone());
    self.conn = Some(Arc::new(Mutex::new(conn)));
    self.client = Some(Arc::new(Mutex::new(client)));
    Ok(())
}

4. 创建容器

Shim 侧的 create_container 将 OCI spec、storage、DNS 等信息通过 tRPC 发送给 agent:

rust
// sandbox/sb.rs
pub async fn create_container(&mut self, id: String, spec: Spec, info: ContainerInfo) -> CResult<()> {
    let client = self.client.as_ref().unwrap();
    let mut c = Container::new(
        self.id.clone(), id.clone(), spec, client.clone(),
        self.log.clone(), self.conf.clone(), info, ...
    )?;
    c.create_container().await?;  // 通过 agent 的 CreateContainer RPC
    containers.insert(id, c);
    Ok(())
}

与 Hypervisor 的交互方式

CubeShim 通过 CubeHypervisor 结构体(shim/src/hypervisor/)与底层 VMM 交互。核心是 cube_hypervisor::VmmInstance——一个封装了 VMM API 的实例。

CubeHypervisor 状态机

关键 API

CubeHypervisor 封装了以下 VMM 操作:

方法底层 API说明
launch_vmm()VmmInstance::new()创建 VMM 实例,设置 seccomp 规则
create_vm()ApiRequest::VmCreate创建 VM,配置 CPU/内存/设备
boot_vm()ApiRequest::VmBoot启动 VM
pause_vm()ApiRequest::VmPause暂停 VM
resume_vm()ApiRequest::VmResume恢复 VM
snapshot_vm()ApiRequest::VmSnapshot快照 VM
restore_vm()ApiRequest::VmRestore从快照恢复
add_dev()ApiRequest::VmAddDevice热添加 PCI 设备
remove_dev()ApiRequest::VmRemoveDevice移除设备
set_fs()ApiRequest::VmSetFs更新 virtiofs 配置

所有操作都通过 VmmInstance::send_request() 统一发送,并通过 wait_notify() 接收异步事件通知(如 VsockServerReadyVmShutdown)。

事件监听

hypervisor 通过 channel 向 CubeShim 推送事件:

rust
// hypervisor/cube_hypervisor.rs
pub async fn wait_notify(&self, timeout: Duration) -> CResult<NotifyEvent> {
    let rx = self.ev_receiver.lock().await;
    rx.recv_timeout(timeout)  // 阻塞等待事件
}

SandBox::monitor_vm() 在后台持续监听这些事件,一旦检测到 VmShutdown,就将所有容器标记为已退出状态。

小结

CubeShim 的设计遵循了 containerd Shim v2 协议的规范,但增加了 sandbox 维度的管理:

  • 一个 shim 进程 = 一个 sandbox = 一个 VM
  • 首次 CreateTask 时启动 VM + 连接 Agent + 调用 CreateSandbox
  • 后续 CreateTask 只需要调用 CreateContainer(复用已有 VM)
  • 所有容器生命周期操作通过 tRPC over vsock 转发给 guest 内的 agent

延伸阅读