一个原型在实验集群上把建沙箱做到了现有做法的 8.7 倍快。我没有把它做成功能。我的判断是:这个倍数只在「有人等着沙箱起来」的时序里才是收益,而真实调用方不在那个时序里。
这个 8.7 倍是怎么来的
现在的做法是一个沙箱一个 pod。请求到服务,服务建一个 Kubernetes 对象,控制器把它变成 pod,调度器挑机器,kubelet 拉起容器,containerd 起 shim,shim 调运行时。
原型把这一整条换掉。每台机器上常驻一个特权 pod,由它直接调 runsc 或 runc 建沙箱,每个沙箱一个网络命名空间、一个子 cgroup,根目录直接用一个 EROFS 文件(只读、可按块随机读的文件系统格式)。Kubernetes 只看得见那一个特权 pod。
同一台机器、同一个已缓存的镜像、同一条首命令,旧版 cgroup 下顺序建单个沙箱,从请求到第一条命令跑完的中位数:
| 路径 | runc | gVisor |
|---|---|---|
| 一沙箱一 pod | 1733 毫秒 | 2565 毫秒 |
| 原型 | 200 毫秒 | 602 毫秒 |
8.7 倍和 4.3 倍。原型侧分段看,runc 建网 50 毫秒、准备根目录 4 毫秒、create 65 毫秒、start 10 毫秒。pod 路径多出来的那一秒多,摊在上面那条链路的每一跳上,没有哪一跳特别慢。
这个数字我信,原型的建沙箱时延在新版 cgroup 下复跑过,量级一致。
它落不到使用方头上
前提藏在「顺序建单个沙箱」这句话里。真实调用方是强化学习训练,时序不长这样。
训练一步一步走,步与步之间有空档。空档约 10 秒时,上一步还在跑,我就能把下一批沙箱建好,每步真正等沙箱的时间,pod 路径是 0.3–1.3 秒,原型是 0.1–0.7 秒,差不到 1 秒。
另一组实验把这件事说得更死。同一道题一次要起 8 个相同沙箱,提前建好 8 个普通 pod 要 0.52–0.76 秒,原型直接起要 0.63–0.76 秒。原型没有更快。
只有空档缩到 2.5–3 秒时差距才回来:pod 路径来不及提前建好,每步要等 2.3–7.7 秒,原型只等 0.2–1.6 秒。
8.7 倍成立的条件,是建沙箱堵在关键路径上。能提前建,它就不堵在那里,加速它没有收益。冷建时延是一个真实的测量值,只是没人等在那里。
代价要我自己背
原型里的沙箱不是 Kubernetes 对象。调度器不知道它们占了多少资源,kubectl 看不见它们,驱逐和重启策略对它们无效,清理得我自己写。集群上会多出一类只有我知道怎么运维的东西。
还有一个具体的坑。实验机器从 cgroup v1 换到 v2 之后,这份原型一个沙箱都建不出来。它解析 cgroup 时按「控制器名到路径」建字典,而 v2 下 /proc/self/cgroup 只剩一行 0::/...,解析直接失败。问题出在失败的样子:建沙箱的请求返回错误,这个特权 pod 的健康检查却一直是 200,Kubernetes 认为它是好的。切过去之后一段时间里没人发现。
修法是让这个特权 pod 先把自己挪进一个叶子 cgroup,再给父目录开子树控制器——v2 规定有进程的 cgroup 不能给子目录开控制器。改约 60 行之后限额正常:标称 1 核时实测 1.00 核,沙箱超内存只杀本沙箱。但这 60 行只在一台机器上验过,没跑过规模,也没跑过故障。
即使把范围收窄到只用 runc、只服务同一道题的一组沙箱、只用专用机器,当时估算也要 5–9 人周。
没有人提过这个需求
最后一条最简单:收集使用方需求的群里,没有任何人提过要更快的冷建。
同一批实验里还有一个开源沙箱项目,它的 7 个设计我全部在 gVisor 上复现成功了——暂停恢复、从一次快照复制多份、模板快照、镜像按需加载、只读盘挂载平台工具、兼容 E2B 协议、内存超卖。最后 5 个明确不做,2 个记成有人要再说。
实验回答的是「做不做得到」。决定做不做的是另外三个问题:收益落不落在真实时序上、代价谁背、有没有人要。7 个全部复现成功,对这三个问题一句话都没回答。
原型的代码和报告我留着。哪天训练侧真把步间空档压到 3 秒以内,或者真有人来要,这些数字和那 60 行修法就是现成的输入。在那之前,它不该变成生产系统里的一条额外路径。