从 guest 到 host:请求怎么出来的
在 CubeSandbox 的架构中,沙箱内部(guest)和宿主机(host)之间需要可靠的通信通道。这篇文章详细分析 guest 内的 agent 如何通过 vsock 与 host 侧的 shim 通信,以及整个请求链路是如何建立的。
通信架构概览
核心问题:shim 运行在 host 的用户态,agent 运行在 guest 的内核态,两者如何通信?
答案是 vsock(Virtual Socket)——一种专为虚拟化场景设计的 socket 类型。
vsock:虚拟化世界的 Socket
vsock 是 Linux 内核提供的 AF_VSOCK 地址族,专门为 host-guest 通信设计:
- 不依赖网络:不需要 IP 地址、不需要网卡,直接通过 hypervisor 的虚拟设备传输
- 可靠有序:与 TCP 一样是面向连接的、可靠的、有序的字节流
- 低延迟:绕过了整个网络协议栈,数据在 hypervisor 内部转发
vsock 地址
vsock 使用 (CID, Port) 二元组寻址:
| 角色 | CID | Port |
|---|---|---|
| Host(任意进程) | VMADDR_CID_HOST | 自定义 |
| Guest(agent) | VMADDR_CID_ANY | 固定端口 |
通道建立流程
CubeShim 侧:连接建立
1. 配置 vsock
在 prepare_resource() 阶段,CubeShim 将 vsock 配置注入 VM 配置:
// sandbox/sb.rs — prepare_resource()
pub async fn prepare_resource(&mut self) -> CResult<VmConfig> {
let mut vc = VmConfig::default();
vc.set_kernel(self.conf.kernel.clone())
.set_vcpus(self.conf.vm_res.cpu)
.set_memory(self.conf.vm_res.memory, false)
.add_nets(&self.conf.net)?
.add_disks(&self.conf.disk)
.add_virtiofs(&self.conf.virtiofs)
.add_vsock(self.id.clone()); // ← 关键:配置 vsock
// ...
Ok(vc)
}add_vsock() 会生成 vsock 设备配置,包括 sandbox ID(用于标识连接)。
2. 等待 vsock 就绪
VM 启动后,hypervisor 会通过事件通道通知 shim "vsock 已就绪":
// sandbox/sb.rs — start_vm()
async fn start_vm(&mut self) -> CResult<bool> {
// ... launch vmm, boot vm ...
let ch = self.ch.as_mut().unwrap().lock().await;
let ev = ch.wait_notify(Duration::from_nanos(10_000_000_000)).await?;
if CH::NotifyEvent::VsockServerReady != ev {
return Err(format!("Not an expected event: {:?}", ev));
}
// vsock 通道已就绪,可以连接 agent 了
Ok(snapshot)
}3. 建立 tRPC 连接
收到 VsockServerReady 后,CubeShim 通过 vsock 连接到 agent:
// sandbox/sb.rs — connect_agent()
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(())
}AsyncUtils::connect_agent() 内部做的事情:
- 创建
AF_VSOCKsocket - 连接到 guest 的 vsock 端口(sandbox ID 用于路由)
- 包装为 tRPC client(基于 gRPC 协议)
4. 发送 RPC 请求
连接建立后,shim 通过 tRPC client 调用 agent 的方法:
// 示例:创建 sandbox
client.create_sandbox(ctx, &req).await?;
// 示例:创建容器
client.create_container(ctx, &req).await?;
// 示例:等待进程退出
client.wait_process(ctx, &req).await?;Agent 侧:监听和处理
1. 启动 tRPC 服务器
agent 在 start_sandbox() 中启动 tRPC 服务器,监听 vsock:
// main.rs — start_sandbox()
async fn start_sandbox(logger, config, init_mode, tasks, shutdown) {
let s = Sandbox::new(logger)?;
let sandbox = Arc::new(Mutex::new(s));
// 启动 tRPC 服务器
let mut server = rpc::start(sandbox, config.server_addr.as_str())?;
server.start().await?;
// server_addr 格式类似 "vsock:///dev/vsock, port"
// agent 监听 VMADDR_CID_ANY 上的指定端口
}2. 注册服务
rpc::start() 将 AgentService 注册为 tRPC 服务:
// rpc.rs
pub fn start(sandbox: Arc<Mutex<Sandbox>>, server_address: &str) -> Result<TtrpcServer> {
let agent_service = AgentService { sandbox };
let agent_ttrpc_service = agent_ttrpc::create_agent_service(agent_service);
let mut server = TtrpcServer::new()
.bind(server_address)?
.register_service(agent_ttrpc_service);
Ok(server)
}3. 处理请求
当 shim 发来 RPC 请求时,tRPC 框架自动路由到 AgentService 的对应方法:
// rpc.rs — AgentService trait 实现
#[async_trait]
impl agent_ttrpc::AgentService for AgentService {
async fn create_container(&self, ctx: &TtrpcContext, req: CreateContainerRequest) -> ttrpc::Result<Empty> {
trace_rpc_call!(ctx, "create_container", req);
is_allowed!(req); // 权限检查
self.do_create_container(req).await
.map_err(|e| ttrpc_error!(ttrpc::Code::INTERNAL, e))?;
Ok(Empty::new())
}
async fn exec_process(&self, ctx: &TtrpcContext, req: ExecProcessRequest) -> ttrpc::Result<Empty> {
// ...
}
async fn wait_process(&self, ctx: &TtrpcContext, req: WaitProcessRequest) -> ttrpc::Result<WaitProcessResponse> {
// ...
}
// ... 其他方法
}完整请求链路
以 kubectl exec 为例,展示一个命令从用户到执行的完整链路:
流式 I/O 的实现
对于 exec 和 attach 等场景,需要双向流式 I/O。agent 通过 pipe 或 PTY 实现:
Pipe 模式
// agent/rpc.rs
async fn do_exec_process(&self, req: ExecProcessRequest) -> Result<()> {
let mut p = Process::new(&logger, &ocip, &exec_id, false, pipe_size)?;
p.open_io(&logger, None)?; // 创建 stdin/stdout/stderr pipes
ctr.run(p).await?;
Ok(())
}
// 读取 stdout
async fn do_read_stream(&self, req: ReadStreamRequest, stdout: bool) -> Result<ReadStreamResponse> {
let reader = if p.term_master.is_some() {
p.get_reader(StreamType::TermMaster) // PTY 模式
} else if stdout {
p.get_reader(StreamType::ParentStdout) // Pipe 模式
} else {
p.get_reader(StreamType::ParentStderr)
};
let data = read_stream(reader, req.len).await?;
Ok(ReadStreamResponse { data })
}TTY 模式
当请求分配 TTY 时,agent 使用 PTY(pseudo-terminal):
// agent/console.rs
fn run_in_child(slave_fd: c_int, shell: String) -> Result<()> {
setsid()?;
dup2(slave_fd, STDIN_FILENO)?;
dup2(slave_fd, STDOUT_FILENO)?;
dup2(slave_fd, STDERR_FILENO)?;
unsafe { libc::ioctl(0, libc::TIOCSCTTY); } // 设置控制终端
execvp(cmd, &args); // 执行 shell
}master 端通过 pipestream 与 vsock 双向转发数据。
健康检查与 VM 监控
CubeShim 在后台持续监控 guest 的健康状态:
// sandbox/sb.rs — monitor_vm()
async fn monitor_vm(&self, check_agent: bool) -> CResult<(Sender<()>, JoinHandle<()>)> {
let client = health_ttrpc::HealthClient::new(conn); // 健康检查 client
let handle = tokio::spawn(async move {
loop {
// 检查 hypervisor 事件
if let Ok(ev) = ch.try_wait_notify() {
if ev == CH::NotifyEvent::VmShutdown {
// VM 已关闭,标记所有容器为 exited
*state = SandBoxState::Exited;
for (_, container) in containers.iter() {
container.notify_vm_shutdown().await;
}
break;
}
}
// 定期 ping agent
if check_agent && counter > interval {
client.check(ctx, &req).await?; // 健康检查 RPC
}
sleep(Duration::from_millis(1000)).await;
}
});
Ok((tx, handle))
}cube-runtime CLI
CubeShim/cube-runtime/ 提供了一个命令行工具,用于管理 VM 快照等操作:
# 创建快照
cube-runtime snapshot create ...
# 登录到 guest
cube-runtime login ...
# Shell 补全
cube-runtime completions bash这个工具通过 cube_hypervisor::VmmInstance API 与 hypervisor 交互,复用了与 shim 相同的底层通道。
小结
整个 guest → host 通信链路可以总结为:
| 层级 | 组件 | 协议 | 说明 |
|---|---|---|---|
| containerd ↔ Shim | UDS (Unix Domain Socket) | tRPC/gRPC | 标准 Shim v2 协议 |
| Shim ↔ Agent | vsock | tRPC/gRPC | hypervisor 内部转发 |
| Agent ↔ 容器 | pipe / PTY | 本地 I/O | 进程间通信 |
vsock 是整个架构的关键——它提供了一种不依赖 guest 网络栈的可靠通信通道,使得即使 guest 没有配置网络,shim 也能管理和控制容器。