10 秒挂掉的 Workspace
一个在 CNB(云原生构建平台)上跑了两个月的 GPU workspace,某天突然全部在 10 秒内失败。触发接口返回成功,workspace 状态却立刻变成 error。配置没改过,代码没动过,就是突然不行了。
这种「什么都没动但突然坏了」的故障,最容易让人怀疑是平台问题。但平台不会告诉你具体哪里出了问题——日志里的报错信息还特别有误导性。这篇文章记录了我用四层证据链逐步锁定根因的过程,核心方法论可以迁移到任何 CI/云构建平台的镜像排查场景。

现象:触发成功,但 Workspace 秒挂
项目的 .cnb.yml 配置了一个 dev-gpu workspace,用 GPU 镜像拉起容器做视频处理。定时任务每 12 小时触发一次,一直以来运行正常。
从 8 月 29 日起,所有 dev-gpu workspace 触发后 10-12 秒就报 error。触发接口 POST /-/workspace/start 返回了正常的流水号,说明触发链路本身没问题——问题出在触发之后。
第一步:对照组排除配置问题
项目里还有一个 dev-cpu workspace,用 CPU 镜像运行,配置结构和 dev-gpu 几乎一样。
关键发现:dev-cpu 完全正常。
同一个仓库、同一套 .cnb.yml、同一个触发链路,CPU 没问题 GPU 有问题——排除配置错误和触发故障,问题特定于 GPU workspace。

第二步:构建日志逐行核对(关键证据)
下载 runner 日志后 base64 解码,逐行检查拉取记录。这是整个排查中最关键的一步,也是最容易忽略的方法——大多数人只看报错尾部,不去核对「哪些动作发生了、哪些没发生」。
日志显示:
- 平台辅助镜像全部拉取成功:
git-clone-yyds、vscode-server、git-backup、clear-containers——每一条都有完整的 pull 和 start 记录 - 唯独
gpu-latest业务镜像没有任何 pull 动作——不是 pull 失败,是根本没尝试拉取
「辅助镜像全成功,唯独业务镜像连 pull 都没有」——这不是网络问题,不是认证问题,是 runner 节点上根本不存在这个镜像的缓存,且 runner 没有尝试去 registry 拉取。
第三步:Registry 元数据验证
查镜像仓库的 tag 状态:
| 镜像 | 最后推送 | 拉取次数 | 状态 |
|---|---|---|---|
gpu-latest |
2026-06-24 | 19 次 | 秒挂 |
cpu-latest |
2026-05-15 | 30 次 | 正常 |
gpu-latest 镜像 digest 为 sha256:fc43e6c...,体积 6.79GB。最后一次推送是两个多月前。而 cpu-latest 虽然推送时间更早,但拉取次数更多——说明 CPU 镜像在 runner 节点上有更活跃的缓存命中。
第四步:时间线交叉验证
把 workspace 运行记录和镜像元数据对齐:
| 时间 | 状态 |
|---|---|
| 6 月 25 日 | 最后一次成功运行(耗时 40 分钟) |
| 7 月 3-14 日 | 跑满 11.7 小时后超时回收(标记 error 但实际完成) |
| 8 月 29 日起 | 全部 10 秒秒挂 |
6 月底到 8 月底,将近两个月没有成功拉取过 gpu-latest 镜像。两个月足够让 runner 节点上的 LRU 缓存把这个 6.79GB 的大块头清理掉。
根因:镜像被 Runner LRU 清理
CI/CD 平台的 runner 节点磁盘空间有限,会用 LRU(最近最少使用)策略清理长时间未命中的镜像缓存。gpu-latest 镜像 2+ 个月没被拉取,体积又大(6.79GB),自然被优先清理。
Runner 发现本地没有缓存 → 尝试从 registry 拉取 → 但拉取失败或超时(6.79GB 的大镜像在 10 秒内不可能拉完)→ 容器从未被创建 → 清理阶段执行 docker kill 报 No such container(退出码 1)→ workspace 标记为 error。
报错信息 docker kill: No such container 有强烈的误导性——看起来像是容器生命周期问题,实际根因是容器从未被创建。镜像拉取失败的证据不在报错里,而在「日志中缺失的 pull 行」里。

修复:重新推送镜像
通过 image-builder 分支重新构建并推送 gpu-latest:
git fetch origin && git checkout image-builder
git pull origin image-builder
git commit --allow-empty -m "rebuild gpu-latest image"
git push origin image-builder
构建约 40 分钟完成后,重新触发 dev-gpu workspace 验证。新推送的镜像会重新写入 runner 节点缓存,workspace 恢复正常。
如果你的 CI 平台不支持空提交触发,可以微调一下触发条件——比如在 .cnb.yml 的 ifModify 中加入一个无关紧要的文件变更,或者手动在 Dockerfile 里加一行注释再推送。
方法论:云构建故障证据链四重法
这次排查的核心收获不是根因本身,而是一套可复用的排查框架:
- 对照组:找一个配置相似但正常的实例,快速排除配置和触发问题。CPU 正常 GPU 异常 → 问题在 GPU 特有环节
- 日志逐行核对:不要只看报错,核对每个动作是否发生。辅助镜像全拉取成功但业务镜像没有 pull 行 → 缓存缺失而非拉取失败
- 元数据验证:查镜像的推送时间、拉取次数、digest,用数据代替猜测。两个月没推送 + 低拉取次数 → LRU 清理嫌疑
- 时间线交叉:把「最后成功 → 全面失败」的时间和元数据对齐。两个月空白期正好覆盖了 LRU 清理的窗口
这套方法不限于 CNB,GitHub Actions 的 runner 缓存、GitLab CI 的 Docker layer cache、任何用 LRU 管理镜像缓存的平台都可能遇到同样的问题。
踩坑清单
- 触发成功 ≠ 执行成功:API 返回正常只说明触发链路通了,workspace 状态和 runner 日志才是执行真相
- 报错信息可能是误导:
docker kill: No such container看起来是容器问题,实际是镜像问题。容器从未被创建,kill 自然找不到 - 日志中缺失的动作比报错更有价值:辅助镜像全成功但业务镜像没 pull 行——「没发生」比「报错了」更能定位根因
- 镜像存在 ≠ runner 上有缓存:registry 里有 tag 和 digest,不代表 runner 节点上有缓存。大镜像、长时间不用,会被 LRU 清掉
- 上次能用不代表现在能用:两个月前跑过,不代表 runner 缓存还在。缓存有生命周期,特别是大体积镜像