将 0.12 Sandbox 类替换为你自己编写的 Durable Object 类。在 0.12 中,Worker 通过 getSandbox() 调用包方法。在 1.0 中,你的类通过 this.ctx.container 启动容器,Worker 调用你的类定义的方法。
你的应用运行的是 @cloudflare/sandbox 0.12.14。本页会替换 Sandbox 类,无论它是从软件包中导出的,还是你扩展得到的。本页还会替换 getSandbox() 及其选项、sleepAfter 和 envVars 等类字段,以及 onStart()、onStop()、onError() 和 onActivityExpired() 等钩子。
本页结束于 Worker 在 wrangler dev 下运行。你无法撤销将 sandbox 切换到 1.0 的那次部署。部署前请遵循 规划迁移到 Sandbox SDK 1.0。
这些步骤保留你的类名,从而就地迁移每个 sandbox。要并排迁移 sandbox,请用新名称(例如 SandboxV1)编写该类,并按其 并排迁移 sandbox 所述添加 Wrangler 配置。
-
安装该包的 1 版本:
npm i @cloudflare/sandboxyarn add @cloudflare/sandboxpnpm add @cloudflare/sandboxbun add @cloudflare/sandbox -
将
Dockerfile中的FROM docker.io/cloudflare/sandbox行替换为包含命令所用工具的基础镜像,并将sandbox-shimhelper 复制进去:Dockerfiledockerfile FROM node:24-trixie-slim RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates git python3 \ && rm -rf /var/lib/apt/lists/* COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim WORKDIR /workspace CMD ["sleep", "infinity"]0.12 镜像包含 Python、Node.js 和 Git。请保留命令所需的工具。
Files、S3Mount和DirectoryBackup需要sandbox-shim。请将其标签与已安装的@cloudflare/sandbox版本保持一致。删除EXPOSE行,因为 1.0 不使用它。exec()运行的命令无法读取ENV行,因此请改为通过env选项传入这些值。 -
更改 Wrangler 配置中该类的
containers条目。保留类名及其绑定,以便每个名称到达它在 0.12 中到达的 Durable Object 和存储。将my-worker-mysandbox-v2替换为账户尚未使用的名称:{ "containers": [ { "class_name": "MySandbox", "name": "my-worker-mysandbox-v2", "scheduling_policy": "durable_object", "images": { "sandbox": { "dockerfile": "./Dockerfile", }, }, }, ], }[[containers]] class_name = "MySandbox" name = "my-worker-mysandbox-v2" scheduling_policy = "durable_object" [containers.images.sandbox] dockerfile = "./Dockerfile"新条目做了这些更改:
scheduling_policy: "durable_object"让每个 Durable Object 启动自己的容器。此策略不接受image、instance_type或max_instances,因此请移除它们。Dockerfile移到images,实例类型移到start()。- 容器应用获得新的
name。没有它时,新应用会占用 Wrangler 为旧应用生成的名称,并且在新 Worker 版本已经上线后部署会失败。 - 保持
durable_objects以及你的migrations或exports不变。该类保留名称,因此不需要类迁移。
同时从
vars中移除SANDBOX_TRANSPORT、SANDBOX_INSTANCE_TIMEOUT_MS、SANDBOX_PORT_TIMEOUT_MS和SANDBOX_POLL_INTERVAL_MS,并保留 1.0 所需的nodejs_compat。 -
检查 compatibility date。
-
为新配置生成类型:
npx wrangler typesyarn wrangler typespnpm wrangler types -
编写该类。在此示例中,0.12 子类设置生命周期和环境变量,并覆盖
onStart():src/index.ts (0.12)ts import { getSandbox, Sandbox } from "@cloudflare/sandbox"; export class MySandbox extends Sandbox<Env> { sleepAfter = "30m"; envVars = { NODE_ENV: "test" }; override async onStart() { await this.ctx.storage.put("startedAt", Date.now()); } }在 1.0 中,该类扩展
DurableObject并自行启动容器:src/index.ts (1.0)ts import { DurableObject } from "cloudflare:workers"; // Replaces sleepAfter and envVars. const INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000; const ENV = { NODE_ENV: "test" }; export class MySandbox extends DurableObject<Env> { constructor(ctx: DurableObjectState, env: Env) { super(ctx, env); const container = ctx.container; // A restarted Durable Object sets the timeout again. if (container?.running) { void ctx.blockConcurrencyWhile(() => container.setInactivityTimeout( INACTIVITY_TIMEOUT_MS, ), ); } } private get container(): Container { const container = this.ctx.container; if (!container) { throw new Error("No container is configured"); } return container; } private setup: Promise<void> | null = null; private ensureRunning(): Promise<void> { // Set up each new container, and a running container // after this Durable Object restarts. if (this.setup === null || !this.container.running) { this.setup = this.startContainer().catch((error) => { this.setup = null; throw error; }); } return this.setup; } private async startContainer(): Promise<void> { const newContainer = !this.container.running; if (newContainer) { this.container.start({ image: this.container.images.sandbox, instance: "standard-1", env: ENV, // 0.12 allowed Internet access by default. enableInternet: true, }); } try { if (newContainer) { // Replaces onStart(). await this.ctx.storage.put("startedAt", Date.now()); } await this.container.setInactivityTimeout( INACTIVITY_TIMEOUT_MS, ); } catch (error) { // The next request starts a new container. await this.container.destroy(); throw error; } } }在每次使用容器之前调用
ensureRunning()。它为每个新容器运行一次startContainer(),并在重启后的 Durable Object 发现容器仍在运行时再运行一次。部署会重启 Durable Object,并可能让设置中途停止,因此start()之后的每一步都必须可以再次运行。start()一返回running就是true,因此设置期间到达的请求会等待同一次startContainer()调用。如果某一步设置抛出错误,startContainer()会停止容器,因此没有请求会在未设置完成的容器中运行命令。start()在容器就绪前返回,第一次exec()会等待它。inactivity timeout 在 Durable Object 重启后不会保留。当重启后的 Durable Object 发现容器仍在运行时,构造函数会再次设置它。超时最多接受 6 小时。如果命令不需要 Internet,请将enableInternet设为false。 -
替换代码设置的每个类字段和
getSandbox()选项:0.12 1.0 sleepAfter在 start()之后调用setInactivityTimeout(),并在构造函数中再次调用。keepAlive在工作仍在进行时、比 inactivity timeout 更频繁运行的 alarm。 envVars,setEnvVars()主进程的 start()上的env,以及每次exec()调用上的env。命令不继承start()变量,PATH除外。entrypointstart()上的entrypoint,或镜像中的CMD。enableInternetstart()上的enableInternet,每次调用都必须传入。0.12 默认为true。labels,setLabels()start()上的labels。defaultPort,requiredPortsthis.container.getTcpPort(port)。请参阅 迁移预览 URL。allowedHosts,deniedHosts,outboundByHost,outbound,interceptHttps请参阅 迁移出站规则。 normalizeId在 getByName()之前将名称转为小写。containerTimeouts需要截止时间的调用上的 AbortSignal。请参阅 替换超时。enableDefaultSession无。每次 exec()调用都是自己的进程。请参阅 替换会话。transport,pingEndpoint无。移除它们。 -
替换子类覆盖的钩子:
onStart():在startContainer()中、在if (newContainer)内运行该代码,如上一步的类所示。onStop()和onError():用monitor()监视容器,并在调用start()和exec()的地方捕获错误。onActivityExpired():inactivity timeout 停止容器之前不会运行代码。要先运行代码,请在 alarm 中保留自己的空闲截止时间,并在到期时调用this.container.destroy()。有关在 sandbox 停止前保存它的示例,请参阅 自动保存 sandbox。
要监视容器,请在
startContainer()的try块中调用类似这样的方法。重启后的 Durable Object 会再次运行startContainer(),因此它也会监视正在运行的容器:src/index.tsts export class MySandbox extends DurableObject<Env> { // ... private watch(): void { this.container.monitor().then( () => console.log("Exited with code 0"), (error: unknown) => console.error("Stopped", error), ); } }当主进程以代码
0退出,或destroy()在未传入原因的情况下停止容器时,monitor()会解析成功。进程失败或destroy()传入原因时,该方法会拒绝。Durable Object 离开内存时调用会结束,因此无法捕获下一次请求前发生的停止事件。调用期间 Durable Object 会保持在内存中,不会开始不活动超时,容器也会在超时后继续运行。更多信息请参阅 Sandbox 生命周期。要查看每次停止的记录,请参阅在 sandbox 停止时运行代码。 -
将 Worker 发出的调用移到类上的方法中。在 0.12 中,Worker 在
getSandbox()返回的 stub 上调用包方法:src/index.ts (0.12)ts export default { async fetch(request: Request, env: Env): Promise<Response> { const id = new URL(request.url).pathname.slice(1); const sandbox = getSandbox(env.MY_SANDBOX, id, { normalizeId: true, }); const result = await sandbox.exec("npm test", { cwd: "/workspace/app", }); return Response.json({ exitCode: result.exitCode, output: result.stdout, }); }, };在 1.0 中,stub 只有你的类定义的方法。向
MySandbox添加方法,并从 Worker 调用它:src/index.ts (1.0)ts export class MySandbox extends DurableObject<Env> { // ... async test(): Promise<{ exitCode: number; output: string }> { await this.ensureRunning(); const process = await this.container.exec(["npm", "test"], { cwd: "/workspace/app", env: ENV, }); const result = await process.output(); return { exitCode: result.exitCode, output: new TextDecoder().decode(result.stdout), }; } }src/index.ts (1.0)ts export default { async fetch(request: Request, env: Env): Promise<Response> { const id = new URL(request.url).pathname.slice(1); // Replaces normalizeId: true. const sandbox = env.MY_SANDBOX.getByName(id.toLowerCase()); return Response.json(await sandbox.test()); }, } satisfies ExportedHandler<Env>;将 0.12 子类定义的方法移到 1.0 类中。Worker 像在 0.12 中一样在 stub 上调用它们。在这些方法内部,将
this.exec()和其他包调用替换为对this.container的调用。有关每类调用,请参阅 更改命令调用 以及 从 Sandbox SDK 0.x 迁移 中的功能页面。
在本地启动 Worker:
npx wrangler devyarn wrangler devpnpm wrangler devWrangler 启动时会构建镜像。向使用 sandbox 的每条路由发送请求。每个名称的第一次请求会启动容器并等待它。每条路由返回它在 0.12 上返回的内容。
- 要部署新类,请遵循 规划迁移到 Sandbox SDK 1.0。
- 要更改
exec()和会话,请参阅 更改命令调用。 - 要更改文件调用,请参阅 更改文件调用。
- 有关
this.ctx.container上的方法,请参阅 Container API。