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,核心非常简洁:
// 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):
#[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 配置:
// 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())
// 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:
// 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:
// 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() 接收异步事件通知(如 VsockServerReady、VmShutdown)。
事件监听
hypervisor 通过 channel 向 CubeShim 推送事件:
// 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