CubeSandbox 沙箱日志读取指南:cubecli logs 命令的完整实战与底层原理
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
导读
在 CubeSandbox 中,每个 AI Agent 沙箱本质上是一个轻量级容器,其启动进程(init 进程)的 stdout/stderr 输出是排查镜像问题、调试沙箱启动失败的第一手依据。本文以 sandbox-logs.md 为主体,系统讲解如何通过cubecli logs命令在计算节点上读取沙箱日志与模板构建日志,并结合 Cubelet 与 CubeShim 的源码,深入解析日志文件的落盘位置、Mount 命名空间重入机制、短 ID 解析与安全打开文件的底层实现。读完本文,你将能熟练使用cubecli logs的完整参数集定位沙箱与模板构建问题,并理解这些日志文件从何而来、为何只能在本机读取。
沙箱的两层日志体系
CubeSandbox 为沙箱暴露了两层互补的日志采集能力:
| 层级 | 捕获内容 | 访问方式 |
|---|---|---|
| Sandbox log(沙箱日志) | 容器 init 进程(主入口点)的 stdout/stderr | cubecli logs命令(本文主题) |
envdtask log(子任务日志) | 沙箱内通过exec派生出的各个子任务的 stdout/stderr | E2B SDK(on_stdout/on_stderr回调) |
两者定位不同:沙箱日志反映的是"容器本身启动时发生了什么",而envd任务日志反映的是"沙箱运行后,Agent 每次exec出的独立任务输出了什么"。由于 exec 出的进程并非容器 init 进程,其输出不会出现在沙箱日志文件中,需要通过 E2B SDK 的事件回调在客户端侧实时接收。
本文仅覆盖沙箱级日志,即 init 进程的 stdout/stderr。若需子任务日志,请参考 E2B SDK 的官方文档。
⚠️工作进展提示:日志读取能力仍在持续迭代中。
cubecli logs命令目前是临时方案,其命令行接口与底层存储布局可能在后续版本中调整。
前置条件:cubecli 必须在计算节点上运行
cubecli与 Cubelet 一同构建,并在标准一键部署流程中随 Cubelet 安装到计算节点。阅读沙箱日志有一个关键约束:
- 沙箱日志文件位于Cubelet 的 Mount 命名空间内(或由 CubeShim 写入宿主机指定目录);
- 因此
logs子命令必须在计算节点上直接执行,无法通过 API 远程调用,也无法从非节点主机上执行。
在使用前,请先确认目标沙箱运行在哪台计算节点上,并 SSH 登录到该节点执行命令。
读取沙箱日志:命令与参数全解
cubecli logs的基本用法如下:
# 读取 stdout 的最后 100 行(默认行为) cubecli logs <sandbox-id> # 读取 stderr 的最后 100 行 cubecli logs --stderr <sandbox-id> # 读取完整日志(全部行) cubecli logs --all <sandbox-id> # 读取最后 N 行 cubecli logs --tail 50 <sandbox-id> # 短形式 cubecli logs -t 50 <sandbox-id> # 读取开头 N 行 cubecli logs --head 20 <sandbox-id> # 短形式 cubecli logs -H 20 <sandbox-id>Flag 参考
| Flag | 短形式 | 说明 |
|---|---|---|
--stderr | -e | 读取 stderr 而非 stdout |
--all | -a | 打印全部行;不能与--tail或--head组合使用 |
--tail N | -t N | 打印最后 N 行(未指定其他输出模式 flag 时,默认值为 100) |
--head N | -H N | 打印开头 N 行 |
参数解析的源码细节
这些 flag 的定义与校验逻辑位于 Cubelet/cmd/cubecli/commands/cubebox/logs.go:
- 输出模式互斥校验:
--all与--tail/--head不能同时出现,--tail与--head也互斥,违反时命令直接报错退出; - 默认值仅在"无任何显式 flag"时生效:源码注释明确指出,只有既未设置
--all、也未显式设置--tail/--head时才把tailN置为defaultTailLines = 100。这样做的好处是--tail 0可以作为一个合法的显式空操作; - 行读取缓冲区:
printHead与printTail使用bufio.Scanner,初始缓冲 256KB、上限 1MB,足以容纳超长单行日志; - 环形缓冲实现 tail:
printTail使用长度为 N 的环形数组(buf[count%n])边读边覆盖,读完后再从正确起点顺序输出,避免了为取末尾 N 行而把整个文件载入内存。
读取模板构建日志
在模板构建(template construction)过程中,容器的 stdout/stderr 会被保存到宿主机文件系统的/data/log/template/<templateID>_0/目录下。这些文件不需要进入 Cubelet 的 Mount 命名空间,因此--tpl模式会跳过命名空间重入(re-exec):
# 读取模板构建 stdout 的最后 100 行 cubecli logs --tpl <template-id> # 读取模板构建 stderr 的完整内容 cubecli logs --tpl --all --stderr <template-id>--tpl在 flag 解析中对应--tpl布尔开关,日志路径固定拼接为<templateLogDir>/<templateID>_0/stdout|stderr。源码中路径常量定义为templateLogDir = "/data/log/template",其中_0后缀是沙箱内的容器序号——当前版本每个沙箱只有一个容器,因此该值恒为 0。
从 CubeShim 侧看,模板构建期与普通沙箱运行期的日志写入路径是分开的:CubeShim/shim/src/container/mod.rs 中明确了"模板创建写入/data/log/template/<id>/stdout|stderr,普通沙箱写入/data/cubelet/log/<sandbox-id>/stdout|stderr",这也是--tpl模式无需进入命名空间的根本原因。
日志文件的实际存放位置
| 场景 | 路径 |
|---|---|
| 沙箱 stdout | /data/cubelet/log/<sandbox-id>/stdout(宿主机,CubeShim 写入) |
| 沙箱 stderr | /data/cubelet/log/<sandbox-id>/stderr(宿主机,CubeShim 写入) |
| 沙箱 stdout(旧版回退) | /data/cubelet/state/io.containerd.runtime.v2.task/default/<sandbox-id>/stdout(Cubelet Mount 命名空间内) |
| 沙箱 stderr(旧版回退) | /data/cubelet/state/io.containerd.runtime.v2.task/default/<sandbox-id>/stderr(Cubelet Mount 命名空间内) |
| 模板构建 stdout | /data/log/template/<template-id>_0/stdout(宿主机文件系统) |
| 模板构建 stderr | /data/log/template/<template-id>_0/stderr(宿主机文件系统) |
💡为什么涉及 Mount 命名空间?沙箱日志文件最初由 CubeShim 写入 containerd 的 bundle 目录,该目录只在 Cubelet 的私有 Mount 命名空间内可见。
cubecli logs在宿主机上找不到新路径文件时,会自动把自己 re-exec 进该命名空间后再读取——你无需做任何额外操作,只要在节点上运行命令即可。
新旧两套路径的演进逻辑
值得说明的是,当前仓库中的实现与文档描述存在一次演进:文档记载的路径是命名空间内的 bundle 路径,而当前源码(Cubelet/pkg/sandboxlog/sandboxlog.go)已将新路径定义为宿主机可见的/data/cubelet/log/<sandbox-id>/stdout|stderr,由 CubeShim 直接创建与删除,读取时无需进入命名空间。
cubecli logs的执行顺序也因此变为"宿主机新路径优先、命名空间旧路径兜底":
- 解析 ID(短 ID 先解析为完整 32 位 ID);
- 先尝试宿主机路径
/data/cubelet/log/<sandbox-id>/stdout|stderr,成功则直接读取; - 若文件不存在,则设置
CUBEMNT=1与CUBECLI_LOGS_MODE=1环境变量 re-exec 自身,通过 Cubelet/pkg/cubemnt/nsenter.c 中的 C 构造函数在单线程状态下进入 Cubelet Mount 命名空间,读取旧版 bundle 路径(cubeletStateDir = /data/cubelet/state/io.containerd.runtime.v2.task/default); - 两条路径均不存在时,返回明确报错:"log file not found ... (sandbox may not exist or log forwarding may not be enabled)"。
单元测试 Cubelet/cmd/cubecli/commands/cubebox/logs_test.go 中的TestOpenSandboxLogFromPrefersNewPath与TestOpenSandboxLogFromFallsBackToBundle分别验证了"新路径优先"与"新路径缺失时回退旧路径"两种分支。
源码级深入:logs 命令的四个关键实现机制
1. 短 ID 前缀解析
cubecli logs接受短 ID 前缀而非仅完整 ID。当传入的 ID 不满足^[0-9a-fA-F]{32}$(32 位十六进制)时,命令会通过 Cubelet gRPC 接口调用List拉取全部沙箱,再经由 Cubelet/pkg/sandboxid/resolve.go 的Resolve函数匹配:
- 输入完全匹配某沙箱 ID → 直接返回;
- 作为前缀唯一匹配 → 返回该完整 ID;
- 前缀匹配到多个沙箱 → 返回
ErrAmbiguous("ambiguous sandbox id prefix"); - 无匹配 → 返回
ErrNotFound。
此外,Cubelet/cmd/cubecli/commands/cubebox/resolve.go 还保留了历史兼容行为:若前缀无法匹配沙箱 ID,则尝试按容器 ID(或容器 ID 前缀)匹配,并把唯一匹配映射回所属沙箱 ID。
集成测试 Cubelet/integration/cubebox_logs_shortid_test.go 对 4/8/12 位及完整长度前缀逐一验证了解析正确性,并验证了歧义前缀会被ErrAmbiguous拒绝。
解析完成后,re-exec 前会用replacePositionalArg把命令行中的原始 ID 替换为解析出的完整 ID 传给子进程,保证子进程使用规范化的 32 位 ID 打开文件。相关边界(flag 在前/在后、无匹配、不修改原始切片)均有对应单元测试覆盖。
2. 安全打开日志文件:O_NOFOLLOW 与路径防逃逸
日志文件由 shim 在宿主机上创建,cubecli读取时通过openNoFollow打开(logs.go):
- 先对目录部分执行
EvalSymlinks解析中间符号链接; - 校验解析后的完整路径必须位于预期基目录(如
sandboxlog.Dir = /data/cubelet/log)之内,防止目录穿越攻击; - 最终以
O_RDONLY|O_NOFOLLOW打开,拒绝在最后一级路径组件上跟随符号链接,避免被恶意替换的 symlink 诱导读取任意文件。
3. 命名空间重入:CUBEMNT 的时序设计
re-exec 的时序是刻意的:Go 运行时启动后会创建多线程,而切换 Mount 命名空间(setns)在多线程进程中是受限的。因此cubecli在子进程中通过CUBEMNT=1环境变量触发 pkg/cubemnt/nsenter.c 的 C 构造函数,在Go 运行时尚未启动多线程之前完成命名空间切换,随后才运行实际的日志读取逻辑(CUBECLI_LOGS_MODE=1标识)。子进程的退出码通过cli.Exit原样透传给父进程,保证脚本调用方能拿到准确的退出状态。
4. 沙箱删除即日志删除
日志文件的创建与删除均由 CubeShim 负责。在 CubeShim/shim/src/service/srv.rs 中可以看到,沙箱删除时会同步清理/data/cubelet/log/<id>目录;同理,容器运行期由 shim 维护的 bundle 日志目录也会随沙箱生命周期销毁。因此沙箱一旦被删除,其日志文件即不复存在,需要在沙箱存活期间及时抓取。
范围与限制
使用cubecli logs前请明确以下边界:
- 仅捕获 init 进程(容器内 PID 1)的输出。通过
exec派生的进程输出通过 E2B SDK 的on_stdout/on_stderr回调捕获,不写入本命令读取的文件; - 日志转发依赖 CubeShim 版本:日志转发能力自v0.4.0起可用,更早版本的部署中日志文件会缺失。当前源码中的新路径
/data/cubelet/log则更进一步依赖后续版本(源码注释提及 pre-v0.7.1 的旧 bundle 路径回退),升级 CubeShim 前请核对版本; - 非实时流式输出:目前没有
--follow之类的实时跟随选项,需要重复执行命令以查看新增输出; - 日志随沙箱删除而删除,且必须在计算节点本机执行,无法远程获取。
相关文档
- 服务管理与日志(宿主机侧服务日志、journalctl 与诊断包)
- 模板检查与请求预览
- CLI 工具总览(cubecli 各子命令用法)
- 模板管理(构建模板并获取 template-id)
【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考