跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

替换 Sandbox 类

最后更新 查看 MarkdownAgent 设置

将 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. 安装该包的 1 版本:

    npm i @cloudflare/sandbox
  2. 将 Dockerfile 中的 FROM docker.io/cloudflare/sandbox 行替换为包含命令所用工具的基础镜像,并将 sandbox-shim helper 复制进去:

    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 选项传入这些值。

  3. 更改 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。

  4. 检查 compatibility date。

  5. 为新配置生成类型:

    npx wrangler types
  6. 编写该类。在此示例中,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。

  7. 替换代码设置的每个类字段和 getSandbox() 选项:

    0.12 1.0
    sleepAfter 在 start() 之后调用 setInactivityTimeout(),并在构造函数中再次调用。
    keepAlive 在工作仍在进行时、比 inactivity timeout 更频繁运行的 alarm。
    envVars, setEnvVars() 主进程的 start() 上的 env,以及每次 exec() 调用上的 env。命令不继承 start() 变量,PATH 除外。
    entrypoint start() 上的 entrypoint,或镜像中的 CMD。
    enableInternet start() 上的 enableInternet,每次调用都必须传入。0.12 默认为 true。
    labels, setLabels() start() 上的 labels。
    defaultPort, requiredPorts this.container.getTcpPort(port)。请参阅 迁移预览 URL。
    allowedHosts, deniedHosts, outboundByHost, outbound, interceptHttps 请参阅 迁移出站规则。
    normalizeId 在 getByName() 之前将名称转为小写。
    containerTimeouts 需要截止时间的调用上的 AbortSignal。请参阅 替换超时。
    enableDefaultSession 无。每次 exec() 调用都是自己的进程。请参阅 替换会话。
    transport, pingEndpoint 无。移除它们。
  8. 替换子类覆盖的钩子:

    • 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 停止时运行代码。

  9. 将 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 dev

Wrangler 启动时会构建镜像。向使用 sandbox 的每条路由发送请求。每个名称的第一次请求会启动容器并等待它。每条路由返回它在 0.12 上返回的内容。

后续步骤

这篇文档对您有帮助吗?