跳到主要内容

上层能力:解释器、隧道、备份

前四章是「跑命令、连通道、暴露端口」的主干。本章是建在主干之上的三个高层能力, 它们都把前面的机制(会话、RPC、运行时身份)当积木用。

1. 代码解释器:不止拿 stdout,而是拿「结果对象」

1.1 要解决的小问题

exec('python3 script.py') 只给你 stdout 字符串。但 AI / Notebook 场景里你常想要 结构化产物:一张 matplotlib 图、一个 DataFrame 表格、一个返回值。光抓 stdout 拿不到这些。

1.2 思路:把执行结果分成四类事件

CodeInterpreter(packages/sandbox/src/interpreter.ts:14)在一个代码上下文 (持久的 Python/JS 内核,类似 Jupyter 的 kernel)里跑代码,并把输出分流成四类回调:

事件含义收集到
onStdout标准输出文本execution.logs.stdout
onStderr标准错误文本execution.logs.stderr
onResult富结果(图/表/值,可有多种 MIME)execution.results
onError执行异常execution.error
// packages/sandbox/src/interpreter.ts:61 —— 真实源码(节选)
await this.getInterpreterClient().runCodeStream(context.id, code, options.language, {
onStdout: (o) => { execution.logs.stdout.push(o.text); ... },
onResult: async (result) => { execution.results.push(new ResultImpl(result) ...); ... },
onError: (error) => { execution.error = error; ... },
});
return execution; // 一个聚合了 logs / results / error 的 Execution 对象

1.3 上下文复用

runCode 不传 context 时,getOrCreateDefaultContext(interpreter.ts:132)会按语言 复用一个缓存的默认上下文——所以连续 runCode 之间变量是保留的(像 Notebook 单元格), 而不是每次新内核。容器侧的真正执行在 InterpreterService,Python 走 runtime/executors/python/ipython_executor.py(IPython 内核),JS 走 node_executor.ts

DO 侧把解释器封成 sandbox.runCode / runCodeStream / createCodeContext 等 (sandbox.ts:5778-5803),底层是 this.codeInterpreter,而 codeInterpreter 又指向 当前 client.interpreter(sandbox.ts:1314)——所以换传输时解释器自动跟着换

安全: 语言在客户端就白名单校验(security.ts:validateLanguage,只允许 python/js/ts 及别名),非法语言在发往容器前就被 SandboxSecurityError 挡掉。

2. 快速隧道:一次性公网 URL

sandbox.tunnels.get(8080)(README「Quick tunnels」)给容器内端口开一个 *.trycloudflare.com 公网地址,不需要 Cloudflare 账号或 DNS 配置——cloudflared 跟 Cloudflare 边缘建一条持久 QUIC 连接,边缘回一个主机名。

关键性质:

  • 幂等。 get(port) 先查 DO 存储里的每沙箱缓存,命中就返回缓存记录,未命中才 spawn 一个 新 cloudflared 进程(README)。同端口重复 get 返回同一条记录。
  • 不跨重启存活。 主机名是 cloudflared 启动握手时分配的,每次容器重启都换;SDK 在容器启动时 清缓存,重启后下一次 get 给你一条全新记录。
  • 需要 RPC 传输。 路由式传输的 tunnels 桩会抛 "RPC transport required"。

隧道与预览 URL(§4)是两条独立的「对外暴露」路线:预览 URL 要你自带通配域名、走 Worker 路由; 隧道零配置、走 cloudflared,但 URL 易变。DO 侧通过 callTunnels(sandbox.ts:1078)分发 get/list/destroy,实现在 tunnels/tunnels-handler.ts

反向通道在这里也有体现:cloudflared 死掉时,容器通过 capnweb 会话回调 DO 的 SandboxControlCallback(§2 文档),触发 tunnelExitHandler 清理(sandbox.ts:961 附近注释)。

3. R2 备份与恢复:跨重启留住文件

会话状态和容器文件在容器被回收后就没了(§3 文档边界)。要持久化,得显式备份。

createBackup(sandbox.ts:6551)/ restoreBackup(sandbox.ts:7004)的设计要点:

  • 压成 squashfs 直传 R2。 备份把目录打成 data.sqsh(squashfs 镜像)+ meta.json, 默认 lz4 压缩(常量 BACKUP_*,sandbox.ts:393-407),用 R2 预签名 URL 让容器直接 传给 R2,不经过 DO。
  • 大文件自适应分片。 calculatePartCount(sandbox.ts:413)按归档大小决定 multipart 分片数:<100MiB 用默认、100MiB–1GiB 升到 32、≥1GiB 用上限 64。下载同理并行分片。
  • 串行化防并发。 backupInProgress(sandbox.ts:1010)是个 in-memory 的 Promise 链, 保证同一沙箱不会同时 create/restore。注释坦白这是内存态、DO 驱逐后会丢——但没关系, 因为容器文件系统也一起没了,没有可竞争的归档。
  • 配置严格校验。 BACKUP_BUCKET_ENDPOINT 必须是 https、无路径、无 query/fragment 的 纯 origin,否则构造时就抛 InvalidBackupConfigError(sandbox.ts:1246-1295)。
createBackup: 容器目录 ──tar+squashfs(lz4)──▶ data.sqsh ──预签名 multipart──▶ R2
restoreBackup: R2 ──并行分片下载──▶ data.sqsh ──解包──▶ 容器目录

4. 巧妙之处汇总(跨能力)

  • 「DO 当真相源」反复出现: 端口 token、隧道缓存、备份串行锁、配置——凡是要跨容器重启或 防并发的状态,都放 DO(存储或内存),容器只管执行。
  • 传输无关: 解释器、tunnels 都通过 () => this.client.xxx 的间接层绑定当前 client, setTransport 一换,它们自动指向新通道(sandbox.ts:13141562)。
  • 容器→DO 反向回调: 隧道退出通知证明这条 capnweb 会话是双向的,不只是 DO 单向调容器。

5. 边界与局限

  • 备份要 R2 + 四个凭证齐全 才工作(r2AccessKeyId/r2SecretAccessKey/账号 id/bucket, sandbox.ts:1016-1021),缺一不可。
  • 隧道 URL 易变(每次重启换),且 trycloudflare 对 SSE 缓冲(README)。
  • 解释器语言受限:仅 Python / JavaScript / TypeScript(security.ts:validateLanguage)。

6. 代码地图

主题文件符号名
解释器(DO 侧封装)packages/sandbox/src/interpreter.tsCodeInterpreterrunCodegetOrCreateDefaultContext
解释器(DO 公开方法)packages/sandbox/src/sandbox.tsrunCoderunCodeStreamcreateCodeContext
解释器(容器侧)packages/sandbox-container/src/services/interpreter-service.tsInterpreterService
Python 内核packages/sandbox-container/src/runtime/executors/python/ipython_executor.py(IPython 执行器)
隧道分发packages/sandbox/src/sandbox.tscallTunnels
隧道实现packages/sandbox/src/tunnels/tunnels-handler.tscreateTunnelsHandler
备份/恢复packages/sandbox/src/sandbox.tscreateBackuprestoreBackupcalculatePartCount
备份恢复生命周期packages/sandbox/src/backup/restore-lifecycle.tsRestoreLifecycleRunner
语言白名单packages/sandbox/src/security.tsvalidateLanguage