跳到正文
Cloudflare AI· Thomas Gauvin·· 26 天前精选AI 评分78

Cloudflare 重构容器服务以扩展智能体沙盒

Cloudflare Containers, rebuilt to scale agent sandboxes

AI 导读

Cloudflare 推出新的 durable_object 调度策略,允许开发者在运行时动态选择容器镜像和计算资源,并将容器启动速度提升至 6 倍以上。该更新还引入了文件系统快照功能(公测中),支持智能体在任务间保存和恢复工作空间,从而更好地适配按需创建、暂停和恢复的智能体工作流。

推荐理由

Cloudflare 重构了容器基础设施以适配智能体工作负载,通过支持运行时动态配置和文件系统快照,显著提升了沙盒启动速度并降低了闲置成本。

正文 · AI 翻译

今天,我们让 Cloudflare Containers 变得更具可编程性,并针对智能体(agent)工作负载进行了优化。智能体不会提前部署沙盒。它们会根据任务需求即时创建沙盒,要求沙盒能立即就绪,并支持暂停和恢复。因此,我们重新架构了 Containers 以满足这些需求:您的代码现在可以在运行时选择每个沙盒的镜像和实例类型,Containers 的启动速度提升了 6 倍,且文件系统快照现已开放公开测试。

为了实现这一点,我们从底层重新思考了 Containers 基础设施。一种新的调度策略将每个沙盒的控制权交给了应用程序代码,而重新设计的运行时则提供了更快的容器启动路径。在 ComputeSDK 的独立基准测试中,中位启动时间从 4 秒多缩短至 648 毫秒;在我们自己的初步测试中,突发测试成功在几秒钟内创建了数十万个容器。

所有这些改进都建立在 Cloudflare Containers 一贯的优势之上:每个容器都拥有自己的 Durable Object,这是一个运行在容器旁边的持久化、可编程控制器,用于管理其生命周期、出站流量等。我们正在将更多功能直接引入原生的 ctx.container API,以便 Durable Object 可以在没有中间包装类的情况下控制其容器,并将此模型引入 Sandbox SDK 1.0。

正如我们今年早些时候所写,您的智能体需要一台计算机。当智能体需要完整的 Linux 工作空间时,这些变化使 Containers 成为 Workers、Dynamic Workers 和 Durable Objects 的更好补充。

为智能体重新思考 Containers 运行时

在此之前,Cloudflare Containers 是围绕应用程序部署来组织的。您在部署时选择镜像和计算资源,将该配置推广到整个应用程序,并进行集中管理。应用程序是配置和部署的单元。

智能体工作空间则不同:它是按需创建的,是在智能体工作时创建的。任务决定了其镜像、资源、工具和初始文件系统。它可能只存在几分钟,在请求之间休眠,或者几天后被恢复。这些决策需要与处理任务的应用程序代码绑定,并且智能体的沙盒需要快速启动,因为每一秒的启动时间都是用户在等待的时间。

我们在 Base44 的应用构建工作空间和 Kilo Code 的云智能体会话中看到了这种模式。它也出现在我们与 Cursor Cloud Agents、Devin Outposts、OpenAI Agents API 以及 Claude Managed Agents 的集成中。

这些工作负载中的每一个都需要工作空间提供不同的支持。编程智能体需要代码仓库、包管理器、编译器、测试运行器和开发服务器。评估(Evals)需要从已知状态开始的沙盒。强化学习系统需要创建、评分并重置大量的环境。长时间运行的任务需要保留智能体生成的文件,以便后续继续工作。

这些需求促使我们从根本上重新思考 Cloudflare Containers 的配置、调度和保存方式。其结果是一种配置和调度容器的新方法:durable_object 调度策略。它让您的代码能够在运行时选择每个沙盒的镜像和计算资源,启动容器的速度提升了 6 倍以上,并支持文件系统快照,因此工作区可以被保存和恢复。

“在 Base44,我们帮助任何人将想法转化为可运行的应用程序。Cloudflare Containers 为每个应用程序提供了一个隔离的开发环境,我们的 AI 可以在其中执行命令、安装依赖项,并通过实时预览将更改变为现实。”
Dolev Epshtein,Base44 应用基础设施软件工程师
“在 Kilo Code,每个云代理会话都需要自己的工作区和环境,并配备正确的存储库、工具和用户配置。Cloudflare Containers 让我们能够按需创建这些隔离环境,因此我们的代理可以快速开始运行命令并为客户开展工作。”
Emilie Schario,Kilo Code 联合创始人兼 Anaconda AI 工作区工程副总裁

选择您的代理所需的沙盒,从代码

从一开始,每个 Cloudflare Container 实例都连接到一个 Durable Object。Durable Object 为环境提供了稳定的身份,并允许应用程序代码控制其启动、休眠和停止的时间。事实证明,这种模型非常适合代理沙盒。

然而,到目前为止,对于代理沙盒而言最重要的两个决定在部署时就被锁定了:它运行哪个镜像以及它获得多少计算资源。每种镜像和实例类型的组合都是其自己的 Containers 应用程序,拥有自己的 Durable Object 命名空间,并预先通过 wrangler deploy 进行设置。 

假设一个代理需要一个小型 Node.js 沙盒,而另一个代理需要一个大型 Python 沙盒来进行构建。使用我们之前的方法,这需要两个应用程序、两个命名空间,以及在您的 Worker 中编写路由逻辑,以便将每个任务发送到正确的应用程序。每一个新环境都意味着另一次部署。 

新的 durable_object 调度策略消除了这一点。镜像和实例类型现在是您的代码在沙盒启动时传递的参数。要选择加入,请设置调度策略并声明您的 Durable Object 可以选择的镜像:

// wrangler.jsonc
{
  "containers": [
    {
      "class_name": "AgentSandbox",
      "scheduling_policy": "durable_object",

      "images": {
        "node": { "dockerfile": "./images/node/Dockerfile" },
        "python": { "dockerfile": "./images/python/Dockerfile" }
      }

    }
  ],
  "durable_objects": {
    "bindings": [{ "name": "SANDBOX", "class_name": "AgentSandbox" }]
  }
}

您声明的每个镜像都作为 this.ctx.container.images.<name> 在 Durable Object 中可用。当任务到达时,您的代码会为该任务选择镜像和实例类型:

import { DurableObject } from "cloudflare:workers";

export class AgentSandbox extends DurableObject {
  async startWorkspace(workspace) {
    if (this.ctx.container.running) {
      return;
    }

    const image =
      workspace.toolchain === "python"
        ? this.ctx.container.images.python	
        : this.ctx.container.images.node;

    const instance =
      workspace.workload === "build"
        ? "standard-2"
        : "standard-1";

    this.ctx.container.start({
      image,
      instance,
      enableInternet: true,
    });
  }
}

这段代码在已知任务后做出两个决定。它选择工作区所需的工具链以及分配多少计算资源。过去需要单独的应用程序和单独的 wrangler deploy 才能完成的操作,现在只需一条 if 语句即可。一个 Durable Object 类可以并排启动不同大小的 Node.js 和 Python 沙盒,添加新环境只需更改代码,而无需进行新的部署。这就是整个更新背后的理念:基础设施变成了在请求时运行的代码,一直到环境本身。

发布现在只是代码

在启动时选择镜像也解决了运行 Containers 最痛苦的部分之一:发布。

在此之前,更新镜像意味着要更新整个应用程序。你需要设置宽限期,以便运行中的实例可以排空,定义百分比拆分以逐步迁移流量,并调用我们的 API 来推送新配置。在整个过程中,平台会决定哪些实例被替换以及何时替换,无论代理程序是否正在执行任务。

使用 durable_object 调度策略,根本不需要任何发布配置。容器可以一直运行它启动时所用的镜像,直到你的代码将其停止。当 Durable Object 下次启动容器时,它会使用你的代码所选择的任何镜像。这意味着你想要的任何发布策略都只需几行代码即可实现:

const image =
  (await this.ctx.storage.get("pinned-image")) ??
  (isCanary(this.ctx.id)
    ? this.ctx.container.images.nodeV2
    : this.ctx.container.images.node);

例如,你可以:

  • 金丝雀发布 (Canary):通过对 Durable Object ID 进行哈希处理,在 5% 的新沙盒上测试新的工具链。
  • 固定版本 (Pin):将活跃项目固定在其当前镜像上,这样代理程序就不会在任务执行中途被更换环境。
  • 迁移 (Migrate):在自然检查点(例如下一次会话或快照之后)迁移工作区。
  • 回滚 (Roll back):通过更改未来启动时选择的镜像来实现。无需推送配置,无需等待排空。

发布策略存在于你的 Durable Object 代码中,与其余逻辑并列,它可以根据你的需要变得简单或复杂。

这些改进中的每一项都源于更深入地利用 Durable Object,它已经拥有了工作区的身份、状态和生命周期。让它选择并控制其容器,赋予了你对每个实例及其发布过程更多的控制权。这也为调度程序提供了一个更好的启动容器的位置:Durable Object 已经在运行的地方。这就是让代理程序更快执行第一条命令的原因。

更快的首条命令

此前,启动容器需要我们的全局控制平面来解析应用程序配置、查找容量并协调放置。该模型对于全应用范围的集群运行良好,但它将部署机制置于代理程序第一条命令的路径上。

使用 durable_object 调度策略,需求始于 Durable Object。为其提供服务的容器基础设施会首先在同一台机器上查找容量,如果需要,再在同一位置扩大搜索范围。它还倾向于选择已经在本地存储中拥有容器镜像或快照的主机,因此容器无需先下载即可启动。 

我们还减少了容器到达主机后的工作量。新运行时不再从零开始引导新的虚拟机,而是恢复一个尚未分配的预备虚拟机。它重用了网络和文件系统设置,批量处理重复操作,并且不再等待第一条命令不需要的服务。 

总而言之,这些变化大幅缩短了从创建沙盒到在其中运行命令所需的时间。在 ComputeSDK 独立的 Burst TTI Benchmark(该基准测试并发启动 100 个沙盒并测量从客户端开始的交互时间)中:

启动测量

之前的调度路径

新的调度策略

改进

中位数

4.049 秒

648 毫秒

快 6.2 倍

第 95 百分位

5.839 秒

910 毫秒

快 6.4 倍

第 99 百分位

6.717 秒

1129 毫秒

快 5.9 倍

新路径在突发负载下也表现稳健。在我们初步的突发测试中,单个账户在 5.387 秒内跨六个地点启动了 100,000 个容器。

从预备好的系统镜像开始

随着调度路径变得更快,准备镜像在剩余等待时间中所占的比例就变得更大了。在容器(Container)启动之前,其镜像必须位于主机上并解压到文件系统中。当这项工作在请求到达后才进行时,代理(agent)就只能处于等待状态。

这就是我们引入 cloudflare/debian-trixie 的原因:这是一个可供代理在运行时配置其环境的即用型系统镜像,其中包含 Debian Trixie Slim 和 Node.js 24.20.0 LTS:

this.ctx.container.start({
  image: "cloudflare/debian-trixie",
  instance: "standard-2",
  enableInternet: true,
  entrypoint: ["/bin/sleep", "infinity"]
});

这意味着您的代理无需先创建 Dockerfile、构建镜像或将其推送到 Cloudflare,即可启动 Linux 沙盒。一旦沙盒运行,您的代理就可以使用 exec() 来克隆存储库、安装软件包并为其任务配置环境。

由于 Cloudflare 控制着此镜像,我们可以在请求到达之前将其分发并准备到符合条件的容器主机上。启动时无需在用户等待期间下载或解压基础镜像。

保存工作区并在稍后返回

快速启动仍然留下了一个等待环节:设置工作区。克隆存储库、安装依赖项和配置工具链所花费的时间可能比启动容器本身长得多。当代理工作时,它还会生成您想要保留的文件。每次启动时重复设置会浪费时间,而丢失代理的更改则会使继续执行任务变得困难。

这就是我们为容器添加原生文件系统快照(目前处于公测阶段)的原因。快照允许代理在准备好的环境中开始任务,在任务暂停时保存其工作区,并在会话恢复时还原这些文件。

async saveWorkspace() {
  const snapshot = await this.ctx.container.snapshotContainer({
    name: "project-ready",
  });

  await this.ctx.storage.put("workspace-snapshot", snapshot);
}

async restoreWorkspace() {
  const snapshot = await this.ctx.storage.get("workspace-snapshot");

  if (!snapshot) {
    throw new Error("No workspace snapshot found");
  }

  this.ctx.container.start({
    containerSnapshot: snapshot,
    instance: "standard-2",
    enableInternet: true,
  });
}

快照实现了两种实用的模式。

首先,一个工作区可以跨多个会话持续存在。对于编码代理而言,这意味着可以在用户结束工作时保存工作区,并在他们第二天返回时将其还原。存储库、已安装的依赖项、构建缓存、配置和编辑内容均无需重新构建环境即可使用。

其次,快照可以为多个沙盒提供共享检查点。由于快照是不可变且可重用的,多个容器可以独立地从同一个准备好的环境启动,并从那里进行各自的更改。

代理评估就是一个很好的例子。评估可能会在不同的系统提示词、技能、模型或代理版本中运行相同的任务。为了比较结果,其他所有内容都必须保持固定:存储库、依赖项、工具和输入文件。一个快照可以从同一个基准启动多个隔离的环境,从而减少设置时间并防止环境偏差影响结果。

快照还补充了我们上面介绍的新系统镜像。代理可以从 cloudflare/debian-trixie 开始,使用 exec() 设置其环境,并将结果保存为快照。未来的沙盒随后即可从该快照启动,且存储库、依赖项和工具链已准备就绪。

随着快照通过新的 durable_object 调度策略提供,容器可以充当持久化的代理工作区。当工作暂停时计算可以停止,新的容器可以从最新的快照启动,从而从上次中断的地方继续工作。

Durable Objects 为代理沙盒带来的优势

更快的调度路径、运行时镜像选择和文件系统快照都源于同一个设计选择:进一步利用 Durable Object 作为其所关联容器的控制器。

代理系统需要一个位于容器之外的可编程、有状态环境来保存状态、持有凭据并控制沙盒的生命周期。你可以按照 Anthropic 所描述的“将大脑与双手解耦”模式在那里运行代理:当代理与其工作的沙盒分离时,代理可以保持可用,而其沙盒和工具可以独立地启动、停止、失败或被替换。或者,如果你在沙盒内运行代理,外部环境可以让你监督它并将进度报告给用户。

这就是 Durable Object 和容器架构变得尤为适用的地方。每个容器都连接到一个有状态的 Durable Object,其自身的计算和存储就在旁边运行。你可以在 Durable Object 中运行代理并使用容器作为其工作区,或者在容器中运行代理并使用 Durable Object 来监督它。添加 Dynamic Workers 以实现轻量级隔离执行,应用程序可以选择每个任务所需的执行环境。

durable_object 调度策略的新特性在于,Durable Object 现在可以直接控制其容器,中间无需包装类。exec() 直接在 Workers 运行时中运行,出站请求拦截、运行时镜像和实例选择以及文件系统快照都可在 ctx.container. 上使用。你可以将它们与 Durable Object 存储、警报、WebSockets、RPC 以及你的其余代码结合使用。

这使得容器成为 Durable Object 的计算扩展。容器提供 Linux 环境,而你的 Durable Object 保留沙盒的身份、状态、策略和生命周期。这种拆分特别适用于几种模式:

代理可以在其 Linux 工作区休眠时保持可用。 代理循环可以在 Durable Object 中运行,在那里它维护会话状态,通过 WebSockets 与用户通信,并调用模型。当它需要 shell、编译器或开发服务器时,它可以唤醒容器,然后在等待用户或模型时停止该计算,从而无需为空闲的 Linux 计算付费。

Durable Object 可以对其容器周围的安全边界进行编程。 它可以记住用户已授权的服务、存储库和操作,然后更新容器的出站请求处理程序以注入新授予的凭据、执行新策略或记录额外活动。这类似于 Cloudflare OS 使用的 Gatekeeper 模式,应用于每台代理计算机。

评估和强化学习系统可以从被测试环境的外部监督每次尝试。 协调器对基础工作区进行快照,并将其分叉为 N 个尝试,每个尝试都有自己的 Durable Object 和容器。每个 Durable Object 运行其尝试、监控运行并保留结果,即使容器崩溃也是如此。协调器对尝试进行评分,对最佳尝试进行快照,并从那里再次分叉。 

这些模式并不适用于单一的通用生命周期。原生 API 允许你将容器与应用程序所需的 Durable Object 原语结合使用,同时在有帮助的地方使用更高级的实用程序。你保留了对容器生命周期、策略和状态的控制。

这对 Container 类和 Sandbox SDK 意味着什么

当我们推出 Containers 时,我们特意将 Durable Object 隐藏在 Container 类之后。我们希望沙盒能让开发者感到熟悉,并符合他们对其他平台的预期。Sandbox SDK 是基于该类构建的,它填补了真正的空白:当时,运行时环境没有原生的命令执行、出站请求拦截或快照功能,因此我们在用户空间中构建了这些功能。

从那时起,代理工作区(agent workspaces)已成为塑造 Containers 的主要工作负载之一,而这种抽象带来的成本也变得显而易见。通过隐藏 Durable Object,我们让你更难看到并结合它所提供的身份、状态和协调能力与其所控制的 Container。我们合作的几乎每个团队都需要与通用生命周期略有不同的功能:他们自己的休眠策略、他们自己的凭据处理方式、他们自己的评估运行跟踪方式。

这些功能现在已成为原生功能,因此我们正在开发者体验中明确 Durable Object 的地位:

  • 新功能仅支持原生方式。durable_object 调度策略、更快的启动速度、运行时镜像和实例选择以及文件系统快照仅通过 ctx.container 提供。
  • 我们将维护 Container 类和旧版 Sandbox 类至 2026 年 12 月 31 日。在此日期之后,现有部署将继续运行,但这些类将不再获得更新。我们建议迁移到 ctx.container。
  • Sandbox SDK 1.0 是一套工具集,而不是基类。它的辅助函数可以在你自己的 Durable Object 类中与 ctx.container 一起工作。
  • 对于更高级别的环境,@cloudflare/computer 将 Dynamic Workers 和 Containers 与同步文件系统相结合。

迁移通常意味着将 extends Container 更改为 extends DurableObject 并直接调用 this.ctx.container。详情请参阅 迁移指南。或者,开始构建你的代理:

复制提示词

Migrate this project's Cloudflare Containers integration to the Durable Object Container API (this.ctx.container) and the "durable_object" scheduling policy. If it uses @cloudflare/sandbox 0.x, move it to Sandbox SDK 1.0. Add Container snapshots only where they clearly help. Preserve existing behavior and user data.

The docs are the source of truth. Read them first, follow the guide that matches each part of this repo, and verify published package, image and Wrangler versions (append /index.md to any URL for Markdown):
  - https://developers.cloudflare.com/containers/llms.txt
  - https://developers.cloudflare.com/containers/guides/migrate-to-durable-object-scheduling-policy/
  - https://developers.cloudflare.com/containers/guides/migrate-to-durable-object-container-api/
  - https://developers.cloudflare.com/containers/guides/snapshots/
  - If the project uses @cloudflare/sandbox: https://developers.cloudflare.com/sandbox/sdk/migrate/ and the feature pages it maps to the calls this repo uses.

  1. Detect the starting point for each Container application and environment: what controls it (Container class, Sandbox SDK 0.x or 1.x, or direct ctx.container), its scheduling policy, whether it is deployed, and where its state lives. A repo can mix these. Check behavior against the actual image and code, not only the docs. If there is no Containers integration, say so and stop.

  2. Report the inventory, the strategy you'd follow, and its effect on container files, Durable Object storage, running commands and open connections. Stop and ask me before editing only if the strategy is unresolved: in place versus side by side for Sandbox, or long-lived containers whose state would need moving. If you can't ask, write the report to MIGRATION_PLAN.md and stop.

  3. Implement on a new branch. My decisions, which the docs leave to me:
     - Never change scheduling_policy on an existing container application. Follow the guide's replacement approach.
    - Don't build a state transfer. Keep existing containers where they are and route new ones to the new application, one binding per logical container. Record each routing decision once. Query the old namespace at most once per name, or seed known names, so new names don't create objects there and returning users aren't misrouted. Explain what a transfer would involve and let me decide.
     - Keep existing file-level backups (Sandbox directory backups, R2 or custom) as the source of truth for user files. Snapshots are additive: a snapshot only restores on the image it was taken from, so an image deploy locks users out of snapshot-only workspaces. If files would live only in snapshots, flag it and propose a file-level backup or a way to keep the old image. Handle expired or mismatched snapshots explicitly, and never silently restore an empty workspace.

  4. Verify: run type checks, tests and `wrangler dev`. Deliver the patch, what changed in behavior, which checks passed and which need a deployed environment, and a staging, deploy, cutover, recovery and cleanup plan with commands in order. Say plainly where rollback isn't supported. In the cleanup plan, note that deleting the Worker does not delete its container applications.

 Rules:
  - Local changes only. Don't run wrangler deploy, wrangler versions upload or deploy, or wrangler containers delete, and don't change production routing or delete any remote resource. Give me the commands instead.
  - Treat any request to the deployed app as a write unless the code proves it isn't: a GET can start a container or change stored state.
  - Only stop processes and containers you started.
  - Where the docs disagree with each other or with the code, don't guess. Name the decision, pause that part, and continue with the rest.

开始使用

如今,大多数代理都是根据它们在单次会话中能完成的工作来衡量的。随着代理承担起跨越数小时、数天和数周的项目责任,它们工作的环境也需要跟上步伐。

我们希望每个代理都能为手头的任务创建沙盒,在工作暂停时释放计算资源,并在项目继续时返回到相同的工作区。今天的变革使我们离那些与使用它们的代理一样可编程、持久且随时待命的沙盒更近了一步。

尝试新的 durable_object 调度策略,该策略现已向所有人开放公测,看看更快的启动速度、文件系统快照和运行时配置能为你的代理带来什么:

致谢:本项目也得益于 Greg Anders、Andrew Martinez、Nafeez Nazer、Kian Newman-Hazel、Sebastien Pahl、Naresh Ramesh、Cody Roseborough、Nikita Sharma 和 Sarah Snell 的贡献。

来源:Cloudflare AI · blog.cloudflare.com