在同一个 Worker 中让 Sandbox SDK 1.0 类与 0.12 类并排运行,并在请求第一次到达某个 sandbox 时迁移它。尚未迁移的 sandbox 中的命令和连接会继续运行。当没有 sandbox 再需要 0.12 类时,删除它。
- 你的应用运行
@cloudflare/sandbox0.12.10,类和绑定名为Sandbox。 - 你的 1.0 类用新名称编写,例如
SandboxV1。选择你想保留的名称,因为稍后重命名类是另一次类迁移。有关该类,请参阅 替换 Sandbox 类。 - 你有一份带有独立 Wrangler 配置文件的预发布 Worker,使用不同的 Worker
name,以及每个容器不同的name。
本页上的代码将 /workspace 作为单个归档从 0.12 容器复制到 1.0 容器。已停止的 0.12 容器没有可复制的文件。归档以 base64 编码在一条 RPC 消息中传输,限制为 32 MiB。对于更大的工作区,请保存 0.12 备份并按 迁移备份 所述转换它。
保留其他迁移页面让你移除的 0.12 导出、变量和密钥,例如 ContainerProxy、SANDBOX_TRANSPORT 和 R2_ACCESS_KEY_ID,直到你删除 0.12 类。0.12 类仍会读取它们。
-
安装该包的 1 版本,并在别名
sandbox-0x下保留 0.12:npm i @cloudflare/sandbox sandbox-0x@npm:@cloudflare/[email protected]yarn add @cloudflare/sandbox sandbox-0x@npm:@cloudflare/[email protected]pnpm add @cloudflare/sandbox sandbox-0x@npm:@cloudflare/[email protected]bun add @cloudflare/sandbox sandbox-0x@npm:@cloudflare/[email protected]包管理器可能保存版本范围。在
package.json中将sandbox-0x设为npm:@cloudflare/[email protected],以便 0.12 代码留在该版本。在 0.12 代码中,将每个从@cloudflare/sandbox的导入改为sandbox-0x。 -
给 0.12 类添加一个报告其容器是否运行的方法,以及一个返回 1.0 代码读取的存储的方法。如果你的代码从包导出该类,请用子类替换该导出:
src/index.tsts import { getSandbox, Sandbox as SandboxBase } from "sandbox-0x"; // Preview tokens, named tunnels, and outbound rules set at runtime. const STORAGE_0X = [ "portTokens", "tunnels", "tunnels:meta", "OUTBOUND_CONFIGURATION", ]; export class Sandbox extends SandboxBase<Env> { // A 0.12 container keeps its files only while it runs. isRunning(): boolean { return this.ctx.container?.running ?? false; } exportStorage(): Promise<Map<string, unknown>> { return this.ctx.storage.get(STORAGE_0X); } }如果已有子类,请将常量和这些方法添加到它。
-
为 1.0 类创建
Dockerfile.v1。在删除 0.12 类之前保留 0.12Dockerfile:Dockerfile.v1dockerfile FROM node:24-trixie-slim COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim WORKDIR /workspace CMD ["sleep", "infinity"]复制步骤需要
tar、gzip和base64,此镜像包含它们。添加命令使用的工具。当其他迁移页面向Dockerfile添加一行时(例如迁移隧道上的cloudflared复制),请将其添加到Dockerfile.v1。 -
将 1.0 类添加到 Wrangler 配置,带有自己的容器、绑定和迁移。保持
Sandbox条目不变。将my-worker-sandbox-v1替换为账户尚未使用的名称:{ "containers": [ { "class_name": "Sandbox", "image": "./Dockerfile", // Your other 0.12 settings, unchanged. }, { "class_name": "SandboxV1", "name": "my-worker-sandbox-v1", "scheduling_policy": "durable_object", "images": { "sandbox": { "dockerfile": "./Dockerfile.v1", }, }, }, ], "durable_objects": { "bindings": [ { "class_name": "Sandbox", "name": "Sandbox" }, { "class_name": "SandboxV1", "name": "SandboxV1" }, ], }, "migrations": [ { "new_sqlite_classes": ["Sandbox"], "tag": "v1" }, { "new_sqlite_classes": ["SandboxV1"], "tag": "v2" }, ], }[[containers]] class_name = "Sandbox" image = "./Dockerfile" [[containers]] class_name = "SandboxV1" name = "my-worker-sandbox-v1" scheduling_policy = "durable_object" [containers.images.sandbox] dockerfile = "./Dockerfile.v1" [[durable_objects.bindings]] class_name = "Sandbox" name = "Sandbox" [[durable_objects.bindings]] class_name = "SandboxV1" name = "SandboxV1" [[migrations]] new_sqlite_classes = [ "Sandbox" ] tag = "v1" [[migrations]] new_sqlite_classes = [ "SandboxV1" ] tag = "v2"如果 Worker 在 Wrangler 配置中用
exports声明类,请在那里添加条目,而不是迁移:{ "exports": { "Sandbox": { "type": "durable-object", "storage": "sqlite" }, "SandboxV1": { "type": "durable-object", "storage": "sqlite" }, }, }[exports.Sandbox] type = "durable-object" storage = "sqlite" [exports.SandboxV1] type = "durable-object" storage = "sqlite" -
向 1.0 类添加迁移一个 sandbox 的方法。每个名称的复制只运行一次,并存储
moved,以便后续请求跳过它:src/index.tsts export class SandboxV1 extends DurableObject<Env> { // ... private moving: Promise<void> | null = null; async moveFrom0x(name: string): Promise<void> { // Requests that arrive during the copy wait for the same copy. this.moving ??= this.copyFrom0x(name).finally(() => { this.moving = null; }); await this.moving; } private async copyFrom0x(name: string): Promise<void> { if (await this.ctx.storage.get("moved")) { return; } const stub0x = this.env.Sandbox.getByName(name); const stored = await stub0x.exportStorage(); await this.ctx.storage.put(Object.fromEntries(stored)); if (await stub0x.isRunning()) { const legacy = getSandbox(this.env.Sandbox, name); // Stop the background processes, so no file changes during the copy. await legacy.killAllProcesses(); const path = "/tmp/workspace.tar.gz"; const packed = await legacy.exec(`tar -czf ${path} -C /workspace .`); if (!packed.success) { throw new Error(packed.stderr); } const archive = await legacy.readFile(path, { encoding: "base64", }); await this.ensureRunning(); const extract = await this.container.exec( ["sh", "-c", "base64 -d | tar -xzf - -C /workspace"], { stdin: new Blob([archive.content]).stream() }, ); const result = await extract.output(); if (result.exitCode !== 0) { const stderr = new TextDecoder().decode(result.stderr); throw new Error(stderr); } // destroy() would delete named tunnels and DNS records. await legacy.stop(); await this.ctx.storage.put("moved", true); return; } await this.ctx.storage.put("moved", true); } }ensureRunning()和container是 1.0 类中的辅助方法,前者启动容器,后者返回this.ctx.container。该方法会先复制 0.12 存储,因此即使 0.12 容器已停止,预览 URL、命名隧道和出站规则也会迁移。如果复制或stop()失败,该方法会在存储moved前抛出异常,下一次请求会重新执行迁移。复制过程会保留运行
tar时的文件状态。killAllProcesses()会停止 0.12startProcess()启动的进程。其他请求通过 0.12 运行的命令仍可能写入文件,因此迁移期间不要向该 sandbox 的 0.12 类发送其他请求。下一步中通过sandboxFor()发出的请求会等待迁移完成。0.12
stop()会保留 0.12 类的存储,以及账户中的隧道和 DNS 记录。0.12destroy()会删除预览令牌、隧道条目,以及每个命名隧道和对应的 DNS 记录。进程会随 0.12 容器结束,因此请在 1.0 容器中重新启动应用所需的进程,具体请参阅迁移后台进程。若要使用相同的命令,请在调用killAllProcesses()前从legacy.listProcesses()读取它们。 -
让每个请求都经过迁移。将此函数添加到 Worker,并将每个
getSandbox(env.Sandbox, name)调用替换为await sandboxFor(env, name):src/index.tsts async function sandboxFor(env: Env, name: string) { const sandbox = env.SandboxV1.getByName(name); await sandbox.moveFrom0x(name); return sandbox; }新 sandbox 会在 1.0 类上启动,因为它们的名称没有正在运行的 0.12 容器。
传入 0.12 使用的名称。如果 Worker 在调用
getSandbox()时传入normalizeId: true,请在调用sandboxFor()前将名称转换为小写,以便两个类使用该 sandbox 的同一个 0.12 Durable Object。 -
生成类型并部署:
npx wrangler typesyarn wrangler typespnpm wrangler typesnpx wrangler deployyarn wrangler deploypnpm wrangler deploy部署会报告 0.12 容器应用没有更改。正在运行的 0.12 容器会保留其文件和进程。
向 0.12 容器正在运行且 /workspace 中有文件的 sandbox 发送请求。第一次请求会复制文件并从 1.0 容器返回结果。对同一名称的第二次请求会跳过复制并更快返回。向新名称发送请求,它只会启动 1.0 容器。
-
迁移剩余的 sandbox。Worker 无法列出 Durable Objects,因此请使用应用的记录,为每个尚未迁移的 sandbox 发送一次请求。
-
移除 0.12 代码。删除 0.12 类、
STORAGE_0X、sandbox-0x导入,以及 1.0 类中的moving字段、moveFrom0x()和copyFrom0x()。在代码曾调用sandboxFor()的地方调用env.SandboxV1.getByName(name)。同时移除你保留的 0.12 导出、变量和密钥,例如ContainerProxy、SANDBOX_TRANSPORT和R2_ACCESS_KEY_ID。 -
从 Wrangler 配置中移除
Sandbox容器和绑定,并添加删除该类的迁移:{ "migrations": [ { "new_sqlite_classes": ["Sandbox"], "tag": "v1" }, { "new_sqlite_classes": ["SandboxV1"], "tag": "v2" }, { "deleted_classes": ["Sandbox"], "tag": "v3" }, ], }[[migrations]] new_sqlite_classes = [ "Sandbox" ] tag = "v1" [[migrations]] new_sqlite_classes = [ "SandboxV1" ] tag = "v2" [[migrations]] deleted_classes = [ "Sandbox" ] tag = "v3"如果 Worker 在 Wrangler 配置中使用
exports,请改为将该类标记为已删除:{ "exports": { "Sandbox": { "type": "durable-object", "state": "deleted" }, "SandboxV1": { "type": "durable-object", "storage": "sqlite" }, }, }[exports.Sandbox] type = "durable-object" state = "deleted" [exports.SandboxV1] type = "durable-object" storage = "sqlite" -
移除别名,生成类型并部署:
npm uninstall sandbox-0xyarn remove sandbox-0xpnpm remove sandbox-0xbun remove sandbox-0xnpx wrangler typesyarn wrangler typespnpm wrangler typesnpx wrangler deployyarn wrangler deploypnpm wrangler deploy -
删除类不会删除其容器应用。Wrangler 根据 Worker 和类名命名该应用,例如
my-worker-sandbox。列出应用以找到它:npx wrangler containers listyarn wrangler containers listpnpm wrangler containers list -
删除旧应用。将
<OLD_APPLICATION_ID>替换为列表中的 ID:npx wrangler containers delete <OLD_APPLICATION_ID>yarn wrangler containers delete <OLD_APPLICATION_ID>pnpm wrangler containers delete <OLD_APPLICATION_ID>旧应用会继续运行其容器并为它们计费,直到你删除它。