Skip to content
个人学习网站
5 分钟阅读

压一压看哪里会塌:这套东西真的稳吗,以及我为什么停在了硬隔离前面

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。