Skip to content

Guest Agent:沙箱里面那个 agent 在干什么

cube-agent 是运行在沙箱 VM 内部的守护进程,负责在 guest 侧完成环境准备、容器管理和命令执行。Shim 通过 tRPC over vsock 下发指令,agent 在 guest 内执行真正的操作。

Agent 的身份

agent 在 VM 内有双重身份:

  1. PID 1(init 模式):如果 agent 以 PID 1 启动,它会先完成系统初始化(挂载 cgroups、设置 hostname),然后再启动 tRPC 服务
  2. 普通进程模式:如果已有其他 init 进程,agent 直接启动 tRPC 服务

启动流程

main() 入口

agent 的入口在 agent/src/main.rs

rust
fn main() -> Result<()> {
    let args = AgentOpts::parse();

    if let Some(SubCommand::Init {}) = args.subcmd {
        rustjail::container::init_child();  // 容器 init 子进程
        exit(0);
    }

    if let Some(SubCommand::Exec {}) = args.subcmd {
        cube::rootfs::do_exec_mount();  // exec 挂载处理
        exit(0);
    }

    let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
    rt.block_on(real_main())
}

real_main() 流程

start_sandbox()

rust
async fn start_sandbox(logger, config, init_mode, tasks, shutdown) {
    // 1. 创建 Sandbox 结构体
    let s = Sandbox::new(logger)?;

    // 2. 启动信号处理(SIGCHLD 回收子进程)
    let signal_handler_task = tokio::spawn(setup_signal_handler(logger, sandbox, shutdown));

    // 3. 启动 uevent 监听(设备热插拔)
    let uevents_handler_task = tokio::spawn(watch_uevents(sandbox, shutdown));

    // 4. 启动 tRPC 服务器(监听 vsock)
    let mut server = rpc::start(sandbox, config.server_addr)?;
    server.start().await?;

    // 5. 等待 shutdown → 关机
    rx.await?;
    unsafe { libc::reboot(libc::LINUX_REBOOT_CMD_POWER_OFF) };
}

Sandbox 初始化

Sandbox 结构体(agent/src/sandbox.rs)是 agent 的核心数据结构,管理所有容器和共享资源:

rust
pub struct Sandbox {
    pub id: String,
    pub hostname: String,
    pub containers: HashMap<String, LinuxContainer>,
    pub network: Network,
    pub mounts: Vec<String>,
    pub storages: HashMap<String, u32>,      // storage 引用计数
    pub shared_utsns: Namespace,             // 共享 UTS 命名空间
    pub shared_ipcns: Namespace,             // 共享 IPC 命名空间
    pub sandbox_pidns: Option<Namespace>,    // 共享 PID 命名空间
    pub rtnl: Handle,                        // netlink 句柄
    pub pcimap: HashMap<pci::Address, pci::Address>,  // PCI 地址映射
    // ...
}

当 Shim 调用 CreateSandbox RPC 时,agent 执行以下初始化:

rust
// rpc.rs — create_sandbox
async fn create_sandbox(&self, req: CreateSandboxRequest) {
    // 1. 配置网络接口
    for i in interfaces { sandbox.rtnl.update_interface(&i) }
    sandbox.rtnl.update_routes(routes);
    sandbox.rtnl.add_arp_neighbors(arps);

    // 2. 设置 hostname
    sandbox.hostname = req.hostname;
    sandbox.running = true;

    // 3. 创建共享命名空间
    sandbox.setup_shared_namespaces();  // IPC + UTS

    // 4. 挂载存储
    add_storages(req.storages);  // virtiofs、pmem、disk 等

    // 5. 配置 DNS
    setup_guest_dns(req.dns);
}

gRPC 服务实现

AgentService 实现了 protocols::agent_ttrpc::AgentService trait(agent/src/rpc.rs),提供完整的容器生命周期管理。

AgentService 核心方法

方法功能
create_sandbox初始化沙箱环境(网络、存储、DNS)
destroy_sandbox销毁沙箱,触发关机
create_container创建容器(设备、存储、OCI spec → LinuxContainer)
start_container启动容器(exec + OOM 监控)
remove_container销毁容器,清理资源
exec_process在容器中执行新进程
signal_process向进程发送信号
wait_process等待进程退出,返回 exit code
write_stdin / read_stdout / read_stderr流式 I/O
update_interface / update_routes网络热更新
online_cpu_mem热插拔 CPU/内存

CreateContainer 流程详解

这是 agent 侧最复杂的操作:

关键代码片段

rust
// rpc.rs — do_create_container
async fn do_create_container(&self, req: CreateContainerRequest) -> Result<()> {
    // 1. 添加设备
    add_devices(&req.devices, &mut oci, &self.sandbox).await?;

    // 2. 挂载存储
    let m = add_storages(logger, req.storages, self.sandbox, Some(cid)).await?;
    sandbox.container_mounts.insert(cid, m);

    // 3. 更新命名空间
    update_container_namespaces(&s, &mut oci, use_sandbox_pidns)?;

    // 4. 设置 bundle
    let olddir = setup_bundle(&cid, &mut oci, req.custom_files)?;

    // 5. 创建 LinuxContainer
    let opts = CreateOpts { spec: Some(oci), no_pivot_root: s.no_pivot_root, ... };
    let mut ctr = LinuxContainer::new(&cid, CONTAINER_BASE, opts, &logger)?;

    // 6. 创建并启动进程
    let mut p = Process::new(&logger, &oci.process, &cid, true, pipe_size)?;
    p.open_io(&logger, None)?;
    ctr.start(p).await?;

    // 7. 注册到 sandbox
    s.add_container(ctr);
    Ok(())
}

ExecuteProcess 实现

exec_process 允许在已运行的容器中执行新命令(如 kubectl exec):

rust
// rpc.rs — do_exec_process
async fn do_exec_process(&self, req: ExecProcessRequest) -> Result<()> {
    let sandbox = self.sandbox.lock().await;

    // 1. 获取目标容器
    let ctr = sandbox.get_container(&cid)?;

    // 2. 创建新进程
    let mut p = Process::new(&logger, &ocip, &exec_id, false, pipe_size)?;

    // 3. 打开 I/O(pipe 或 unix socket)
    if req.runtime_unix_addr.is_empty() {
        p.open_io(&logger, None)?;      // pipe 模式
    } else {
        p.open_io(&logger, Some(&req.runtime_unix_addr))?;  // socket 模式
    }

    // 4. 运行进程
    ctr.run(p).await?;
    Ok(())
}

信号处理

agent 作为 PID 1(或 subreaper),负责回收所有子进程(agent/src/signal.rs):

rust
pub async fn setup_signal_handler(logger, sandbox, mut shutdown) {
    set_subreaper(true)?;  // 注册为 subreaper

    let mut sigchild_stream = signal(SignalKind::child())?;

    loop {
        select! {
            _ = shutdown.changed() => break,
            _ = sigchild_stream.recv() => {
                // 遍历所有已退出的子进程
                let result = wait::waitpid(Some(Pid::from_raw(-1)),
                    Some(WaitPidFlag::WNOHANG | WaitPidFlag::__WALL));

                // 找到对应 Process,设置 exit_code
                let p = sandbox.find_process(raw_pid);
                p.exit_code = ret;
                let _ = p.exit_tx.take();  // 通知 WaitProcess RPC
                p.notify_term_close();
            }
        }
    }
}

Console 管理

agent 提供 debug console 功能(agent/src/console.rs),允许通过 vsock 连接到 guest 内的 shell:

rust
pub async fn debug_console_handler(logger, port, mut shutdown) {
    let shell = find_shell();  // /bin/bash 或 /bin/sh

    if port > 0 {
        // vsock 模式:监听 vsock 端口
        let listenfd = socket::socket(AddressFamily::Vsock, ...);
        let addr = SockAddr::new_vsock(VMADDR_CID_ANY, port);
        socket::bind(listenfd, &addr)?;

        // 接受连接,fork shell 进程
        loop {
            select! {
                conn = incoming.next() => {
                    tokio::spawn(run_debug_console_vsock(logger, shell, stream));
                }
            }
        }
    } else {
        // 串口模式:直接连接 /dev/console
        run_debug_console_serial(shell, fd).await;
    }
}

shell 进程通过 PTY 与客户端连接,实现了完整的终端交互。

网络配置

agent 的网络模块(agent/src/network.rs)负责 DNS 配置:

rust
pub fn setup_guest_dns(logger, dns_list: Vec<String>) -> Result<()> {
    // 1. 将 DNS 写入临时文件
    let content = dns_list.join("\n");
    fs::write("/run/cube-containers/sandbox/resolv.conf", &content)?;

    // 2. bind mount 到 /etc/resolv.conf
    mount::mount(
        Some("/run/cube-containers/sandbox/resolv.conf"),
        "/etc/resolv.conf",
        Some("bind"), MsFlags::MS_BIND, None
    )?;
    Ok(())
}

网络接口和路由的管理则通过 netlink(rtnl)在 create_sandbox RPC 中完成。

小结

agent 在沙箱中的角色可以概括为:

职责说明
系统初始化PID 1 模式下挂载文件系统、设置 cgroups
沙箱管理创建共享命名空间、配置网络/存储/DNS
容器管理通过 rustjail 创建和管理 OCI 容器
进程执行支持 exec 新进程、信号发送、流式 I/O
资源监控OOM 监控、CPU/内存热插拔
调试支持vsock debug console

延伸阅读