压一压看哪里会塌:这套东西真的稳吗,以及我为什么停在了硬隔离前面
Agent 沙盒系列最后一篇。20 个并发长任务能证明什么、不能证明什么;教案里设计的杀节点混沌实验为什么在一台笔记本上做不了;以及"容器隔离到此为止、再往下是虚拟机"这一步的理论账——那是我硬件条件到头的地方。
前五篇每一篇都在解决上一篇捅出来的娄子:隔离 → 冷启动 → 空转成本 → 状态丢失 → 高峰排队。每一步单独看都成立。
但“单独成立”和“合起来扛得住”是两回事。最后一篇干两件事:压一压看哪儿会塌,以及说清楚我在哪儿停下了、为什么。
压测:20 个并发的长任务
最后一个 lab 把前面的部件拼在一起,压测脚本很朴素:
async def main():
print("同时发起 20 个 60 秒的长任务...")
tasks = [submit_task(client, i) for i in range(20)]
await asyncio.gather(*tasks)
20 个请求同时进来,每个任务要跑约一分钟,而池子里只有几个容器。
能证明的事(都是前几篇的承诺兑现了):
- 20 个任务一个都没丢——多出来的老老实实排在队列里;
- 用户拿到的是“你排第几”,不是错误页;
- 任务进度可以随时查,而且问哪个容器都行,因为进度写在共享盘上;
- 干活的容器忙完自动接下一个,没人工干预。
不能证明的事(这部分更重要):
- 20 并发是笔记本级别的量。真实系统的问题往往在几百上千并发时才现形——队列积压速度超过消费速度、连接数打满、写盘变慢;
- 全程没有任何东西坏掉。没杀容器、没杀节点、没断网。压测测的是“忙”,不是“坏”;
- 没有测量发现故障要多久和恢复要多久这两个数——系统里根本没有能测它们的东西。
教案里设计的混沌实验,我做不了
原本的设计是:撑住 500 个活跃会话,然后杀掉集群里 30% 的节点,再杀掉队列的主节点,量三个数——多久发现、多久恢复、多少用户看到了错误。
这三步在我的环境里都做不了,原因很实在:
- 杀 30% 的节点:本地集群只有一个节点。杀掉它不是故障演练,是关机;
- 杀队列的主节点:Redis 是单实例,没有主从,也就没有“切换”可言;
- 500 个活跃会话:这台笔记本撑不住 500 个各自吃内存的容器。
这不是“以后再说”的托辞,是这类实验的门槛就在那儿:混沌工程需要一个能承受破坏的集群,而不是一台能跑通代码的机器。 前五篇能在笔记本上做完,是因为它们验证的是架构缺陷;第七步验证的是规模行为,规模买不来。
顺带说清楚一件上一篇已经承认的事:真正的“自动故障转移”现在也还没有。容器挂了,它手上那个任务不会被自动重新派发——得有人发现、有人重发。要补上,缺的是一张任务状态表(谁在做、什么时候开始的)加一条超时规则(超过多久没动静就收回重派)。这才是“能自愈”和“能续传”的分界线。
再往下一步:容器隔离到此为止
系列的最后一个话题,是我只做了理论准备、没有动手的一步。这里必须说明白,免得看起来像做完了。
回到第一篇的问题:Agent 会执行大模型现场生成的代码。前面用“一任务一容器”解决了互相干扰,但容器的隔离到底有多硬?
打个比方:容器像同一栋楼里的隔间——墙是真的,但地基和水电总管是共用的。这个“共用的总管”就是操作系统内核:所有容器共享同一个内核,内核上出一个漏洞,隔间墙就形同虚设。日常业务里这个风险可以接受,因为跑的是你自己审过的代码;跑不受信任的生成代码时,前提变了。
业界给出的两条路,都是在往“独立的房子”方向走:
| 方案 | 做法 | 代价 |
|---|---|---|
| gVisor | 在容器和真内核之间插一层用户态内核,替它处理系统调用 | 启动仍然快,但部分系统调用不支持,某些负载性能下降明显 |
| Kata Containers | 每个容器塞进一台轻量虚拟机,各带各的内核 | 隔离最硬,代价是启动时间和内存开销,而且要求硬件虚拟化支持 |
在 Kubernetes 里切换它们并不难——用 RuntimeClass 指定某类负载走哪种运行时即可,应用代码不用改。
我没有实现它,原因很具体:本地集群跑在一台笔记本的容器里,要再往里嵌一层虚拟机需要嵌套虚拟化,内存也不够。 这一步不是想不明白,是硬件到头了。写出来是因为:知道边界在哪、知道代价是什么,本身就是这一步的产出——不受信任的代码,终点是虚拟机而不是容器,这个判断不需要跑一遍才能下。
整个系列的账
回头看这六步,每一步都不是“变好了”,而是拿一样东西换了另一样:
| 换来 | 付出 |
|---|---|
| 隔离 | 冷启动 45 秒 |
| 秒级响应 | 一排容器常年空转 |
| 崩了不丢进度 | 存储依赖、写盘频率的权衡 |
| 高峰不拒绝 | 多一个必须自己活着的队列 |
| (未做)不受信任代码的硬边界 | 启动时间、内存、硬件要求 |
架构不是设计出来的,是被账单逼出来的。 这也是我把它拆成一步步的实验、而不是直接给一张最终架构图的原因:单看最终架构,预热池、共享盘、队列每一个都像过度设计;顺着账单走一遍,就知道每个组件是被什么逼出来的。
留在桌上没做完的,按我的优先级排是:把拉模型做真(成本最低、直接消掉调度冲突)→ 任务状态表加超时重派(这才叫自愈)→ 换成多机可读写的存储 → 最后才是隔离升级。
一句话版本:能跑通不等于扛得住;说清楚哪些没验证,比多写一个组件更有价值。
全部代码:agent-sandbox-oss。有想法欢迎在仓库里提 issue。
- Kubernetes
- AI Agent
- 混沌工程
- 安全隔离
- 架构演进