一个组件比它的替代方案慢一倍,这个数字本身说明不了该不该换掉它。慢可能来自一份没写对的配置,也可能来自它的工作方式本身。我的判断是:只有改配置消不掉的那部分慢,才构成换掉一个组件的理由。
判据达标,结论却是不换
一个沙箱服务通过开源引擎 OpenSandbox 在 Kubernetes 上建沙箱。一直有个说法:走引擎比自己直接建 Pod 慢。要不要绕开它自己建,得先有数。
实验前把判据写死:直接建 Pod 要比走引擎快 30% 以上,并且每建一个沙箱向 API Server 发的写请求不超过 5 次,才考虑绕开引擎。先定判据,是为了不让结果反过来改标准。
60 个沙箱同时建,每个一个镜像,测从提交到可用的中位数(含拉镜像):
| 建法 | 就绪中位数 |
|---|---|
| 走引擎,按线上配置 | 45.0 秒 |
| 精简原型直接建 Pod | 22.6 秒 |
快 49.8%,判据达标。结论是不绕开。
慢在 init 容器没写 resources
引擎给每个沙箱加了一个 init 容器(主容器启动前先跑完的一次性容器),它的 spec 里没写 resources。
这个字段缺省不等于不占资源。命名空间上挂了 LimitRange,容器没写资源时由它填默认值;实验命名空间填的是 2 核,而 requests 没写时会被设成和 limits 相同。直接建 Pod 那一组自己写了 requests,0.5 核。(这条默认值是实验集群的,生产集群有没有同样的设置没去查。)
接下来是调度器的事。调度器按 requests 判断节点装不装得下,也按 requests 扣命名空间配额。一个 Pod 的 requests 取「所有 init 容器的最大值」和「所有普通容器之和」中的较大者——init 容器跑完就退出,它的申请量仍然决定整个 Pod 的占位。于是走引擎的每个沙箱按 2 核占位,直接建的按 0.5 核。
同样的配额下,同时跑起来的沙箱峰值:走引擎 27 个,直接建 53 个。
后面那一半沙箱在排队,排队时间计进了就绪中位数。慢的地方不在引擎的代码路径,在那份没写的资源声明。
三处配置修掉后重测:init 容器补上 resources,引擎控制器的 CPU 上限从 0.5 核放开,命名空间给沙箱对象的个数配额从 40 放开。走引擎 21.7 秒,同一轮直接建 21.1 秒,差 3%。
这一轮两边都换成了预先拉好镜像,所以 45.0 降到 21.7 不全是改配置的效果。能比的只有同一轮里的那两个数。
改配置消不掉的那部分
同一批实验里还有一项不随配置变。
每建一个沙箱向 API Server 发的写请求:走引擎 12.6 次,直接建 4.1 次,多出约 8.5 次。这 8.5 次里,控制器对沙箱对象状态的写占 6.6 次,事件占 2 次。换一个引擎版本测,仍是 12.6 到 14.5 次;调参数也没变。
多出来的写请求来自它的工作方式:声明式控制器把一次创建拆成「写下期望状态、看实际是什么样、再把状态字段写回去」的循环,每观察一轮就要写回一次状态,状态字段的写冲突(409)平均每个对象 3.35 次,冲突后还要重试。直接建 Pod 没有这一层,一次写入就完成。
并发更大时差距会放大。50 台模拟节点上 1000 个同时建、只算建 Pod:引擎按原配置要 3.79 倍的时间,瓶颈是它访问 API Server 的客户端限速(每秒 100 次)。把限速放到 400,仍然是 2.35 倍,代价是 API Server 写请求多 15%,409 冲突从每个对象 1.7 次翻到 3.5 次。放开限速没有少发一次写请求,只是让它们发得更快。
怎么分清这两种慢
把配置改对,再测一次:差距消失的是配置性的,还在的是结构性的。这件事里差距从 49.8% 掉到 3%,剩下的是每个沙箱多出的 8.5 次写请求,换版本、调参数都没掉。
最后保留了引擎,按实测调配置,这 8.5 次写请求和千级并发的差距记成风险。按现在的规模这两项还不用付代价:60 个沙箱时配置调对就够用,千级并发会不会真的来,排期上没有依据。什么时候该重看这个决定:单集群沙箱并发到百级以上,或者 API Server 的写入额度出现争用。
没测到的部分:引擎对照每组只跑了 1 次,波动和 5% 同量级;千级并发那组直接建的一方是压测脚本,不经服务层,服务层自己取出排队请求的速度没测;实验集群的 etcd 存储有秒级卡顿,和 API Server 相关的数字只看量级。