日志
本页说明 ctrl、node、E2B 网关与 Exposure 网关怎样输出日志,怎样采集和查询,以及 guest 的控制台与 VMM 日志留在哪里。
概述
每个服务把日志写到 stderr,一行一条 JSON 记录。打包的 systemd 单元把 stderr 交给 journald,容器把它交给容器运行时。Kibotos 自己不采集、不导出日志:部署方在每台宿主上运行采集器,把记录送进自己的日志存储。
guest 内 Kibotos 代理的记录由 node 取回,写进 node 自己的日志。guest 的控制台与 VMM 日志不进入日志流,只保留在 node 宿主上,见控制台与 VMM 日志。
记录格式
每条记录都有下面四个字段:
| 字段 | 取值 |
|---|---|
time | UTC 时间,毫秒精度,例如 2026-10-06T08:12:03.418Z |
level | DEBUG、INFO、WARN、ERROR |
msg | 事件名,例如 workspace command settled。事件名固定,可以直接用于查询与告警 |
component | ctrl、node、guest、e2b、gw |
其余字段随事件而定。一条 node 记录的例子:
{"time":"2026-10-06T08:12:03.418Z","level":"WARN","msg":"instance cleanup pending","component":"node","instance_id":"inst-06gfvmpnqsv7xcsfgz7ybx77pg","step":"volume_attachments","attempt":3,"error":"volume is still attached","suppressed":5,"failing_since":"2026-10-06T08:11:02.004Z","node_id":"node-a","incarnation":12}级别的含义:
| 级别 | 含义 |
|---|---|
ERROR | 不会自己恢复、需要人处理,例如命令的重试预算耗尽 |
WARN | 失败或降级,服务会重试或已经绕过 |
INFO | 状态变化与完成的操作 |
DEBUG | 排查细节,默认不输出 |
调用方自己的错误(参数非法、对象不存在)不记 WARN 或 ERROR。
关联字段
同一个对象在所有服务的记录里用同一个字段名,按这个字段查询就能把一次操作经过的服务串起来:
| 字段 | 对象 |
|---|---|
workspace_id | Workspace;E2B 网关的 sandbox ID 也写在这里 |
instance_id | Workspace 的一次执行在 node 上的 Instance |
node_id、incarnation | node 与它的本次运行。node 进程的每条记录都带这两个字段 |
data_plane_generation | node 数据面进程的一代。数据面进程的记录带它与 node_id,不带 incarnation:一代可以跨过 node 的多次运行 |
egress_generation | node 出口进程的一代。出口进程的记录带它与 node_id,不带 incarnation,与数据面进程相同 |
egress_id | guest 连接经由转发的 Egress |
command_id | ctrl 的命令。node 记录里是 ctrl 下发给 node 的命令 ID,例如 ctrl/workspace-create-fc0e90a06bf735372522aa4e/create,里面包含 ctrl 的命令 ID |
control_session_id | ctrl 与 node 之间的一条控制连接 |
volume_id、transfer_id、exposure_id、template_id、storage_pool | 同名对象 |
principal、serial | 身份的 principal URI 与证书序列号 |
guest_boot_id | guest 的一次启动 |
trace_id、span_id | 写下这条记录时所在的 trace 与 span,小写十六进制。哪些记录带它们见链路追踪的日志字段 |
时长字段以 _ms 结尾,单位是毫秒;error 是错误文本,code 是 RPC 错误码;状态、原因与阶段用小写,例如 running、unavailable。
从一个 Workspace 查到 guest 的步骤:
- 在 ctrl 的记录里按
workspace_id找workspace command settled,它带instance_id、node_id与command_id。 - 在 node 的记录里按
instance_id查这台 node 做了什么。按 ctrl 的命令 ID 做词组查询,会同时匹配 node 记录里派生出的命令 ID。 - 同一个
instance_id下component为guest的记录来自 guest 内的代理。
重复失败
同一件事反复失败时,日志不会每次都写一行:
- 第一次失败立即记录。
- 之后同一对象的同一事件每分钟最多一条,带
suppressed(这期间省略的次数)与failing_since(这一轮开始失败的时间)。 - 会重试的操作恢复时记一条同名的
INFO记录,带recovered: true与failed_ms(持续失败了多久)。
所以「一分钟只有一条」不代表只失败了一次,看 suppressed。
级别设置
级别由环境变量设定,默认 info,取值为 debug、info、warn、error。取值非法时服务拒绝启动。
| 服务 | 环境变量 |
|---|---|
| ctrl | KIBOTOS_CTRL_LOG_LEVEL |
| node | KIBOTOS_NODE_LOG_LEVEL |
| E2B 网关 | KIBOTOS_E2B_LOG_LEVEL |
| Exposure 网关 | KIBOTOS_GW_LOG_LEVEL |
ctrl 与 node 在服务单元里设置。下面的命令打开 node 的单元覆盖文件:
sudo systemctl edit kibotos-node.service在打开的文件里写入下面两行,保存后重启 node:
[Service]
Environment=KIBOTOS_NODE_LOG_LEVEL=debug两个网关写在各自的环境文件里,见 e2b.example.env 与 gw.example.env。
在宿主上查看
journald 保存着最近的日志,采集器不可用时也能直接在宿主上查。下面的命令显示 node 最近一小时的 WARN 与 ERROR:
journalctl -u kibotos-node --since -1h -o cat | jq -c 'select(.level == "WARN" or .level == "ERROR")'下面的命令找出一个 Instance 在这台 node 上的全部记录,包括 guest 代理的:
journalctl -u kibotos-node -o cat | jq -c 'select(.instance_id == "inst-06gfvmpnqsv7xcsfgz7ybx77pg")'容器部署用 kubectl logs 或 docker logs 读同样的记录。
控制台与 VMM 日志
guest 的控制台与 VMM 自己的日志不进日志流。Instance 结束时,node 先把它们保留下来,再清理 Instance 的运行时目录:
- 位置:
<storage.root>/machine-logs/<instance_id>/,例如/var/lib/kibotos/node/machine-logs/inst-06gfvmpnqsv7xcsfgz7ybx77pg/。 - 所有结束方式都保留:按请求停止、强制停止、VM 自行退出、VMM 启动失败。同一个 Instance 再次结束时覆盖上一次的文件。
- 每个文件只保留末尾 1 MiB。保留区总量超过 1 GiB 或超过 1000 个 Instance 时,删掉最旧的 Instance 目录。
- node 日志里只有一条
instance machine exited,带退出码或信号、是否被 OOM 杀掉、运行时长,以及retained(保留目录的路径)。按请求停止后 systemd 已经回收了单元、查不到退出状态时,用exit_status: unobserved代替退出码。guest 自己复位后 node 重启 VMM,成功时再写一条instance machine restarted after guest reset,带restart_count与新的guest_boot_id。
注意:
- 控制台里可能有 Workspace 内程序写出的内容。保留区只有 root 能读,不要把它交给日志采集器。
- 这些文件只能在宿主上读取,ctrl 与网关没有读取它们的接口。
不会出现在日志里的内容
下面这些内容不会写进任何级别的日志:
- 进程的参数、环境变量与输入输出,文件内容,用户给出的路径与文件名,剪贴板与屏幕内容。
- token、私钥、口令、注册密钥、API key 与会话凭证。需要区分时只记 key ID、证书序列号或摘要的前 8 个十六进制字符。
- Exposure 主机名里随机的那部分。它就是访问 Exposure 的凭证;日志里记
exposure_id,解析出 Exposure 之前记 zone 与标签摘要的前缀。 - 带查询串的完整 URL。网关记录路由模式,例如
GET /sandboxes/{id}。
参考部署:Vector、VictoriaLogs 与 Grafana
参考部署用 Vector 在每台宿主上采集,写入 VictoriaLogs 单节点版,用 Grafana 查询。能读 journald 或容器输出、并按 JSON 解析的采集器与存储都可以替换它们。
VictoriaLogs
VictoriaLogs 的启动参数:
| 参数 | 示例 | 说明 |
|---|---|---|
-storageDataPath | /var/lib/victoria-logs | 数据目录,放在本地磁盘 |
-retentionPeriod | 30d | 保留期 |
-httpListenAddr | 10.0.0.6:9428 | 只监听采集器与 Grafana 能访问的内网地址 |
-httpAuth.username、-httpAuth.password | kibotos、口令 | 写入与查询的认证。需要分开写入与查询的权限时,在前面放 vmauth |
Vector
每台宿主运行一个 Vector,读 Kibotos 服务的 journald 记录并解析成字段。参考配置是 deploy/logging/vector.toml,改 endpoints 后即可使用。
- Vector 0.58 起默认不展开配置里的环境变量。口令放在只给 Vector 读取的文件里,用 Vector 的 secret 文件后端引用,参考配置就是这样写的。
- node 的数据面进程每一代是一个以代号命名的单元,都在
kibotos-dataplane.slice里。参考配置按 slice 选中它们,unit统一记为kibotos-dataplane,代号在记录的data_plane_generation字段里。 - 出口进程同样一代一个单元,都在
kibotos-egress.slice里,unit统一记为kibotos-egress,代号在egress_generation字段里。 - stream 字段是
host、component与unit。instance_id、command_id这类字段数量很多,作为普通字段写入,VictoriaLogs 照样能按它们查询。 - 容器部署把
journaldsource 换成 Vector 的kubernetes_logs或docker_logssource,解析部分不变。
Grafana
Grafana 装 VictoriaLogs 数据源插件 victoriametrics-logs-datasource,添加数据源时填 VictoriaLogs 的地址与口令。deploy/logging/grafana-dashboard.json 是一个按组件与级别统计记录数、列出最常见的 WARN 与 ERROR 的看板,可以直接导入。
看板的变量 filter 填一个 LogsQL 过滤条件,作用于每个面板,默认 *。链路追踪看板的链接按 uid kibotos-logs 打开这个看板,并把 filter 设为 trace_id: 加 Trace ID,所以导入时保留这个 uid。
常用查询(LogsQL):
| 想找 | 查询 |
|---|---|
| 一个 Workspace 的全部记录 | workspace_id:"ws-06gcx267w1zffcb3w06zhv5z9c" |
| 一台 node 上一个 Instance 的记录,包括 guest 代理 | instance_id:"inst-06gfvmpnqsv7xcsfgz7ybx77pg" |
| 失败结算的命令 | msg:"workspace command settled" level:WARN |
最近一小时最多的 WARN 与 ERROR | _time:1h level:in(WARN,ERROR) | stats by (component, msg) count() n | sort by (n desc) |
注意
- 采集器与存储要能从 Kibotos 宿主访问,租户不能访问:日志里有对象 ID 与错误信息。
- 日志量随 Workspace 操作次数增长。先在一台 node 上观察一天的写入量,再按保留期估算磁盘。