CubeSandbox 组件日志排查指南:定位各组件日志路径、使用 cubecli logs 与查看 guest kernel 日志
2026/9/16 22:50:57 网站建设 项目流程

CubeSandbox 组件日志排查指南:定位各组件日志路径、使用 cubecli logs 与查看 guest kernel 日志

【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox

CubeSandbox 由 CubeAPI、CubeMaster、Cubelet、CubeShim、Hypervisor(VMM)、Cubelet 内置 network runtime 以及 cube-proxy 等多个组件组成,排障时最大的困惑往往在于"这条报错该去哪个日志文件里找"。本文以一键部署(systemd 托管)场景为准,系统汇总各组件业务日志的确切路径与查看方式,并深入讲解cubecli logs读取沙箱/模板日志的原理、在 CubeShim 日志中直接查看 guest kernel(虚拟机内核)启动输出的方法,以及两种无需重启即可将 Cubelet 切换到 debug 级别日志的途径。读完本文,你可以在一分钟内根据报错定位到对应日志文件,并在复现问题时拿到足够详细的调试信息。

适用环境与前提

本文内容面向 CubeSandbox v0.4.0 及以上版本的一键部署环境(systemd 托管),all-in-one 单机与多机集群部署方式均适用。涉及组件包括 CubeAPI、CubeMaster、Cubelet、CubeShim、Hypervisor(VMM)、Cubelet 内置 network runtime 与 cube-proxy。宿主机为运行 Cubelet 的 Linux 主机,以下命令中的路径均指宿主机文件系统路径。

根因分析:CubeSandbox 的三类日志

CubeSandbox 的日志在来源和查看方式上分成三类,三者互不重叠,排查时必须先判断报错属于哪一类:

  1. 业务行为日志:请求、调度、审计、VMM 创建过程等运行时行为日志,各组件直接写入/data/log/<Module>/目录下的文件中,不会进入journalctl
  2. 启动期日志:进程被 systemd 拉起到稳定运行(或失败退出)期间的 stdout/stderr,只能通过journalctl -u <unit>查看。这部分内容量很小,仅覆盖启动/退出窗口。
  3. 容器沙箱/模板日志:沙箱内 init 进程(容器 PID 1)的 stdout/stderr。沙箱日志由 CubeShim 写入 Cubelet 的私有挂载命名空间内,必须用cubecli logs读取;模板构建日志则直接落在宿主机文件系统,无需进入命名空间。

guest kernel(虚拟机内核)的启动日志本质上属于"容器 init 进程输出"的范畴:CubeShim 通过内核参数console=hvc0接管 guest 内核的控制台输出(包括Linux version ...之类的启动信息),并转发进同一条cube-shim-req.log,因此guest kernel log 直接在 CubeShim 的日志文件中就能看到,不需要任何额外工具或挂载。

此外,Cubelet 默认日志级别较低(warn),常规运行时不会打印详细的调试信息。打开 debug 级别有两种方式:修改动态配置文件热更新,或者调用内置 HTTP 接口临时切换(无需重启进程)。

各组件日志速查表

下表汇总了一键部署场景下各组件日志的确切路径与查看方式:

组件日志类型路径查看方式
CubeAPI业务请求日志(按天滚动)/data/log/CubeAPI/cube-api-YYYY-MM-DD.logtail -F
CubeMaster业务请求日志/data/log/CubeMaster/cubemaster-req.logtail -F
CubeMaster启动期日志/data/log/CubeMaster/cubemaster.logtail -F
Cubelet业务请求日志/data/log/Cubelet/Cubelet-req.logtail -F
Cubelet统计/指标日志/data/log/Cubelet/Cubelet-stat.logtail -F
Cubelet启动期日志journalctl -u cube-sandbox-cubelet.service
CubeShim业务请求日志(含 guest kernel 输出)/data/log/CubeShim/cube-shim-req.logtail -F
CubeShim统计日志/data/log/CubeShim/cube-shim-stat.logtail -F
Hypervisor (VMM)VMM 创建过程日志/data/log/CubeVmm/vmm.logtail -F
cube-proxy访问/错误日志/data/log/cube-proxy/{access,error}.logtail -F(见下文)
沙箱容器init 进程 stdout/stderr/data/cubelet/log/<sandbox-id>/{stdout,stderr}(新版本,宿主机直读);旧版本在/data/cubelet/state/io.containerd.runtime.v2.task/default/<sandbox-id>/{stdout,stderr}(Cubelet 挂载命名空间内)cubecli logs <sandbox-id>(见下文)
模板构建构建容器 stdout/stderr/data/log/template/<template-id>_0/{stdout,stderr}(宿主机文件系统)cubecli logs --tpl <template-id>

提示:业务日志不在 journalctl 与 服务管理与日志 中说明的一致:以上业务/请求类日志全部直接写到/data/log/<Module>/目录,journalctl里只能看到进程启动/退出期间很少量的 stdout/stderr。

其中沙箱容器日志的路径值得单独说明。从 Cubelet/cmd/cubecli/commands/cubebox/logs.go 的源码注释与实现可以看到,新版本 CubeShim 会把日志转发到宿主机路径/data/cubelet/log/<sandboxID>/stdout|stderr(对应 Cubelet/pkg/sandboxlog/sandboxlog.go 中定义的Dir = "/data/cubelet/log");仅当该路径不存在时才回退到旧的 containerd bundle 路径(Cubelet 挂载命名空间内)。cubecli会优先尝试宿主机路径直读,命中旧路径时才通过CUBEMNT=1重入挂载命名空间读取,这也是cubecli logs必须直接在计算节点上执行的原因。

用 cubecli logs 查看沙箱 / 模板日志

cubecli随 Cubelet 一同构建,一键部署时自动安装。沙箱日志文件位于 Cubelet 的私有挂载命名空间内(或由 CubeShim 转发到宿主机路径),因此cubecli logs必须直接在计算节点上执行,无法远程调用。

# 沙箱 stdout,最后 100 行(默认) cubecli logs <sandbox-id> # 沙箱 stderr,最后 100 行 cubecli logs --stderr <sandbox-id> # 沙箱完整日志 cubecli logs --all <sandbox-id> # 沙箱最后 N 行 / 前 N 行 cubecli logs --tail 50 <sandbox-id> cubecli logs --head 20 <sandbox-id> # 模板构建日志:直接读宿主机文件系统,跳过命名空间切换 cubecli logs --tpl <template-id> cubecli logs --tpl --all --stderr <template-id>

参数说明:

参数简写说明
--tpl把 id 当作模板 ID,从/data/log/template/<id>_0/读取,无需进命名空间
--stderr-e读取 stderr,默认读取 stdout
--all-a输出全部行;不可与--tail/--head同时使用
--tail N-t N输出最后 N 行(默认 100)
--head N-H N输出前 N 行

上述命令与参数与 logs.go 中的 CLI 定义完全一致:默认行为是当--all--tail--head均未显式指定时输出最后 100 行;--all--tail/--head互斥、--tail--head互斥,同时指定会直接报错退出。实现上,--tail采用环形缓冲区只保留末尾 N 行,--head用 Scanner 顺序输出前 N 行,读取时使用了 256 KiB 的缓冲,能应对单行较长的日志内容。

几个值得注意的工程细节:

  • 短 ID 前缀解析:传入的 id 如果不是完整的 32 位 sandbox ID,cubecli logs会先通过 Cubelet 的 gRPCList接口拉取沙箱列表,把短前缀解析为完整 ID 后再去读日志文件。
  • 路径安全校验:日志文件通过O_NOFOLLOW(拒绝打开符号链接)打开,并校验解析后的真实路径必须落在日志根目录之内,防止目录穿越攻击。
  • 模板日志的_0后缀_0是沙箱内容器索引,单容器模板恒为 0,构建日志路径即为/data/log/template/<template-id>_0/{stdout,stderr},直接读取宿主机文件系统即可,无需进入命名空间。

cubecli logs只覆盖**容器 init 进程(PID 1)**的输出。通过 E2Bexec接口在沙箱内启动的子任务,其 stdout/stderr 需要用 E2B SDK 的on_stdout/on_stderr回调获取,不在本文范围内,详见 沙箱日志。

沙箱删除后,对应日志文件会一并清除;日志转发需要 v0.4.0 及以上版本的 CubeShim。

Guest kernel 日志:直接在 CubeShim log 里看

CubeShim 在启动虚拟机时,通过内核参数console=hvc0把 guest 内核的控制台输出接管过来,和沙箱容器 init 进程日志一样,统一写进 CubeShim 的请求日志文件:

LC_ALL=C sudo grep -a -E "(<sandbox-id 或 InstanceId>|Linux version)" /data/log/CubeShim/cube-shim-req.log

cube-shim-req.log是逐行 JSON,日志本体在LogContent字段里。典型的 guest 内核启动记录形如:

{"Module":"Shim","InstanceId":"<sandbox-id>","ContainerId":"<sandbox-id>","Timestamp":"...","LogContent":"[ 0.000000] Linux version 6.12.33-cube.sandbox.pvm.guest-... #2 SMP PREEMPT_DYNAMIC ...","FunctionType":""}

InstanceId(即 sandbox ID)过滤即可拿到对应沙箱完整的内核启动输出,包括设备探测、EXT4-fsrootfsmount 等信息,无需额外工具或进入命名空间。

JSON 行格式可以在 CubeShim/shim/src/log/mod.rs 中得到印证:LogItem结构体使用serde(rename_all = "PascalCase")序列化,字段依次为ModuleInstanceIdContainerIdTimestampLogContentFunctionType;日志目录与文件名常量分别定义为/data/log/CubeShim/cube-shim-req.logcube-shim-stat.log

为什么在这里,不在别处console=hvc0是 x86_64 虚拟机的串口(virtio console)控制台,内核所有printk输出都会走这条通路。CubeShim 在 hypervisor/config.rs 中为 x86_64 追加console=hvc0(arm64 平台则使用console=ttyAMA0,115200),并把这条控制台流量直接转发进它自己的日志管道(与普通请求日志共用同一个 writer),所以查 guest kernel 日志不需要额外挂载或调试工具,直接 grepcube-shim-req.log即可。

cube-proxy 宿主机日志

cube-proxy是基于 OpenResty 的 nginx 容器。一键部署会将宿主机目录/data/log/cube-proxy/bind mount 到容器内同一路径,可以直接从宿主机读取日志:

tail -F /data/log/cube-proxy/error.log tail -F /data/log/cube-proxy/access.log

容器内仍使用同一个/data/log/cube-proxy/路径,底层由宿主机目录提供。该路径配置可以在 CubeProxy/nginx.conf 中找到:error_log /data/log/cube-proxy/error.log notice;access_log /data/log/cube-proxy/access.log access;

文件内容
access.lognginx 访问日志
error.lognginx 错误日志

开启 Cubelet debug 级别日志

Cubelet 默认日志级别是warn,排障时往往需要临时调高到debug才能看到足够的调试信息。有两种方式,二者都不需要重启 Cubelet 进程

方式一:改动态配置文件(持久生效,热加载)

Cubelet 会以 10 秒为周期轮询动态配置文件dynamicconf/conf.yaml,检测到变化后自动热加载并调用日志级别更新逻辑。在common:段落下加一行log_level

common: enable_pf_mode: false log_level: debug # 新增这一行 sandbox_exec_cmd_time_out: 5s ...

一键部署环境下该文件的默认路径是:

/usr/local/services/cubetoolbox/Cubelet/dynamicconf/conf.yaml

保存后等待约 10 秒(配置轮询周期),Cubelet 会自动切到 debug 级别,此后/data/log/Cubelet/Cubelet-req.log会打出更细粒度的记录。

源码层面,这一机制的链路清晰可见:配置结构体在 Cubelet/pkg/config/config.go 中定义了LogLevel string \yaml:"log_level"`字段,并通过hotswap.NewWatcher(configPath, 10, &Config{})创建 10 秒轮询的 watcher;[Cubelet/pkg/hotswap/file.go](https://link.gitcode.com/i/e7157f8be0800d5df10f2c1c5bbc667b) 的实现同时使用 fsnotify 事件与 10 秒 ticker 做双重保障——即使文件被原子替换(remove + create)也能在下个 tick 捕获到;配置变化后触发 [Cubelet/services/server/server.go](https://link.gitcode.com/i/c7d5e568c62d0825b1f4340323212525) 中的OnEvent,将conf.Common.LogLevel通过CubeLog.SetLevel应用到日志框架。dynamicconf/conf.yaml的完整内容可参考 [Cubelet/dynamicconf/conf.yaml](https://link.gitcode.com/i/4f8c1d6219244c0053bf7a115d4d96f9),其中common:段还包含enable_pf_modesandbox_exec_cmd_time_outdefault_dns_servers` 等其余运行参数。

debug 级别可能使单次请求的日志量放大一个数量级,高吞吐场景下会带来磁盘 I/O 争用和 CPU 开销——开启 debug 期间建议关注/data/log/磁盘占用。排障结束后记得把这一行删掉或改回info/warn,避免长期产生大量日志。

方式二:调试端口临时切换(不改文件,进程重启后失效)

Cubelet 内置了一个 debug HTTP 端口(默认监听:9966,对应 Cubelet/config/config.toml 里的[debug] address配置),暴露了/debug/loglevel接口,可以直接调用来临时切换级别:

# 切到 debug curl -X POST 'http://127.0.0.1:9966/debug/loglevel?level=debug' # 排障结束后切回 info curl -X POST 'http://127.0.0.1:9966/debug/loglevel?level=info'

这种方式立即生效、无需等待轮询周期,但只在当前进程生命周期内有效——Cubelet 重启后会恢复到配置文件里写的级别(默认warn)。适合临时抓一段现场,而不想改配置文件的场景。

该接口的实现位于 Cubelet/services/server/server.go 的ServeDebug中:/debug/loglevel处理器读取请求中的level参数并同时调用CubeLog.SetLevellogrus.SetLevel完成切换。注意该接口不返回 JSON,只接受POST请求携带level表单参数。

同一个 debug 端口下还挂载了pprof相关的 profiling 接口(/debug/pprof/*,以及expvar/debug/vars),性能问题排查时也可以使用。安全提示:pprof 会暴露 goroutine 栈、堆内存 profile 和源码片段等信息——请确保:9966仅监听 localhost,不要通过反向代理暴露,也不要绑定到0.0.0.0

警告:只对新启动的沙箱生效 无论用哪种方式切到 debug,已经处于运行中的沙箱不会补出更详细的日志——它们创建/启动阶段的日志早已按之前的级别打完,不会因为后来调高了级别而重新打印。要看到完整的 debug 级别日志,需要在切换级别之后重新创建/启动沙箱。因此排障时建议的顺序是:先切到 debug,再复现问题(新建沙箱触发问题场景),而不是对着一个已经在跑的沙箱切日志级别等日志变详细。

参考资料

  • 相关文档:
    • 沙箱日志
    • 服务管理与日志
  • 相关代码:
    • Cubelet/cmd/cubecli/commands/cubebox/logs.go(cubecli logs实现)
    • Cubelet/services/server/server.go(/debug/loglevelHTTP 接口)
    • Cubelet/pkg/config/config.go、Cubelet/pkg/hotswap/file.go(动态配置热加载)
    • CubeShim/shim/src/log/mod.rs(CubeShim 日志与 console 转发实现)
    • CubeShim/shim/src/hypervisor/config.rs(console=hvc0内核参数配置)
    • Cubelet/config/config.toml(debug 端口、日志滚动等运行配置)
    • CubeProxy/nginx.conf(cube-proxy 访问/错误日志路径)

【免费下载链接】CubeSandboxInstant, Concurrent, Secure & Lightweight Sandbox for AI Agents.项目地址: https://gitcode.com/GitHub_Trending/cu/CubeSandbox

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询