返回博客

Agent 沙箱密度提升 10 倍|执行层接入实录

人工智能2908
Agent 沙箱密度提升 10 倍|执行层接入实录

智能体栈的竞争正在从模型层下沉到执行运行时层:谁能把数百万个短命 sandbox 塞进更少的机器、挂起之后又能恢复得够快,谁的 Agent 产品账单就更低。这是我在 4sapi(https://4sapi.com)做模型 API 中转与网关接入时感受最深的一点,下面把执行层在链路里的位置、密度与 resume 两个关键指标,以及在 Kubernetes 上自建执行层的取舍一次讲清。

一、开篇痛点:Agent 一上量,先撑不住的往往不是模型

模型这一层的问题,如今大多有现成答案:接口格式有网关做转换,限流有中转层兜底,用量有看板可查。真正开始疼的,是把模型接进真实业务之后——Agent 需要一个地方"动手":跑代码、调工具、开浏览器会话、落临时文件。

执行层的难处来自负载形态本身:

拿标准容器运行时硬扛,很快撞上两堵墙:要么给每个会话常驻一个容器,机器费用先于 token 费用失控;要么按需启动容器,冷启动延迟把交互体验拖垮。执行层欠下的债,最终都记在账单和口碑上。

二、先摆事实:一个开源执行运行时已经在 GKE 可用

动手设计之前,先把这期的事实底座摆出来,按我自己的理解复述一遍。

一个开源、默认安全的智能体执行运行时已经在 GKE(Google Kubernetes Engine)可用,名字叫 Agent Substrate。它的设计目标是运行数百万个 sandbox;sandbox 密度较标准容器运行时高 10 倍;resume 操作低于 500ms,每秒能完成超过 500 次挂起/恢复激活;自带零信任(zero-trust)内核与网络隔离;可以运行在任何 Kubernetes 基础设施上,并针对 GKE 做了优化。

这段描述里有几个词值得单独圈出来:开源,意味着执行层不必绑定某一家云;默认安全,意味着隔离和网络策略是出厂状态而不是可选插件;任意 K8s 可跑,意味着自建路径始终存在;针对 GKE 优化,意味着在 GKE 上能省掉一部分调优工作。

对正在搭 Agent 平台的团队来说,信号很明确:sandbox 不必再拿通用容器运行时硬扛,执行运行时已经独立成层,有了专门的基础设施。

三、原理速览:执行层在整条链路里的位置

把一条 Agent 请求从入口到出口走一遍,执行层的位置就清楚了:

text
网关(鉴权、限流、计费、路由)
    |
    v
调度器(会话租约、配额、节点选择)
    |
    v
沙箱池(挂起 suspend / 恢复 resume)
    |
    v
模型 API
    |
    v
结果回传(状态与快照落盘后返回调用方)

每一跳各管一件事:

一句话概括:模型层决定"想得多快",执行层决定"同一批机器能同时让多少个 Agent 干活、又能放下多少个暂时不干活的会话"。

四、为什么偏偏是密度和 resume 速度这两个指标

执行运行时可以列出一长串指标,真正压住账单和体验的是两个。

密度决定机器账单。Agent 产品的固定成本大头,是承载沙箱的那批机器。同样一台机器能塞下的 sandbox 越多,单会话摊到的基础设施成本就越低。密度较标准容器运行时高 10 倍,意味着承载同样的会话规模,机器规模有机会压到原来的零头——账单里最沉的一块被直接削掉。

resume 速度决定体验上限。挂起的价值在于释放资源,但恢复要够快才敢真的挂起。resume 低于 500ms,会话在用户感知里几乎一直"在线",调度器才敢在会话空闲的第一时间收回资源,而不是为了保体验把沙箱白养着。

每秒超过 500 次挂起/恢复激活,解决的是脉冲并发的问题。整点任务、活动峰值,本质上是海量会话在极短时间内索取资源;挂起/恢复吞吐跟得上,调度器就能用"先挂起、再恢复"把尖峰削平,而不是按峰值常备机器。

还有一个常被忽略的收益:挂起/恢复与快照,让长任务的计费与资源占用解耦。一个跑数小时的长任务,真正活跃的计算时间可能只占零头,其余时间挂起只保留快照——资源按活跃时刻占用,计费按会话数与活跃时长记录,两边不再互相锁死。

五、Agent 工作负载到底长什么样

第三节是静态链路,这一节看动态负载,因为负载特征直接决定执行层怎么设计。

短生命周期会话占绝对多数。执行层面对的不是少数几个长进程,而是源源不断"出生—干活—结束"的小会话。任何为长进程设计的假设——进程号稳定、内存常驻——在这里都会失灵。

并发是脉冲,不是匀速。平均水位不高,但尖峰会突然出现。执行层要么按峰值常备机器,要么具备快速吞吐会话的能力,两者的成本差距巨大。

单会话很小,峰值很尖。一个空闲沙箱几乎不吃资源,但一次构建、一次浏览器渲染就能把单会话的占用推高一大截。资源配额必须按峰值设计,而不是按均值。

状态必须可挂起。长任务的中间状态一旦只能存在进程内存里,资源就无法真正释放。快照是解法:把内存与文件系统状态定格下来,进程可以整个消失,需要时再原样长回来。

这四个特征合在一起,正好命中第二节的两个指标:密度应对"海量小会话",resume 速度应对"脉冲与状态"。执行层选型时盯着这两个指标看,方向基本不会错。

六、三种执行层方案对比

把常见的选择收敛成三种形态,差异一目了然:

维度常驻容器按需容器挂起恢复沙箱
资源占用每会话独占,空闲也占着机器运行时才占用,结束即释放挂起后几乎不占计算资源,只留快照
冷启动 / 恢复无需启动,随取随用每次经历拉镜像与启动,交互延迟明显resume 可低于 500ms(见第二节运行时)
隔离与安全取决于自行加固的程度同左,加固动作重复一遍可内置 zero-trust 内核与网络隔离
会话状态存在进程内存里,重启即失需外挂存储重建快照随会话走,恢复即续跑
运维复杂度低,但费钱中等需要调度器与租约管理
适合场景极低延迟的常驻服务低频批处理海量短会话、突发并发、长任务挂起

三种形态不是互斥的,更常见的是混布:真正的常驻服务用常驻容器,低频批处理用按需容器,占会话总量绝大多数的 Agent 执行流量交给挂起恢复沙箱。

七、接入教程(上):模型侧先用 openai SDK 走通

执行层再复杂,模型侧的接入依然是最薄的一层。4sapi 的网关兼容 openai SDK 风格,base_url 指向 https://4sapi.com/v1 即可,密钥在控制台申请后放环境变量。

环境准备:

bash
pip install openai
export LLM_API_KEY="sk-xxxx"   # 密钥只放环境变量,不进代码仓库

调用示例:

python
import os
from openai import OpenAI

# 中转网关地址:openai SDK 风格,以 /v1 结尾
client = OpenAI(
    api_key=os.environ["LLM_API_KEY"],
    base_url="https://4sapi.com/v1",
)

def ask_model(prompt: str) -> str:
    resp = client.chat.completions.create(
        model="gpt-4o",  # 模型名以网关提供的模型列表为准
        messages=[
            {"role": "system", "content": "只输出执行结果的 JSON 摘要。"},
            {"role": "user", "content": prompt},
        ],
        temperature=0.2,
    )
    return resp.choices[0].message.content or ""

if __name__ == "__main__":
    print(ask_model("总结一次沙箱执行任务的输出要点"))

这一段解决的是"推理调用"这条腿:格式转换、限流、计费都在网关侧完成,沙箱里的代码只需要把它当成一个普通的 openai 风格端点来用。另一条腿——"会话在哪里执行"——才是执行层的正题,下一节给骨架。

八、接入教程(下):会话租约与挂起-恢复的编排骨架

执行层编排在概念上只有三件事:签发会话租约、维护沙箱池、在挂起与恢复之间搬运会话。下面是一份可以直接跑通的骨架,把状态机语义写清楚,具体调用留给所选运行时的 SDK:

python
import time
import uuid
from dataclasses import dataclass


@dataclass
class SessionLease:
    """会话租约:持有租约才有资格占用沙箱执行。"""

    session_id: str
    sandbox_id: str | None = None
    state: str = "new"  # new -> active -> suspended -> resumed -> closed
    expires_at: float = 0.0


class SandboxPool:
    """沙箱池编排骨架:只表达状态机,不绑定具体运行时。"""

    def acquire(self, lease: SessionLease) -> str:
        lease.sandbox_id = f"sandbox-{uuid.uuid4().hex[:8]}"
        lease.state = "active"
        lease.expires_at = time.time() + 300
        return lease.sandbox_id

    def suspend(self, lease: SessionLease) -> None:
        # 生产实现:快照内存与文件系统,释放计算资源,仅保留快照
        lease.state = "suspended"

    def resume(self, lease: SessionLease) -> None:
        # 生产实现:从快照恢复执行上下文,交互场景要把耗时压进亚秒级
        lease.state = "resumed"

    def release(self, lease: SessionLease) -> None:
        lease.sandbox_id = None
        lease.state = "closed"


def run_task(pool: SandboxPool, steps: list[str]) -> None:
    lease = SessionLease(session_id=uuid.uuid4().hex)
    pool.acquire(lease)
    try:
        for step in steps:
            if time.time() >= lease.expires_at - 30:
                pool.suspend(lease)  # 长任务中途让出计算资源
                lease.expires_at = time.time() + 300
                pool.resume(lease)
            print(f"session={lease.session_id} sandbox={lease.sandbox_id} step={step}")
            # 这一步里发起模型调用或工具执行
    finally:
        pool.release(lease)


if __name__ == "__main__":
    run_task(SandboxPool(), ["取数", "调模型", "写报表"])

这份骨架刻意保持薄:SessionLease 决定"谁有权在哪个沙箱里执行、执行到什么时候";SandboxPool 决定"挂起、恢复、回收怎么发生"。把它换成任何真实运行时,思路都不变——租约到期前挂起,恢复后续跑,任务结束回收。

九、K8s 侧的部署示意

执行层最终要落在 Kubernetes 上。下面的 YAML 是通用示意,只表达"调度器常驻、沙箱池可扩缩"的形态;具体镜像、参数与自定义资源以所选运行时文档为准,不代表任何官方清单:

yaml
# 通用 Kubernetes 示意:调度器 Deployment,非任何运行时的官方清单
apiVersion: apps/v1
kind: Deployment
metadata:
  name: agent-dispatcher
  labels:
    app: agent-dispatcher
spec:
  replicas: 2
  selector:
    matchLabels:
      app: agent-dispatcher
  template:
    metadata:
      labels:
        app: agent-dispatcher
    spec:
      containers:
        - name: dispatcher
          image: registry.example.com/agent/dispatcher:latest  # 镜像占位
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2"
              memory: "2Gi"
          env:
            - name: SANDBOX_POOL_SIZE
              value: "50"  # 单节点沙箱容量按实测密度调整,不要照抄

两点部署建议:

十、成本模型:按"沙箱会话数 × 活跃时长"算账

网关侧要给 Agent 流量建成本模型,关键是把两个此前被绑在一起的量拆开:会话数与资源占用。拆开之后,账可以这样算:

text
执行层月成本 ≈ 节点数 × 节点单价
节点数     ≈ (会话数 × 活跃占空比 × 单会话峰值资源) / (单节点容量 × 目标水位)
活跃占空比 = 活跃时长 / 会话总在线时长
                                  ↑ 挂起/恢复就是用来压低这个比值的

三条推论直接可用:

对内,这个模型用来做容量规划;对外,它就是产品计费的骨架。谁把"会话数 × 活跃时长"这条公式算得越准,价格空间就越大。

十一、zero-trust 隔离与密钥注入怎么配合

执行层自带零信任内核与网络隔离,意味着沙箱内进程默认不被信任:网络默认不通、权限默认最小、按需逐条放行。这套默认姿态与密钥注入的关系,值得单独讲。

原则只有一条:模型 API 的密钥不进沙箱的通用环境。沙箱里跑的代码是模型生成或用户提交的,必须假设它可能泄露任何看得见的东西。密钥的正确路径是:

text
密钥保存在统一的 Secret 存储
        |
调度器按会话租约注入短时凭证
        |
沙箱内进程只能访问网络策略放行的域名与端口
        |
任务结束或会话挂起时凭证随之失效
        |
审计日志只记录凭证引用,不落明文

配合三个习惯:密钥注入做到"短时、最小作用域";网络放行名单逐条评审,不为调试长期开洞;日志与快照里定期扫描明文密钥。零信任内核解决"进程不可信",密钥注入解决"凭证不过期、不脱敏"这两类事故,两者拼在一起才是完整的执行层安全姿态。

十二、中小团队在 K8s 上自建执行层的取舍

自建与否,本质是"运维负担"与"成本与自主权"之间的交换。

自建的理由:数据与执行环境不出门;运行时可跑在任意 K8s 上,不锁定某一家云;开源意味着可以按需改造;规模上去之后,密度带来的机器成本优势归自己。

自建的代价也不小:调度器、会话租约、快照存储、监控告警、故障演练,每一项都要自己背;密度与 resume 速度要用真实负载反复压测;运行时升级与快照兼容需要专人跟进。

一条务实的折中路径:

  1. 模型侧先走中转网关,把格式转换、限流、计费这三件最容易做糙的事外包出去;
  2. 执行层用开源运行时自托管在现有 K8s 集群,先灰度一小部分流量;
  3. 会话量还小的阶段,用按需容器加外挂状态存储先跑通业务,等短会话成为主要负载,再把主力切到挂起恢复沙箱;
  4. 在 GKE 上,把节点池与扩缩容交给托管能力;在其他 K8s 上,预留出做等价调优的工期。

规模判断的粗略依据:会话量小到一台机器就能兜住时,不必急着引入挂起恢复;当机器账单开始逼着提高密度、或脉冲并发开始拖垮交互体验时,执行运行时的投入就到了回收期。

十三、成本与风险提示

成本侧,四笔账要分开看:沙箱池的节点费用、快照存储与网络流量费用、模型 token 费用、中转服务费用。挂起不等于免费,快照照样占存储;密度提升省下的是计算侧的钱,存储侧的开销会随快照数量增长。

风险侧,按优先级列:

切换策略上,不建议把全部流量一次性切到新执行层。灰度、双跑、可回退,是这类基础设施迁移的底线动作。

十四、上线检查清单

正式放量前,逐项过一遍:

  1. 密钥全部走环境变量或 Secret 注入,代码仓库里 grep 不到任何明文;
  2. 沙箱默认网络不通,放行域名逐条评审并记录变更人;
  3. 会话租约有 TTL,到期自动挂起并回收沙箱;
  4. resume 耗时在自己负载下实测并记录,达不到交互要求先扩池再放量;
  5. 挂起率、活跃占空比、单节点沙箱数三项进监控面板,并配告警;
  6. 完整演练过一次"挂起—隔夜—恢复",业务状态无损;
  7. 长任务挂起后续跑不依赖进程内存里的临时变量,全部状态来自快照与外部存储;
  8. 计费按"会话数 × 活跃时长"落库,能对上每一笔账单;
  9. 压过一次突发脉冲,观察排队、丢弃与恢复的完整链路;
  10. 保留灰度开关,随时可以把执行层切回旧路径;
  11. 日志、快照、错误堆栈定期扫描,确保没有明文密钥。

十五、总结

这一期把执行层从上到下捋了一遍:Agent 栈的竞争正从模型层下沉到执行运行时层;一个开源、默认安全的智能体执行运行时已经在 GKE 可用,设计目标是数百万个 sandbox,密度较标准容器运行时高 10 倍,resume 低于 500ms,每秒超过 500 次挂起/恢复激活,自带零信任内核与网络隔离,任何 K8s 都能跑并针对 GKE 优化。密度压住机器账单,resume 速度保住交互体验,挂起/恢复与快照让计费和资源占用解耦;模型侧用 openai SDK 风格接中转网关,执行侧用会话租约与挂起恢复的编排骨架落地,中小团队按"先网关、再灰度、后切换"的节奏自建。正在搭 Agent 执行层的话,可以从 4sapi(https://4sapi.com)先把模型侧的用量与成本看板搭起来。

欢迎在评论区聊聊最近在执行层踩过的坑:密度、resume,还是计费口径?

标签:Agent沙箱沙箱密度挂起恢复Kubernetes执行层零信任隔离

推荐阅读

探索更多前沿洞察与行业干货。