worker fleet · docker runtime reset
把任务运行时从主机里拿出来。
新方向不是继续给 worker2 / worker3 手装依赖。worker 仍然从 DB 抢任务, 但真正跑模板的那一步,改成每个 job 起一个干净的 Docker runtime container。
decision
这次改的是执行边界
1一个 job 一个容器,跑完就销毁。
basic先用一个基础 runtime 覆盖主流 MB / Mai。
digestworker 拉固定镜像摘要,不靠机器手装。
rerun旧 host runtime PASS 要用容器重跑。
architecture
任务还是从 DB 来,命令进容器跑
判断点:worker 不是被 Docker 取代。被替换的是“跑模板命令”的那一层。controller 还在 host 上抓任务,但模板实际在容器里跑。
before / after
原来的 subprocess,只剩启动容器这一个职责
之前
- worker 抢到 job。
- host 上直接起 `codex` / `python` / `ffmpeg`。
- 缺包就 ssh 上去补。
- worker2 / worker3 靠探针比对。
现在目标
- worker 抢到 job。
- host subprocess 只负责 `docker run`。
- 模板依赖都在 image 里。
- 两台机器拉同一个 digest。
worker claim job
-> docker run --rm \
--name tobatsu-job-<job_id> \
--env-file <runtime-env> \
-v <workspace>:/workspace \
-v /mnt/tobatsu-cfs:/mnt/tobatsu-cfs \
-v /mnt/tobatsu-data:/mnt/tobatsu-data \
-v <codex-home>:/home/tobatsu/.codex \
-v <claude-home>:/home/tobatsu/.claude \
tobatsu-runtime-basic@sha256:<digest> \
tobatsu-run-job <task_id>
hard gates
cc review 点名的 4 个硬要求
| 要求 | 为什么重要 | 验收证据 |
|---|---|---|
| Codex appserver socket | 容器里的 `127.0.0.1` 不是 host。Codex Ask 不能继续走 host loopback。 | 容器内 `TOBATSU_CODEX_APPSERVER_URL=unix://...`,socket bind-mounted,Ask probe 通过。 |
| DB 连接预算不变 | Docker 不会让 Neon 连接变少。每个 job container 还是会开 DB 连接。 | 每执行仍按 `POOL_SIZE=1 / MAX_OVERFLOW=1` 预算,pooler gate 保留。 |
| 挂载不能少 | 只挂 workspace 和 NAS 不够。热 scratch 不能写到 container layer。 | workspace、NAS、data-disk scratch、`CODEX_HOME`、claude home 都在 mount readback。 |
| 旧 PASS 要重跑 | 旧结果证明了路由,但不是 Docker runtime 下的最终完成。 | 测试表标 `RE-RUN-REQUIRED`,容器路径重跑后再盖最终 PASS。 |
sessions
Continue、Ask、Fork 不放进容器层里
per-job container 是一次性执行壳。真正要续上的状态,必须放在容器外: DB、NAS、workspace、`CODEX_HOME`、Claude home、host appserver socket。
如果 session 状态留在 container writable layer,Continue 和 Fork 一销毁就断。正确做法是容器每次重新连外部状态。
runtime contracts
三类会话功能的实现口径
| 功能 | Docker 下怎么做 | 必须挂载 / 注入 |
|---|---|---|
| Continue | 不是继续同一个进程。controller 找到 source task/run,新容器 mount source/current workspace,再按 source ids 续跑。 | DB source ids、source workspace、current workspace、callback env、runtime env。 |
| Codex Ask Session | host codex appserver 继续长驻。容器通过 bind-mounted UNIX socket 请求 fork / Ask。 | `CODEX_HOME`、appserver socket、`TOBATSU_CODEX_APPSERVER_URL=unix://...`。 |
| Codex Fork / Refork | fork 由 host appserver 处理,session metadata 写 DB / evidence。容器只是请求方。 | `CODEX_HOME`、appserver socket、workspace evidence path。 |
| Claude Resume | Claude 没 appserver。容器里调用 Claude CLI resume,状态来自 mounted Claude home。 | Claude home/session/auth、workspace、runtime env。 |
image hosting
镜像放哪里,怎么不把盘打爆
镜像托管
- 默认选私有 GHCR / private registry。
- Docker Hub 可以用,但 runtime 细节会被发现。
- 不把密钥 bake 进 image。
- worker 用只读 pull token。
- 生产拉 digest,不拉漂移 tag。
磁盘规则
- 不在 worker2 / worker3 上 build 大镜像。
- 用 build host 或 CI 构建。
- Docker `data-root` 必须在数据盘。
- 不能让 `/var/lib/docker` 吃系统盘。
- container writable layer 终态后清理。
{
"data-root": "/mnt/tobatsu-data/docker"
}
这条是硬闸。Docker image 层、pull 缓存、容器可写层都很大;没把 Docker root 移到数据盘前,不应该在 worker 上拉 basic runtime。
rollout
重新开始时,按这个顺序走
| 顺序 | 动作 | 不做什么 |
|---|---|---|
| 01 | 把 Docker reset 写进 goal / implementation / engineering / test-plan。 | 不继续 host 手装路线。 |
| 02 | 准备 build host / 私有 registry / image digest 规则。 | 不在 worker 上 build。 |
| 03 | worker2 / worker3 先验 Docker data-root 在数据盘。 | 不拉大镜像到系统盘。 |
| 04 | 实现 controller 的 container runner。 | 不让模板命令直接跑 host。 |
| 05 | 解决 codex appserver UNIX socket mount。 | 不用 host network 绕过。 |
| 06 | 跑 cheap canary,确认容器路径 claim / run / callback。 | 不直接恢复 MB/Mai fire。 |
| 07 | 用容器路径重跑 MB creative / delivery 等主线模板。 | 不把旧 host PASS 当最终 PASS。 |
what survives
前面的工作不是全丢,但要重新定位
保留为控制面证据
- `required_host_id` 能把任务钉到指定 host。
- `fleet-test` 能隔离 fleet 验收任务。
- 临时 clone template 的 E2E 模型成立。
- MB/Mai 必须走真实上游入口。
- callback / readback / rollback 闸序保留。
不再当完成证据
- host apt / pip / font 手装路线。
- worker2 / worker3 host parity probe。
- 旧 host runtime 下跑出的 template PASS。
- 把环境问题留给 E2E 撞出来。
一句话:路由证据保留,运行时证据重跑。最终报告必须清楚区分这两类,不能把旧 PASS 包装成 Docker runtime 完成。