Skip to content
View as Markdown

多实例 ​

N 个控制面实例挂在一个负载均衡后面,共享一个 PostgreSQL,每个实例有自己的 -node-url(对端能访问到的内网地址)。

bash
./server -db postgres://… -port 8080 -node-url http://10.0.1.5:8080   # A
./server -db postgres://… -port 8080 -node-url http://10.0.1.6:8080   # B

一个 worker 拨向其中一个实例,它的隧道就活在那个进程的内存里。数据库只记录哪个 节点持有它(tunnel_owners,每次心跳续期,可信 90 秒)。下面所有事情都源于这一条。

什么会跨实例 ​

请求落在错误节点时的行为
POST /sandboxes/execute转发给持有隧道的节点
/sandboxes/{name}/toolbox/*(文件、PTY、流)转发
E2B 面转发
POST /api/v1/sandboxes(创建)由持有隧道的节点投递(POST /internal/provision)

执行和代理路径转发整个请求:对着持有连接的那个节点做一次无状态重放。

创建不能重放 —— 它会扣额度、写记录、记 job —— 所以只有它的最后一步跨网络。接收 请求的实例做完到容器之前的所有事,然后请持有隧道的节点把它推下去。

不需要粘性路由,这正是几十台 worker 的机队能用的原因。

创建能在"接单的进程"死掉后活下来 ​

"接受"和"投递"是两步,进程可能死在中间。载荷在尝试投递之前就落进 jobs.request, 所以任何实例都能把它做完:

步骤落地的东西
接受沙箱记录、job 行,以及要发出去的精确载荷
投递成功后 job 变 COMPLETED
恢复任何实例发现超过 45 秒的 PENDING 创建,认领并投递

认领是对 jobs.updated_at 的 compare-and-swap,所以 N 个实例里恰好一个投递。语义是 至少一次,worker 能容忍重投,因为 pode 是用沙箱名给容器命名的。

如果沙箱记录已经不在了,或者已经是 ERROR / STOPPED,job 会被判失败而不是重试: 重试一个被删除的沙箱,会让它在每一轮都复活一次。

必须成立的前提 ​

数据库每个实例都用 PostgreSQL;SQLite 只支持单实例
-node-url / CLOUD_NODE_URL每个实例都要有,且和 tunnel_owners 里出现的字符串一致
凭据所有实例用同一套签发材料,否则 A 发的会话会被 B 拒绝
/internal/必须到达控制面,而不是 SPA 的 catch-all —— 给未知路径返回 index.html 的门户会用 HTML 应答对端
限流计数是每实例独立的:N 个实例意味着 N 倍限额
迁移在新实例启动前跑一次;它们只做加法

验证它 ​

bash
# owner 记录必须存在且新鲜
psql -tAc "select tunnel_key, node_url, updated_at, now() from tunnel_owners"

# 在 A 上执行,从 B 上执行同一句 —— 第二次必须是转发
curl -s -X POST http://A/api/v1/sandboxes/execute?sandbox=… -d '{"language":"bash","code":"echo ok"}'
curl -s -X POST http://B/api/v1/sandboxes/execute?sandbox=… -d '{"language":"bash","code":"echo ok"}'

第二次返回 503 sandbox tunnel not available,说明 owner 记录过期了:检查持有方实例的 -node-url 是否非空、是否在收心跳。

仓库里的 hack/multi-instance-check.sh 把这一切端到端跑一遍 —— 两个实例、一个数据库、 一个真实 worker、一条人为制造的丢失投递 —— 并且跑在 CI 里。

Apache 2.0 — your clouds, your machines, your rules.