CNB GPU Workspace 启动即秒挂排查:镜像被 Runner 清理的证据链诊断

2026-09-01

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 里加一行注释再推送。

方法论:云构建故障证据链四重法

这次排查的核心收获不是根因本身,而是一套可复用的排查框架:

  1. 对照组:找一个配置相似但正常的实例,快速排除配置和触发问题。CPU 正常 GPU 异常 → 问题在 GPU 特有环节
  2. 日志逐行核对:不要只看报错,核对每个动作是否发生。辅助镜像全拉取成功但业务镜像没有 pull 行 → 缓存缺失而非拉取失败
  3. 元数据验证:查镜像的推送时间、拉取次数、digest,用数据代替猜测。两个月没推送 + 低拉取次数 → LRU 清理嫌疑
  4. 时间线交叉:把「最后成功 → 全面失败」的时间和元数据对齐。两个月空白期正好覆盖了 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 缓存还在。缓存有生命周期,特别是大体积镜像
标签: CI/CD 云构建 排错