1400 字
7 分钟
静默降级把 46 次失败变成 46 个坏沙箱

建沙箱要读镜像自带的启动命令。读不到的那条分支原来写的是「当没事发生,继续建」。9-13 这天读了 46 次全部失败,46 次创建照常返回成功,镜像里的服务一个都没起。我的判断是:读不到就该当场报错。继续建不会让系统更稳,只是把这次失败挪到更晚、更难查的地方。

静默降级与快速失败的两条路径

为什么要去读这条启动命令#

Kubernetes 里只要给容器设了 command,镜像 Dockerfile 写的 ENTRYPOINT 就整个被替换掉,不是在它后面追加。

沙箱引擎无条件设这个字段:它要把 bootstrap.sh 放在最前面,装好执行代理再 exec "$@"。结果是调用方自带镜像里的 ENTRYPOINT 永远不会执行。

补救办法是服务端主动读回来。按 Registry v2 协议取 manifest,再取 config blob,拿到里面的 Entrypoint 和 Cmd 拼成一条 argv,放回 command 尾部。只读几 KB 的 JSON,不拉镜像层。

读得到就拼回去,调用方什么都不用改。读不到的那条分支,原来是记一条 INFO 日志,然后让容器以 sleep infinity 启动。

Pod 起来了,状态 Running,执行代理健康,创建接口返回 201。镜像里的启动命令一行没跑。

一条网络策略,46 次读取全失败#

9-11 起实验集群的 Pod 到镜像仓公网 443 的出口被拦掉。服务跑在 Pod 网络里,读镜像元数据这一步全部超时。

9-13 这天 46 次读取没有一次成功,46 次创建全部返回成功。失败结果本身会缓存 60 秒,所以受影响的沙箱只多不少,我没有单独统计。

坏在失败被推迟。调用方拿到 sandbox_id,以为镜像里的评测服务在跑,exec 进去调它的端口,连不上。这时距离创建已经隔了很久,那 46 条 INFO 日志早刷过去了。症状是「端口不通」,根因在创建那一步,中间隔着整条链路,从症状倒推不回去。

网络后来修好了(给镜像仓加内网 hostAliases),但那只解决这一次的 46 次。真正要改的是那条分支本身。

读不到就报错,错误要带三个字段#

9-14 先做的是让失败可见:创建结果新增 notices 字段,自定义镜像没传启动命令时必给一条,写明是「服务端读到了 X 代为启动」还是「没读到,沙箱以 sleep 启动,镜像里的 ENTRYPOINT/CMD 不会执行」;读取失败的日志从 INFO 提到 WARNING。行为一点没动:仍然自动读,读不到仍起 sleep 沙箱,不拒绝任何请求。

当场失败是再往后一步的事:自定义镜像既没传启动命令、服务端也读不到,就直接 400 拒掉。启动命令这个信息本来就在调用方手里,不该每次建沙箱都回源去镜像仓读。镜像仓一旦进了建沙箱的热路径,它的可用性就等于建沙箱的可用性。

直接拒绝等于把判断推给调用方:这次失败是不是自己少传了启动命令,要不要重试。所以错误体要带 code、retryable、executed 三个字段。executed 有三个取值:没执行、不确定、已执行。手写的 HTTP 客户端只认状态码,碰上「不确定」就会把同一条命令再跑一遍,要分清这三种情况只能靠这个字段。

要能快速失败,先得报出创建到了哪一步#

错误体之外,创建过程本身也要报阶段:排队 → 已调度 → 拉镜像中 → 就绪 → 失败(带原因)。

创建接口现在是同步等满 300 秒再回一个 504,调用方只能靠超时猜,猜完它会删掉沙箱重建一个。被删的 Pod 在镜像拉完之前仍占着名额和拉取并发(这一步是推断,我没有逐层核过 kubelet),于是再冷拉一次。生产集群某个时刻有 292 个 Pod 卡在 Terminating,距离删除时间中位 15 分钟,其中 286 个的主容器还在 PodInitializing。

「就绪」的判据也要跟着改。Pod Running 不代表能用,要等真的能跑一条命令才报就绪。

400 推迟了#

这个 400 原定 9-21 上线,没上。

到期前查了一周日志,自定义镜像没传启动命令的约 2.9 万次,其中一个 RL 训练框架的两个镜像占了绝大多数(28700 次和 401 次),而且全部是 auto_read=ok——它们现在正靠自动读在工作。它那个运行配置结构里只有后端、镜像、导入路径和一包 kwargs,没有启动命令这一项:那套配置是照着 Docker 写的,Docker 本来就会执行镜像里的 ENTRYPOINT,它那边从来不需要填。

按原计划取消自动读,这两个镜像的沙箱会全部以 sleep 启动,或者被 400 拒掉。

眼下是:调用方能从 notices 看出镜像里的启动命令到底跑没跑,但请求照收不误。生产集群的网络问题也没解决,自动读在那边仍然不可靠。要关掉这条降级,得先让调用方有地方写启动命令——要么它自己传,要么平台自己存一张表,按镜像 digest 记下该用哪条启动命令,建沙箱时查表。这张表还没有。