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

列出用户的 sandbox

最后更新 查看 MarkdownAgent 设置

每个用户都有一个 registry:记录该用户 sandbox 的 Durable Object。本例中,当 sandbox 首次运行命令时,Worker 会将其添加到 registry。要报告哪些 sandbox 正在运行,Worker 会询问每个 sandbox 的 Durable Object 其 container 是否在运行。

前提条件

列出 sandbox

  1. 在 wrangler.jsonc 中,为 registry 类添加绑定,并在 exports 中声明该类:

    {
    	"durable_objects": {
    		"bindings": [
    			{
    				"class_name": "MyContainer",
    				"name": "MY_CONTAINER",
    			},
    			{
    				"class_name": "SandboxDirectory",
    				"name": "SANDBOX_DIRECTORY",
    			},
    		],
    	},
    	"exports": {
    		"MyContainer": {
    			"type": "durable-object",
    			"storage": "sqlite",
    		},
    		"SandboxDirectory": {
    			"type": "durable-object",
    			"storage": "sqlite",
    		},
    	},
    }
    [[durable_objects.bindings]]
    class_name = "MyContainer"
    name = "MY_CONTAINER"
    
    [[durable_objects.bindings]]
    class_name = "SandboxDirectory"
    name = "SANDBOX_DIRECTORY"
    
    [exports.MyContainer]
    type = "durable-object"
    storage = "sqlite"
    
    [exports.SandboxDirectory]
    type = "durable-object"
    storage = "sqlite"

    如果你的 Worker 改用 migrations 数组声明类,请添加带有新 tag 和 "new_sqlite_classes": ["SandboxDirectory"] 的迁移。更多信息请参阅 Durable Object 类迁移(旧版)。

    npx wrangler types
  2. 将 registry 类添加到 Worker。每个用户获得一个 SandboxDirectory,它会在 SQLite 中存储该用户的 sandbox session:

    src/index.tsts
    export class SandboxDirectory extends DurableObject<Env> {
    	constructor(ctx: DurableObjectState, env: Env) {
    		super(ctx, env);
    		ctx.storage.sql.exec(
    			"CREATE TABLE IF NOT EXISTS sandboxes (session TEXT PRIMARY KEY, created_at INTEGER NOT NULL)",
    		);
    	}
    
    	add(session: string) {
    		// Keep the time of the first command when the same session runs again
    		this.ctx.storage.sql.exec(
    			"INSERT OR IGNORE INTO sandboxes (session, created_at) VALUES (?, ?)",
    			session,
    			Date.now(),
    		);
    	}
    
    	remove(session: string) {
    		this.ctx.storage.sql.exec("DELETE FROM sandboxes WHERE session = ?", session);
    	}
    
    	list() {
    		return this.ctx.storage.sql
    			.exec<{ session: string }>("SELECT session FROM sandboxes ORDER BY created_at")
    			.toArray()
    			.map((row) => row.session);
    	}
    }
  3. 在 MyContainer 中设置不活动超时,并添加报告和停止 container 的方法。在文件顶部定义超时,并添加构造函数,以便在重启后的 Durable Object 发现 container 仍在运行时设置超时:

    src/index.tsts
    const INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
    
    export class MyContainer extends DurableObject<Env> {
    	constructor(ctx: DurableObjectState, env: Env) {
    		super(ctx, env);
    		const container = ctx.container;
    		if (container?.running) {
    			void ctx.blockConcurrencyWhile(() =>
    				container.setInactivityTimeout(INACTIVITY_TIMEOUT_MS),
    			);
    		}
    	}
    }

    在 exec() 中,于 start() 之后、if (!container.running) 块内设置超时:

    src/index.tsts
    await container.setInactivityTimeout(INACTIVITY_TIMEOUT_MS);

    然后将这两个方法添加到 MyContainer:

    src/index.tsts
    export class MyContainer extends DurableObject<Env> {
    	// ...
    
    	isRunning() {
    		// Reading `running` does not start the container
    		return this.ctx.container?.running ?? false;
    	}
    
    	async destroy() {
    		await this.ctx.container?.destroy();
    	}
    }

    列出请求可能会唤醒重启后的 Durable Object,而它启动时没有 exec() 设置的超时。构造函数会再次设置超时,因此 sandbox 不会在列出请求结束后很快停止。更多信息请参阅 Sandbox 生命周期。

  4. 向 Worker 添加路由:将每个 sandbox 命名为 <OWNER>/<SESSION>,在运行命令前记录 session,列出带状态的 session,并在停止 container 后移除 session:

    src/index.tsts
    export default {
    	async fetch(request: Request, env: Env): Promise<Response> {
    		const url = new URL(request.url);
    		const [, users, owner, sandboxes, session] = url.pathname.split("/");
    
    		if (users !== "users" || !owner || sandboxes !== "sandboxes") {
    			return new Response("Not found", { status: 404 });
    		}
    
    		const directory = env.SANDBOX_DIRECTORY.getByName(owner);
    
    		if (request.method === "GET" && !session) {
    			const sessions = await directory.list();
    			// Call the Durable Object of every session in parallel
    			const list = await Promise.all(
    				sessions.map(async (name) => ({
    					session: name,
    					running: await env.MY_CONTAINER.getByName(`${owner}/${name}`).isRunning(),
    				})),
    			);
    			return Response.json(list);
    		}
    
    		if (!session) {
    			return new Response("Not found", { status: 404 });
    		}
    
    		const sandbox = env.MY_CONTAINER.getByName(`${owner}/${session}`);
    
    		if (request.method === "POST") {
    			const { argv } = (await request.json()) as { argv: string[] };
    			await directory.add(session);
    			return Response.json(await sandbox.exec(argv));
    		}
    
    		if (request.method === "DELETE") {
    			await sandbox.destroy();
    			await directory.remove(session);
    			return new Response(null, { status: 204 });
    		}
    
    		return new Response("Not found", { status: 404 });
    	},
    };

    container 已停止的 sandbox 会以 running: false 留在列表中,直到 Worker 移除其 session。

    请先认证调用方,并根据其身份派生 owner,而不是从 URL 读取。否则任何人都可以列出、使用或停止其他用户的 sandbox。更多信息请参阅 Sandbox 安全。

  5. 部署你的 Worker:

    npx wrangler deploy
  6. 为用户 ada 启动两个 sandbox,使用 Wrangler 打印的 workers.dev URL:

    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/users/ada/sandboxes/notes --request POST --json '{"argv":["uname","-s"]}'
    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/users/ada/sandboxes/tests --request POST --json '{"argv":["uname","-s"]}'
  7. 列出它们:

    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/users/ada/sandboxes
    [
    	{ "session": "notes", "running": true },
    	{ "session": "tests", "running": true }
    ]
  8. 停止其中一个,然后再次列出:

    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/users/ada/sandboxes/tests --request DELETE
    curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/users/ada/sandboxes
    [{ "session": "notes", "running": true }]

列出账户中的 sandbox

registry 只列出 Worker 记录的 sandbox。Containers API 会列出 Container 应用启动的每个 sandbox,包括失败的或 registry 遗漏的 sandbox。对于每个 sandbox,实例列表会返回传给 getByName() 的名称和状态:

curl "https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/containers/applications/<APPLICATION_ID>/instances?name_prefix=ada/" \
	--header "Authorization: Bearer <API_TOKEN>"
  • 应用 ID 会出现在 wrangler containers list 中。应用以 Worker 和 Durable Object 类命名,例如 sandbox-linux-mycontainer。
  • API token 需要 Containers Read 权限,该权限覆盖账户中的每个 Container 应用。将其用于管理和清理,而不是用户调用的路由。
  • name_prefix 按名称开头过滤,state=active 只返回正在启动、运行或停止的 sandbox。已停止的 sandbox 仍会留在列表中。
  • 新 sandbox 可能需要几秒钟才会出现,在 wrangler dev 下运行的 sandbox 不会出现。

相关资源

这篇文档对您有帮助吗?