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 SDKon_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-idFlag 参考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足以容纳超长单行日志环形缓冲实现 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/stdoutCubelet Mount 命名空间内沙箱 stderr旧版回退/data/cubelet/state/io.containerd.runtime.v2.task/default/sandbox-id/stderrCubelet 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成功则直接读取若文件不存在则设置CUBEMNT1与CUBECLI_LOGS_MODE1环境变量 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前缀匹配到多个沙箱 → 返回ErrAmbiguousambiguous 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在子进程中通过CUBEMNT1环境变量触发 pkg/cubemnt/nsenter.c 的 C 构造函数在Go 运行时尚未启动多线程之前完成命名空间切换随后才运行实际的日志读取逻辑CUBECLI_LOGS_MODE1标识。子进程的退出码通过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),仅供参考