news 2026/9/15 10:26:09

CubeSandbox 沙箱日志读取指南:cubecli logs 命令的完整实战与底层原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CubeSandbox 沙箱日志读取指南:cubecli logs 命令的完整实战与底层原理

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/stderrcubecli logs命令(本文主题)
envdtask log(子任务日志)沙箱内通过exec派生出的各个子任务的 stdout/stderrE2B 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可以作为一个合法的显式空操作;
  • 行读取缓冲区printHeadprintTail使用bufio.Scanner,初始缓冲 256KB、上限 1MB,足以容纳超长单行日志;
  • 环形缓冲实现 tailprintTail使用长度为 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的执行顺序也因此变为"宿主机新路径优先、命名空间旧路径兜底":

  1. 解析 ID(短 ID 先解析为完整 32 位 ID);
  2. 先尝试宿主机路径/data/cubelet/log/<sandbox-id>/stdout|stderr,成功则直接读取;
  3. 若文件不存在,则设置CUBEMNT=1CUBECLI_LOGS_MODE=1环境变量 re-exec 自身,通过 Cubelet/pkg/cubemnt/nsenter.c 中的 C 构造函数在单线程状态下进入 Cubelet Mount 命名空间,读取旧版 bundle 路径(cubeletStateDir = /data/cubelet/state/io.containerd.runtime.v2.task/default);
  4. 两条路径均不存在时,返回明确报错:"log file not found ... (sandbox may not exist or log forwarding may not be enabled)"。

单元测试 Cubelet/cmd/cubecli/commands/cubebox/logs_test.go 中的TestOpenSandboxLogFromPrefersNewPathTestOpenSandboxLogFromFallsBackToBundle分别验证了"新路径优先"与"新路径缺失时回退旧路径"两种分支。

源码级深入: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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 10:25:45

解压异常、SmartScreen 拦截,Hermes 本地部署故障处理大全

&#x1f50d;前言 许多希望体验 Hermes Agent 办公能力的用户&#xff0c;常常因复杂的环境配置而止步不前。手动下载匹配依赖、反复调整系统目录、处理持续不断的命令行报错、修复权限异常、补全缺失的核心文件——这一系列操作对普通用户而言门槛较高&#xff0c;难以快速体…

作者头像 李华
网站建设 2026/9/15 10:23:44

小苯的序列合并:贪心+优先队列的哈夫曼树解法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 10:21:47

CDIF信号分选算法:从差分直方图到工程参数调优实战

简介&#xff1a;CDIF信号分选&#xff08;相干决策交织频分复用&#xff09;的MATLAB源码与仿真图资源&#xff0c;面向通信工程、信号处理方向的科研人员与算法工程师&#xff0c;适用于多路复用信号的分选、解调及抗干扰性能验证。压缩包共11个文件&#xff0c;含2个m脚本与…

作者头像 李华
网站建设 2026/9/15 10:18:19

Simulink光伏阵列故障仿真:从单二极管模型到IV/PV曲线分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 10:16:59

AI时代为什么离不开PCIe:从链路训练到拓扑,补上算力背后的基础

前两天帮人调一台8卡AI训练服务器&#xff0c;现象很典型&#xff1a;多卡训练时NCCL动不动超时&#xff0c;重试几次又能跑&#xff0c;系统日志里没有任何报错&#xff0c;GPU也都能认到。排查到最后&#xff0c;问题出在PCIe拓扑上——一张GPU被挂到了离CPU很远的Switch下面…

作者头像 李华