每个任务一个容器,隔离解决了,用户要干等 45 秒
Agent 沙盒系列第二篇。一任务一容器听起来理所当然,代价是用户提交后要等 45 到 90 秒。这一篇拆开这几十秒到底花在哪,以及为什么"管理系统的接口"不能放在用户点击的那条路上。
上一篇的结论是:所有任务挤在一个程序里,一个出事全体陪葬,而且谁都能读到别人的数据和钥匙。
修法很自然——每个任务发一个自己的容器。用完就扔,互不干扰。
这一步在 Kubernetes 上写起来顺手得不像话,顺手到让人怀疑上一篇是不是小题大做。
代码:来一个任务,现造一个容器
# 收到请求 → 让 K8s 造一个新容器
v1.create_namespaced_pod(namespace="default", body=pod_manifest)
# 然后不停地问:"好了吗?好了吗?"——这就是用户干等的那段时间
while True:
pod = v1.read_namespaced_pod(name=task_id, namespace="default")
if pod.status.phase == "Running":
break
time.sleep(0.5) # 问得太勤会把管理系统问垮,所以每半秒问一次
return {"time_to_interactive_seconds": round(time.time() - start_time, 2)}
隔离是真拿到了:一个任务一个容器,内存超标只杀自己,钥匙也不再共用。上一篇那两堵墙确实没了。
然后看最后那行返回值——它记录的是用户从点下按钮到真正开始干活等了多久。
从半秒变成 45–90 秒
这不是代码写得慢,是这段时间里排队要办的事本来就多:
| 这几十秒花在哪 | 谁在忙 | 能不能优化 |
|---|---|---|
| 登记这个新容器 | 集群的“总账本”要记一笔并确认写牢了 | 基本不能 |
| 挑一台机器放它 | 调度程序筛选、打分、选中一台 | 有限 |
| 下载程序镜像 | 那台机器去仓库拉安装包 | 提前拉好可以,但只对已知的镜像有效 |
| 创建容器、分配网络 | 底层运行时开容器、网络插件分 IP | 基本不能 |
| 程序自己启动 | Python 解释器起来、加载依赖 | 有限 |
很多人第一反应是怪镜像太大。但镜像拉过一次就存在那台机器上了,再快也快不到能接受的程度——真正的大头是“登记 + 挑机器 + 初始化”这几步,它们是这套系统固有的节奏,不是你的代码慢。这就是“优化镜像体积”这条路走不远的原因:你优化的只是能优化的那一小块。
更麻烦的是那个“不停地问”
注意代码里那句注释:问得太勤会把管理系统问垮。写下这行的时候,问题的性质已经变了。
打个比方:K8s 有两套东西,一套是总务处(负责登记、审批、分配资源),一套是车间(真正干活的地方)。造容器、查状态,走的都是总务处。而总务处这种部门,设计出来就是给内部流程用的——不着急、可以重来、慢一点没关系。
现在的写法,等于把每一个客户都领到总务处窗口前排队,而且每半秒催一次。
一个客户还好。人多起来就是:
- 每个在等的请求每 0.5 秒查一次,100 个人同时来就是每秒 200 次查询;
- 每来一个新任务就要登记一笔,而登记要写进总账本、要确认写牢;
- 总务处先忙不过来,总账本的写入跟着变慢——这两个一慢,整个集群都开始慢,包括跟你的 Agent 毫无关系的其他业务。
这条分界值得记住:管理系统的接口是给管理流程用的,不是给用户点击用的。 冷启动只是它开给你的第一张账单,把整个集群拖慢才是真正的风险。
这一步定下来的事
两件事同时成立:
- 隔离是对的,不用回头;
- 临时去造是错的,错在时机——不能等客人到了才开始盖房子。
所以下一步只有一个方向:把“造”这件事挪到用户到达之前。提前准备一批容器闲着待命,请求来了直接认领一个。
等待会从 45 秒掉到零点几秒。但那批一直空转的容器,从此每分钟都在烧钱。下一篇算这笔账。
一句话版本:隔离没错,错的是等用户来了才开始准备。
代码:agent-sandbox-oss/lab2,一个文件,自己跑一次比看数字有说服力。
- Kubernetes
- AI Agent
- 冷启动
- 云原生
- 架构演进