一台节点塞更多 Agent:GKE Agent Sandbox 的真实数字
Google Cloud 公布了一次实验,回答的是大多数 Agent 平台在第二个月就会撞上的问题:你已经在付费的那台机器,到底能塞下多少个 Agent。环境是一台 n2-standard-48 节点上跑一群 OpenClaw Agent,变量不是选哪个模型,而是隔离方式与编排策略。用 microVM 隔离时,跑到 61 个 Agent 就撞墙,健康检查开始频繁失败。换成 GKE Agent Sandbox 加 gVisor 之后到了 88 个。再加上 Pod 快照,让闲置 Agent 能被冻结,开预热池到 133 个,激进超卖配置到 274 个。两步改动、数字很大,而且两步都不用改你的 Agent 代码。
整个实验都在一台节点上完成,有意思的是每一步之间到底变了什么
为什么 Agent 负载会打破朴素的容量规划
Google 的那段定性最有用。Agent 的工作模式是爆发式的:忙一阵、处理请求或执行代码,然后长时间闲置等你或等外部触发。如果你按静态分配算资源,就是在为「什么都不做」付费。他们实验的基线,正是这个问题最常见的答案:把每个 Agent 放进一个独立 microVM(比如 Kata 容器)以获得硬件级隔离。这确实给了你真正的安全边界,同时立刻竖起了扩展墙:每个 microVM 都要带一套完整的客体操作系统,内存和 CPU 被内核吃掉,而不是给 Agent 用。在这台节点上跑到 61 个 Agent,可靠性就掉了,健康检查开始规律性失败。这就是问题的诚实形状:安全选择是对的,密度很糟。
# Encode the isolation guarantee in a spec, not in a convention someone remembers.
import json
def sandbox_spec(name, runtime_class="gvisor", cpu="2", memory="4Gi"):
return {
"apiVersion": "agents.x-k8s.io/v1alpha1",
"kind": "Sandbox",
"metadata": {"name": name, "labels": {"runtime": runtime_class}},
"spec": {
"runtimeClassName": runtime_class,
"replicas": 1,
"podTemplate": {
"spec": {
"containers": [{
"name": "agent",
"resources": {
"requests": {"cpu": cpu, "memory": memory},
"limits": {"cpu": cpu, "memory": memory},
},
}],
"automountServiceAccountToken": False,
}
},
},
}
EGRESS = [] # default deny. Add destinations one at a time, with a reason each.
print(json.dumps(sandbox_spec("research-agent-01"), indent=2)[:430])
print("explicit egress rules:", EGRESS or "none, so nothing gets out")
# The isolation claim lives in runtimeClassName. Everything else is scheduling detail.把 microVM 换成 gVisor 沙箱
第一个优化,是把同一套负载从 microVM 迁到 GKE Agent Sandbox——一个专为 Agent 代码执行与「计算机使用」打造的 Kubernetes 原语。它不再为每个 Agent 提供完整客体操作系统,而是用 gVisor:一个运行在用户态的、名叫 Sentry 的内核,用来拦截和过滤系统调用。你既保住了面向不可信代码的生产级隔离,又甩掉了客体系统的开销。在 Google 的测试里,同一台 VM 上的上限从 61 提到 88,增幅 44%;用他们的说法是每 vCPU 能跑多 40% 以上的 Agent,且性能画像相近的情况下单 Agent 成本降低 30% 以上。GKE Agent Sandbox 在 5 月正式 GA,Google 说其使用量在不到四周内增长了 7 倍以上。有个细节值得知道:gVisor 也是用来保护 Gemini 的那套隔离技术,所以这不是玩具档。
# Google's measured points on one n2-standard-48 node, turned into something reusable.
MEASURED = {
"microvm_kata": {"agents": 61, "note": "baseline: health checks start failing"},
"gvisor_sandbox": {"agents": 88, "note": "+44% on the same fixed node"},
"warm_pool": {"agents": 133, "note": "performance-optimised, sub-second start"},
"oversubscribed": {"agents": 274, "note": "cost-optimised, start under 5s"},
}
def density(base, target, node_vcpus=48):
b = MEASURED[base]["agents"]
t = MEASURED[target]["agents"]
return {
"from": base, "to": target,
"agents_per_vcpu": round(t / node_vcpus, 2),
"density_multiplier": round(t / b, 2),
"cost_per_agent_index": round(b / t, 3),
}
for pair in [("microvm_kata", "gvisor_sandbox"),
("microvm_kata", "warm_pool"),
("microvm_kata", "oversubscribed")]:
print(density(*pair))
# Google summarises the combination as more than 3x density and up to 75% lower cost per agent.
# Sizing should use your own measured agent counts, not someone else's multiplier.沙箱既是安全问题,也是调度问题
第二步针对的不是忙的 Agent,而是闲的
沙箱解决的是「把活跃工作塞得更紧」。它完全帮不上那个刚干完活、正在等 webhook 的 Agent。这正是 GKE Pod 快照要处理的问题:与其让闲置 Agent 常驻内存,不如把它 checkpoint 到持久存储上,把 CPU 和内存还给集群;等任务触发到来时,控制器在毫秒级从快照恢复它。这就是超卖,而 Google 对取舍讲得相当坦率:不同 Agent 的延迟要求不同,一刀切的集群策略要么伤用户体验,要么白花钱。他们的答案是在同一个集群里并行跑不同策略。性能优先的配置下,预热的沙箱池让他们跑到 133 个 Agent;成本优先的配置下,大幅超卖并把启动时间控制在 5 秒内,同一台节点跑到 274 个。
# Three latency classes, three different orchestration strategies.
CLASSES = {
"realtime_coding_assistant": {"startup_ms_max": 1000, "tolerates_queuing": False, "strategy": "warm_pool"},
"autonomous_teammate": {"startup_ms_max": 5000, "tolerates_queuing": True, "strategy": "snapshot_resume"},
"headless_background": {"startup_ms_max": 3600000, "tolerates_queuing": True, "strategy": "max_oversubscription"},
}
def classify(agent, startup_budget_ms, interactive):
for name, spec in CLASSES.items():
if startup_budget_ms <= spec["startup_ms_max"] and spec["tolerates_queuing"] >= (not interactive):
return {"agent": agent, "class": name, "strategy": spec["strategy"]}
return {"agent": agent, "class": "unclassified", "strategy": "warm_pool"}
for args in [("copilot-inline", 800, True),
("pr-reviewer", 4000, False),
("nightly-report", 300000, False)]:
print(classify(*args))
# Getting this wrong in either direction costs you: either latency, or over-provisioned nodes.先给 Agent 分类,再算节点容量
整个实验最实用的产出是那个三分类。实时编码助手需要亚秒级启动,不能容忍排队,所以要配 Pod 快照加热池——也就是已经启动好、互相隔离、随时可用的沙箱。自主队友能接受几秒的启动时间,所以普通的挂起-恢复策略就够了,而它在闲置时不再占用算力。无头后台 Agent,比如每天夜里跑的调研任务,完全可以为容量等上一小时,那就给它最大程度的超卖。Google 同时点出了分错类的后果:惊群问题——一大波 Agent 同时唤醒、同时要算力。他们的可调答案是:预热池保性能,挂起-恢复省钱,而且按节点池分别设置,而不是全局一刀切。
# Idle agents are the bill. Freeze them, and be explicit about the wake-up budget.
def lifecycle(state, idle_seconds, snapshot_after_s=120, wake_budget_ms=5000):
plan = {"state": state, "action": "noop", "frees_resources": False}
if state == "running" and idle_seconds > snapshot_after_s:
plan.update(action="checkpoint_to_persistent_storage", frees_resources=True)
elif state == "snapshotted":
plan.update(action="resume_on_event", frees_resources=False)
plan["wake_within_budget"] = wake_budget_ms <= 5000
return plan
for case in [("running", 30), ("running", 900), ("snapshotted", 0)]:
print(lifecycle(*case))
# A snapshot that takes too long to resume is just an outage with extra steps. Budget it.把闲置 Agent 冻起来,而不是为「暂停」付费
那些数字,以及该信几分
Google 的总结性数字是「3 倍以上的 Agent 密度」和「单 Agent 成本最多降低 75%」,而他们自己表格里的具体端点,是同一台节点上 61 个和 274 个。这两句话的角度略有偏差,这在厂商博客里很正常,所以请把倍数当成方向、把 Agent 数量当成数据。真正能干净迁移过来的是顺序:隔离方式的改变,在不改动工作负载的前提下大约值三分之一成本;编排方式的改变值好几倍,但只对那种「大部分时间确实在闲置」的 Agent 成立。如果你的 Agent 一直在满负荷跑,超卖什么都换不到,而快照本身的开销还要你付出延迟。先去量自己的占空比,因为决定该选哪种配置的,就是那个比值。
# Cost per agent is the figure that survives a budget review.
NODE_USD_PER_MONTH = 1168.0 # illustrative: one n2-standard-48 class node at list price
def cost_per_agent(agents_on_node, node_cost=NODE_USD_PER_MONTH):
return {
"agents_on_node": agents_on_node,
"cost_per_agent_usd": round(node_cost / agents_on_node, 2),
}
baseline = cost_per_agent(61)["cost_per_agent_usd"]
for name, agents in [("microVM baseline", 61), ("agent sandbox", 88),
("warm pool", 133), ("oversubscribed", 274)]:
row = cost_per_agent(agents)
row["config"] = name
row["reduction_vs_baseline"] = str(round((1 - row["cost_per_agent_usd"] / baseline) * 100, 1)) + "%"
print(row)
# Swap in your own node price and your own measured agent count. The shape of the curve is the point.在你的集群上该怎么做
三点。第一,隔离层要明确选,并且写进 spec——gVisor 给你用户态内核边界,microVM 给你硬件级隔离外加客体系统开销,正确答案取决于你跑的是模型生成的不可信代码,还是你自己的代码。第二,按启动预算给 Agent 分类,而不是按团队归属分,因为分类结果才决定编排策略。第三,给单 Agent 成本一个具体数字,因为「密度」的讨论会死在预算评审里,而「每 Agent 每月多少钱」这样的数字能活下来。如果你的平台还在 microVM 基线、一台节点 61 个 Agent、且其中很大一部分在闲置,那么最便宜的那个收益点,不是换一台更大的机器。
📌 常见问题 FAQ
Google 到底测了什么?
他们把一批 OpenClaw Agent 塞进单个 GKE 节点,底层是一台固定的 n2-standard-48 VM 实例,然后逐步改变隔离方式与编排策略,观察在可靠性下降前能跑多少个 Agent。
基线配置能跑多少 Agent?
在「每个 Agent 一个 microVM」以获得硬件级隔离的配置下,测试在 61 个 Agent 处撞墙,此后可靠性下降,工作负载健康检查开始规律性失败。
GKE Agent Sandbox 改变了什么?
它用基于 gVisor 的沙箱取代了 microVM——用用户态内核替代完整客体操作系统,把数量从 61 提到 88,增幅 44%;每 vCPU 可跑的 Agent 多 40% 以上,单 Agent 成本降低 30% 以上。
超卖是怎么做到 274 个的?
用 GKE Pod 快照把闲置 Agent checkpoint 起来,把 CPU 和内存还给集群,等任务触发到来时再从快照恢复。在把启动时间控制在 5 秒内的成本优先配置下,同一台节点上跑到了 274 个。
我该把所有 Agent 都超卖吗?
不该。超卖适合大部分时间闲置的 Agent。像实时编码助手这种延迟敏感型,需要预热池和亚秒级启动,否则你就是在拿用户体验换成本。