从一次 run_code 看完整数据链路
当你调用 sdk.sandbox.run_code(code="print('hello')") 时,请求并没有经过 CubeMaster 的调度器,而是直接走了数据面:SDK → CubeProxy → 沙箱 49999 端口 → envd 执行器。
这篇文章把这条数据面链路拆开,告诉你每一步该去读哪个源码文件。
1. 总览:run_code 请求经过了哪些组件
2. 第一步:SDK 构造请求
你要读的文件
| 文件 | 看什么 |
|---|---|
sdk/go/sandbox.go | RunCode() — 构造 HTTP POST |
sdk/go/envd.go | envd 客户端封装 |
sdk/go/sandbox.go | GetHost(port) — 生成虚拟主机名 |
关键代码路径
go
// sdk/go/sandbox.go
func (s *Sandbox) RunCode(code, language string, envVars map[string]string) {
host := s.GetHost(JupyterPort) // → "49999-sb-123.cube.app"
url := fmt.Sprintf("https://%s/execute", host)
body := map[string]string{
"code": code,
"language": language,
"env_vars": envVars,
}
resp := http.Post(url, body)
s.parseStream(resp) // 解析流式事件
}关键设计:
GetHost(port)生成<port>-<sandboxID>.<domain>格式的虚拟主机名- 这个格式就是 CubeProxy 的路由键
- SDK 不关心节点 IP,只关心 sandboxID 和 domain
3. 第二步:CubeProxy 路由
你要读的文件
| 文件 | 看什么 |
|---|---|
CubeProxy/lua/rewrite_phase.lua | Host 解析、sandboxID 提取 |
CubeProxy/lua/sandbox_backend.lua | 路由元数据查询、upstream 选择 |
CubeProxy/lua/balancer_phase.lua | 负载均衡 |
关键流程
关键设计:
- CubeProxy 不做业务逻辑,只做路由
- 路由元数据存在 Redis 中,由 CubeMaster 在创建成功后写入
- 一个 sandboxID 可能有多个端口映射(49999 for envd、49983 for process API、自定义端口等)
4. 第三步:CubeVS 网络转发
你要读的文件
| 文件 | 看什么 |
|---|---|
CubeNet/src/mvmtap.bpf.c | TAP 设备入口、DNAT 逻辑 |
CubeNet/src/natenic.bpf.c | NAT session 管理 |
CubeNet/src/session.h | session map 数据结构 |
关键流程
- 流量到达宿主机网卡
- CubeVS eBPF 程序根据目的端口查找 session map
- 执行 DNAT:将宿主端口映射回沙箱 TAP 设备的端口
- 包通过 TAP 设备进入 guest 网络栈
5. 第四步:envd 执行器
你要读的文件
| 文件 | 看什么 |
|---|---|
agent/src/sandbox.rs | guest 内沙箱管理 |
agent/src/rpc.rs | vsock RPC 服务 |
关键动作
- envd 监听沙箱内 49999 端口
- 收到
POST /execute后:- 解析 code + language + env_vars
- 启动对应语言的解释器
- 执行代码
- 通过流式 HTTP 响应返回 stdout / result / error 事件
6. 与 commands.run 的对比
| 维度 | run_code | commands.run |
|---|---|---|
| 端口 | 49999 | 49999 |
| 路径 | POST /execute | POST /process.Process/Start |
| 语义 | 代码解释器 | 进程执行器 |
| 返回 | 结构化执行事件流 | connect 风格 process 事件流 |
| 用途 | 执行 Python/JS 等代码 | 执行 shell 命令 |
7. 完整链路速查表
| 阶段 | 组件 | 关键文件 | 关键函数/配置 |
|---|---|---|---|
| 构造请求 | SDK | sdk/go/sandbox.go | RunCode(), GetHost() |
| 解析 Host | CubeProxy | lua/rewrite_phase.lua | Host 解析逻辑 |
| 查路由表 | CubeProxy | lua/sandbox_backend.lua | Redis 查询 |
| 网络转发 | CubeVS | CubeNet/src/mvmtap.bpf.c | DNAT + TAP |
| 执行代码 | envd | agent 内部 | /execute handler |
| 流式返回 | SDK | sdk/go/sandbox.go | parseStream() |
延伸阅读
- 一次 run_code 的完整数据面时序图 — 架构视角的数据面时序
- 网络请求是如何进入沙箱的 — CubeProxy + CubeVS 协作
- Agent 的 Tool Call 如何在沙箱里执行 — 控制面 vs 数据面