P2-1:preflight reuse/CPU/内存校验块小提取为 _preflight_config_checks(纯移动,深耦合处如实停)。 P2-2:build_vm_command 降级为私有 _build_vm_command; Docker goal 内联解析顶部加正典分叉说明 + 同步检查清单。 P2-3:gaps grep 文本断言改成真实 Worker._run_attempt 行为断言。P2-4:README/build.sh 口径对齐(章节顺序、 BUILDINFO 名字列表、Node.js 不装写实)。P2-5:stop_vm 删除失败记审计。P2-6:CPU/内存校验统一进 preflight。 测试:原 84 + 新增 33 = 117 项,mock,无真 VM。
250 lines
15 KiB
Markdown
250 lines
15 KiB
Markdown
# Cloud Hypervisor 执行器生产就绪清单
|
||
|
||
> 状态图例:✅ 代码已完成 · 🧪 mock 测试已覆盖(无真 VM) ·
|
||
> ⏳ 需 KVM 宿主实测 · 🏗️ 需镜像构建 · 📈 需压测 · 🔒 需安全审计
|
||
>
|
||
> 本清单是 PR 的一部分,不是事后补的口号。凡是标 ⏳/🏗️/📈/🔒
|
||
> 的项,在本轨内一律不声称已验证、已建成、已通过。
|
||
|
||
## 0. 交付点卡在哪一关(必读,先于逐项清单)
|
||
|
||
- **CI 门禁(未过,交付点 = PR OPEN)**:CI 必须通过才能合并。
|
||
此前全仓 CI 因 GitHub 账单问题 0 步骤失败;若账单未恢复、
|
||
CI 仍失败,本 PR 只到 OPEN,不合并,更不为变绿改 CI 配置
|
||
或绕过检查。
|
||
- **真 VM 门禁(未过,执行器默认关闭)**:生产启用(把真实任务
|
||
路由到 cloud-hypervisor、部署到生产主机)必须先有带 /dev/kvm
|
||
的宿主并完成真实 VM 启动、快照恢复、任务端到端验证。
|
||
当前事实:生产主机 124.222.165.170 本身就是 CVM
|
||
(systemd-detect-virt=kvm,DMI product=CVM),/dev/kvm 不存在、
|
||
无 vmx/svm 标志、无 kvm 模块、未装 cloud-hypervisor,无嵌套
|
||
虚拟化透传;本 VM 容器内同样无 /dev/kvm。
|
||
故真 VM、快照恢复、压测在本轨内一律不声称已验证;执行器
|
||
默认关闭(server 默认仍是 docker,worker 默认仍是 local,
|
||
CHV 只在任务创建时显式选 `executor=cloud-hypervisor` 且有
|
||
CHV worker 认领时才跑),不进入生产任务路由。
|
||
- **还差什么才能真正上生产**(按顺序):① 用户侧开通带 /dev/kvm
|
||
的裸金属或开启嵌套虚拟化的实例;② 在该宿主上构建并发布
|
||
chv-guest 镜像(见 §4);③ 在该宿主上跑通真实 VM 启动、
|
||
快照恢复、任务端到端、预热池与压测;④ 安全审计通过(§6);
|
||
⑤ CI 账单恢复且本 PR CI 变绿后,按当时 main 头复核 diff 再合并、
|
||
再部署。缺任何一项,都不把 CHV 推上生产。
|
||
|
||
## 1. 单任务单 VM 隔离
|
||
|
||
- ✅🧪 一个任务一个 VM:dispatch/run/goal 均走 wake → 跑 → finally
|
||
shutdown,同一任务重复 wake 先关旧 VM;VM 名、api-socket、
|
||
每任务磁盘路径均按任务派生,互不相同(测试断言)。
|
||
- ✅🧪 每任务磁盘独立:copy 模式复制基础镜像为每任务磁盘;
|
||
reuse 模式仅单租户实验,preflight 拦截(须显式实验二次确认并审计放行,feat-chv-fix 起代码真正拦截;此前“代码与文档双写”的说法是错的,代码当时并不拦截)。
|
||
- ✅🧪 租户无共享路径:执行器不挂载宿主根、宿主代码目录或含
|
||
密钥目录;客机代码在镜像内 `/app`,不在宿主共享挂载上。
|
||
- ✅🧪 密钥不进镜像/代码/prompt:SSH argv 只有 key 文件路径;
|
||
模型 key 由客机 cloud-init 预置的环境变量名注入;测试断言
|
||
命令中不含哨兵密钥值。
|
||
- ✅🧪 SSH host key 按 VM 钉死(优化轨,#216 的 no + /dev/null
|
||
已废除):每 VM 在 state_dir/<vm>/known_hosts 有独立文件,
|
||
首次启动经 ssh-keyscan 在解析出的 SSH 目标上捕获 key 写入该文件,
|
||
此后 SSH 一律 StrictHostKeyChecking=yes + 该文件校验;
|
||
非 root 客机用户(CHV_SSH_USER,显式空串直接拒绝)与端口
|
||
(CHV_SSH_PORT,1-65535)为显式配置。mock 只验证捕获→
|
||
写文件→钉死选项的管线,不验证首次密钥真实性。
|
||
- ✅🧪 Host key 预置方案(补缺口轨,替代 TOFU 为生产路径):
|
||
宿主在启动前为每 VM 生成一次性 ed25519 host key 对,
|
||
公钥立即写进该 VM 的 known_hosts 钉死,私钥只进 per-VM
|
||
cloud-init seed(随 VM 关闭/回收而删),客机镜像硬性
|
||
要求见 chv-guest/README.md §5(cloud-init NoCloud、
|
||
首启先应用 seed 再起 sshd、不得无条件重生成 host key)。
|
||
ssh-keyscan 首次捕获只保留为弱回落(CHV_HOST_KEY_MODE
|
||
=keyscan),生产部署必须用 provisioned 模式;TOFU 首次
|
||
真实性仍待预置 key 方案在真机上验证,安全审计时须单独立项确认。
|
||
- 🔒 需安全审计:VM 逃逸面(virtio 设备、tap 桥接、SSH 配置)、
|
||
客机 root 提权面、租户间东西向隔离的宿主网络配置,须由
|
||
安全审计逐项确认,代码自查不能代替审计。
|
||
|
||
## 2. 快速启动:预热快照池 + 镜像缓存
|
||
|
||
- ✅🧪 快照/恢复接口:snapshot_vm / restore_vm(restore 后显式
|
||
resume,见 §3),WarmPool 接口与状态机(cold → prewarming →
|
||
warm → assigned → running → recycled/destroyed)。
|
||
- ✅🧪 冷启动回落:池空时 acquire 返回 cold,调用方回落 wake_vm
|
||
冷启动,绝不降级 Docker/Local。
|
||
- ✅🧪 预热条目一次性:acquire 消耗、release 一律销毁,绝不跨
|
||
任务复用脏 VM。
|
||
- ✅🧪 预热池 registry 与孤儿清理(优化轨):池状态落
|
||
state_dir/warm-pool.json(原子写),worker 重启后载入的
|
||
条目一律当孤儿、只能 reconcile_orphans() 清理,绝不重新
|
||
发给任务;registry 损坏时 quarantine 成 .corrupt 并从空池
|
||
开始;registry 之外的残留 socket 仍由 cleanup_stale_vms()
|
||
扫描处理。
|
||
- ⏳ 需 KVM 宿主实测:预热池的真实冷启动耗时、resume 耗时、
|
||
池大小与命中率、快照来源一致性,均需真机数据,mock 数字
|
||
不能当性能结论。
|
||
- 📈 需压测:多 VM 并行预热/分配/销毁的宿主 CPU/内存/磁盘
|
||
压力、快照存储增长,均未测。
|
||
|
||
## 3. 完整生命周期
|
||
|
||
- ✅🧪 create / boot:API 模式 wake_vm(vmm.ping → vm.create →
|
||
vm.boot → SSH 就绪)。
|
||
- ✅🧪 pause-resume(sleep-wake):pause_vm / resume_vm(ch-remote)。
|
||
- ✅🧪 snapshot-restore:restore_vm(restore + 显式 resume +
|
||
SSH 就绪),恢复后默认 paused 的坑已在代码与测试里钉住。
|
||
- ✅🧪 hibernate:CHV 无原生 hibernate 端点,本轨用 pause +
|
||
snapshot + shutdown 等价实现,文档与代码双写“等价实现”,
|
||
不声称接了原生端点;唤醒走 restore_vm。
|
||
- ✅🧪 shutdown-destroy / recycle:shutdown_vm(ch-remote
|
||
shutdown + 杀进程 + 删 socket)、recycle_vm(保留
|
||
task_checkpoints/task_attempts,丢失 guest /tmp 与未拷出
|
||
的 worktree 临时物,绝不声称恢复临时文件)。
|
||
- ✅🧪 execute 只读已运行 VM(优化轨):execute() 先查 VM
|
||
是否在运行(登记活进程或 api-socket 存在),未运行报
|
||
ChvFailure(kind=vm_not_running),不为一次 status 起 VM,
|
||
也不向不存在的端口发 SSH 再拿连接失败冒充任务状态。
|
||
- ✅🧪 长任务中途死亡判 interrupted(优化轨):客内命令抛
|
||
传输异常、或非 goal 结果已 failed 且登记 VMM 进程已死时,
|
||
_run_with_vm 改判 interrupted(断点可续跑)而非笼统
|
||
failed;VM 还活着时的异常/失败原样处理。中途心跳未做:
|
||
host.py 单次 SSH 长回合没有客内进度信号可采,另起一路
|
||
SSH 轮询只能证明 VM 活着、不能证明任务在进展,评估后
|
||
认为风险大于收益,宁可只做死亡分类。
|
||
- ⏳ 需 KVM 宿主实测:上述全部生命周期在真机上的状态转换、
|
||
快照恢复后 guest 状态一致性、休眠后文件/worktree 保留,
|
||
均未在真机验证。
|
||
|
||
## 4. 完整开发环境(客机镜像)
|
||
|
||
- ✅ 客机镜像内容清单:`chv-guest/README.md` 按
|
||
`cloud/docker/Dockerfile` 能力对齐(Python 3.12、git、
|
||
pandoc/poppler/latex、python-docx/openpyxl/pptx/PyMuPDF/
|
||
Pillow、codex 0.164.0-alpha.9 成套、Playwright + Chromium、
|
||
openssh-server、cloud-init)。
|
||
- ✅ 构建脚本实做(补缺口轨):`chv-guest/build.sh`(参数校验、
|
||
KVM 宿主检查、下载 Ubuntu 云镜像、qemu-img 转 raw、
|
||
virt-customize 装清单、拷入 /app 代码、virt-get-kernel、
|
||
BUILDINFO.json 生成与 tag 校验;--dry-run 只打印命令)。
|
||
- 🏗️ 需镜像构建:镜像本身未构建、未发布;BUILDINFO 版本核对、
|
||
浏览器版本钉死、中文 LaTeX 单独一版,均待构建时做。
|
||
- ⏳ 需 KVM 宿主实测:镜像在真 VM 里启动、codex/Playwright/
|
||
git/文档工具链逐项跑通,均未验证。
|
||
|
||
## 5. 资源与成本
|
||
|
||
- ✅🧪 每 VM 配额:CPU/内存进 VmConfig(CHV_CPU/CHV_MEM),
|
||
磁盘配额门(CHV_DISK_GB,超限拒绝),宿主容量门
|
||
(CHV_MAX_VMS,超限明确拒绝排队,不降级)。
|
||
- ✅ 超时与最大轮次:SSH 单次超时(CHV_TASK_TIMEOUT)+
|
||
worker task_timeout 外层护栏;goal 最大轮次 100(与 Local
|
||
同款),执行器内部不重试(重试是 worker max_retries 语义)。
|
||
- ✅ 成本复用:不另起一套计费;worker 现有 quota/成本语义
|
||
(per-user USD 配额、quota 看门狗、成本写库)仍是唯一口径,
|
||
执行器只把 host.py 用量原样返回。
|
||
- ✅ 空闲休眠省成本:hibernate_vm(pause + snapshot +
|
||
shutdown)+ WarmPool 释放即销毁,空闲 VM 不占 vCPU/内存
|
||
(快照只占磁盘,见 §2 压测项)。
|
||
- ✅ autoscaler:按 executor 过滤队列深度(已有)+ 本轨透传
|
||
`--executor` 给拉起的 worker(否则 CHV 池永远饿死)。
|
||
- 📈 需压测:配额下的真实资源占用、超限拒绝的排队行为、
|
||
成本与配额在长任务下的实际表现,均未测。
|
||
|
||
## 6. 网络与治理
|
||
|
||
- ✅🧪 受控 egress:默认 fail-closed(CHV_EGRESS_PROXY 未设且
|
||
未显式允许直连时 preflight 直接失败);客机命令加 env
|
||
代理前缀,出口只走白名单代理(Tinyproxy 思路复用)。
|
||
- ⏳🔒 需宿主网络配置 + 安全审计:tap 桥接是否真的只路由到
|
||
白名单代理、VM 能否绕过代理直连外网,是宿主网络配置问题,
|
||
执行器代码不能自证,必须在 KVM 宿主上实测并审计。
|
||
- ✅ 审批门:任务 approval 经 `_approval_cli_args` 进客内 host.py
|
||
顶层参数,VM 内 agent 不能自改;delete/deploy/spend 类动作
|
||
仍走 server 的 approval/merge_gate,不由 VM 内 agent 自批
|
||
(本轨不改 approval/merge_gate 语义,只复用)。
|
||
- ✅ 工具与系统调用范围收敛:VM 边界本身就是系统调用边界
|
||
(客机独立内核);plan 任务走 read-only 沙箱参数;最大步数
|
||
(goal 100 轮)、时间(task_timeout)、重试(worker 1 次)
|
||
超限即停并审计(vm_failed 带 kind)。
|
||
|
||
## 7. 可观测与审计
|
||
|
||
- ✅🧪 VM 事件审计:create/boot/ready/pause/resume/snapshot/
|
||
restore/shutdown/destroy/failed/recycled/warm_pool_acquire/
|
||
release 均经 audit_callback 发出;worker 接线后转 AuthStore
|
||
审计(_audit 本身 best-effort)。
|
||
- ✅🧪 失败分类:ChvFailure 带 kind(kvm_missing/binary_missing/
|
||
kernel_missing/image_missing/ssh_key_missing/cpu_flag_missing/
|
||
egress_not_configured/api_timeout/guest_not_ready/vm_create_failed/
|
||
vm_boot_failed/host_capacity_exceeded/disk_quota_exceeded),
|
||
不笼统 failed。
|
||
- ✅ 断点对齐(补缺口轨已接线):VM 快照/休眠与现有
|
||
task_checkpoints/task_attempts 语义对齐(recycle 保留两者,
|
||
丢失临时文件);worker 通用分支对任何提供
|
||
goal_resume_breakpoint_task 的执行器调用它(Local 仍走
|
||
原路径,网关任务仍被 loud fail 守卫拦住);WarmPool
|
||
reconcile_orphans 已由 worker 启动与关闭时显式调用,
|
||
失败不炸主流程但进审计;VM 中途死亡改判 interrupted
|
||
后走同一断点路径,不再落 failed 死信(见 §3 与新增测试)。
|
||
- ⏳ 需 KVM 宿主实测:审计事件在真机任务 timeline 里的完整
|
||
呈现、失败分类与真实故障的对应关系,均未验证。
|
||
|
||
## 8. 多 VM 并发与容量
|
||
|
||
- ✅🧪 worker 注册:`_executor_name()` 返回 cloud-hypervisor,
|
||
worker register/heartbeat 已上报 executor + capacity(现有
|
||
机制复用,无需新表)。
|
||
- ✅🧪 多 VM 并行:`_vms` 登记 + 锁保护,wake 按任务名登记,
|
||
不同任务互不覆盖;容量门在 wake 前检查。
|
||
- ✅🧪 残留 VM 清理:cleanup_stale_vms 处理已死/已关/socket
|
||
残留三种情况,只清 chv- 前缀 socket。
|
||
- ✅🧪 容量不足明确拒绝:kind=host_capacity_exceeded,调用方
|
||
排队/拒绝,绝不降级 Docker/Local(测试断言 launcher 未调用)。
|
||
- 📈 需压测:多 VM 并行下的宿主容量、VM 启动并发上限、残留
|
||
清理在高压下的表现,均未测。
|
||
|
||
## 9. 默认关闭与生产启用条件(重申)
|
||
|
||
- ✅ 默认关闭:server 默认执行器仍是 docker,worker 默认 local;
|
||
CHV 只在显式选 executor 且有 CHV worker 时才跑;无 CHV_*
|
||
配置时 preflight 直接失败。
|
||
- ⏳ 生产启用前置(全部未完成):带 /dev/kvm 的宿主开通 →
|
||
chv-guest 镜像构建发布 → 真实 VM 启动/快照恢复/任务端到端
|
||
验证 → 压测 → 安全审计 → CI 变绿后按当时 main 头复核 diff
|
||
再合并部署。缺一不可。
|
||
|
||
## 10. 审核修复轨 feat-chv-fix(堆叠于 #230 之上)
|
||
|
||
- ✅🧪 P0 已修:seed 磁盘(CIDATA ISO,第二块只读盘)挂进
|
||
VmConfig;restore 不回落 keyscan(provisioned 下复用已钉
|
||
死 known_hosts、缺失 fail-fast,快照内 host key 不重预置);
|
||
reuse 由 preflight 拦截(显式实验二次确认 + 审计放行);
|
||
SSH 目标选客机 IP 方案(显式 host > net 静态 ip > 显式
|
||
转发宣称下的回环,三者皆无 fail-fast;vsock 与纯 NAT 方案
|
||
未选,理由见 config.py)。
|
||
- ✅🧪 P1 已修或有结论:网关 extra_env 经 SSH env 前缀注入,
|
||
worker 守卫收窄到 Local/CHV;取消/清理/看门狗接 CHV 通用
|
||
路径;WarmPool acquire 内 resume(但 worker dispatch 前查
|
||
池仍未接线,预热收益仍不是已交付特性);容量门改持久化
|
||
计数(socket 联合计数);磁盘配额改总量+余量、已存在磁盘
|
||
校验重拷;API 客户端按 Content-Length 验完整性;快照
|
||
file:// 目标验存在且非空再 shutdown;execute 死端口归类
|
||
vm_not_running。
|
||
- ⚠️ 仍未接线/未验证(不得声称已交付):worker dispatch 前查
|
||
WarmPool;CHV + 网关真实任务;真机 wake/boot/SSH(含 seed
|
||
注入与 NAT/客机 IP 实测);快照恢复端到端;压测;安全审计;
|
||
CI 账单恢复。另:P0-2 审计原说法“restore 须把 seed 接入
|
||
VmConfig”已纠正——restore 不走 vm.create,seed 无从挂盘,
|
||
正确不变量是复用钉死 known_hosts。
|
||
- ✅🧪 P2 已做(范围已恢复为全做,上一条收窄令作废):
|
||
test_chv_gaps.py 的 grep 文本断言改成真实 Worker._run_attempt
|
||
行为断言;stop_vm 删除失败记 vm_key_cleanup_failed 审计
|
||
(同名复用旧 key 的彻底根治“清理失败即拒起”列为遗留,不
|
||
假装已根治);配置校验时机统一到 preflight(CPU/内存非法
|
||
进 preflight,kind=config_invalid);build.sh 与 README 口径
|
||
对齐(章节顺序 §5 host key → §6 对接、BUILDINFO contents
|
||
纠正为名字列表无逐项版本、Node.js 不装写实);第二启动路径
|
||
清理(build_vm_command 降级为私有 _build_vm_command,手动
|
||
等价写法保留;Docker goal 内联解析顶部加指向正典
|
||
parse_goal_result 的分叉说明 + 同步检查清单);长函数只做
|
||
小提取(preflight 的 reuse/CPU/内存校验块抽成
|
||
_preflight_config_checks,语义不变;config.py 字段解析与
|
||
其余长函数深耦合,本轨如实停、不硬拆)。
|