外观
锁步契约
控制面与 fleet agent 之间有两类硬耦合,改动时必须按序发布:协议 schema与连通性契约。
一、协议 schema:extra="forbid" 的代价
packages/dn42_schemas 的所有模型继承 StrictModel,配置是 extra="forbid" 加 frozen=True。
好处是任何隐式协议漂移都在解析期直接报错,而不是被静默忽略后在别处炸开。代价是接收方必须先认识新字段,否则整份 payload 被拒。
因此发布顺序取决于数据流方向:
| 变更 | 数据流方向 | 先升谁 | 理由 |
|---|---|---|---|
| DesiredState 加字段 | 控制面 → agent | 先升 agent | agent 解析 DesiredState,不认识新字段就整份拒收、无法 reconcile |
| DesiredState 删字段 | 控制面 → agent | 先升控制面 | 控制面停止下发之后,agent 才能安全地移除该字段的处理 |
| 上报 payload 加字段 | agent → 控制面 | 先升控制面 | 控制面解析上报,不认识新字段就整份拒收、对账假失败 |
| 上报 payload 删字段 | agent → 控制面 | 先升 agent | agent 停止发送之后,控制面才能移除 |
一句话记法:加字段先升接收方,删字段先升发送方。
同时改两个方向的变更需要拆成两次发布。
校验规则与哈希算法也在此列
修改 dn42_common 的校验器或 serialization 的哈希算法,同样影响两侧对同一输入的接受集与摘要口径。校验收紧可能拒掉存量配置,发布前要先全量核查。
例外:向后兼容的可选字段
上报侧的新增可选字段如果控制面用 Optional 且默认 None 接收,旧 agent 不发时控制面会 no-op——这类改动没有顺序要求。前缀级 flap 的路径证据字段就是这么加的。
判断标准是:接收方的模型能不能在字段缺席时正常构造。
二、连通性契约
改 agent 与控制面之间的连接方式(API 前缀、鉴权形态、WebSocket 路径),是另一类锁步——它不只是顺序问题,还会让自更新链路本身失效。
自更新链路的适用边界
自更新的健康门要等新版 agent 重写 agent-healthy.json,而该文件只在 WS 心跳成功时才写。
于是:
- 先升 agent:新 agent 说新协议、旧控制面不认,心跳必然失败 → 健康门超时 → 每个节点自动回滚。
- 先升控制面:旧 agent 连 wheel 都下载不到——分发端点也在新协议下。
结论:涉及连通性契约的变更,必须走 SSH 手工 wheel 路径(见 Agent 滚动升级)。
两种执行次序
两种都可行,都存在一段控制面失明窗口,数据面均不受影响——节点按最后一次已应用的配置继续转发。
agent 优先:
- 构建 wheel 并校验产物确实携带新协议。
- 逐节点手工滚动。此刻起各节点陆续失联(agent 说新的、控制面还认旧的,报 404 并退避重试)。
- 部署新版控制面(含 nginx 配置)。三个后端服务一起换。 上线后全机群自行回连,无须再登节点。
- 全机群绿了之后,把 wheel 上传到控制面并设 target 版本,让自更新机制回到可用状态——否则下一次自更新会从一个不存在的旧版本起跳。
控制面优先:失明窗口从控制面上线起算,每台 agent 升完即恢复。好处是控制面这个「全有全无」的步骤可以在不碰任何节点的前提下先验证,失败只需回滚镜像。
前端与其它客户端
后端不留兼容期时,客户端仓库须同一批次发布。控制台与门户经 CORS 直连控制面;授权页与 auth-server 同源,改协议时两侧要一起换产物。
三、发布前检查清单
- [ ] 本次变更是否动了
dn42_schemas的模型字段?动了就按方向定顺序。 - [ ] 是否动了
dn42_common的校验器或哈希?动了就先全量核查存量配置会不会被新校验拒掉。 - [ ] 是否动了 API 前缀、鉴权形态或 WS 路径?动了就不能用自更新。
- [ ] 是否有数据库迁移?加列类在代码前跑,删列类在代码后跑。
- [ ] 三个后端服务是否一起发布?
- [ ] 前端产物是否需要同批次发布?
golden 渲染回归(tests/unit/test_golden_rendered_hkg1.py)会挡住模板输出的意外改变——schema 默认值、模板、runtime 输出有意改变时才刷新 golden,见 贡献指南。