Files
qinglong/docs/adr/ADR-0238-production-worker-certificate-renewal-single-cadence.md
T

161 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-0238Production Worker 证书续期单 Cadence 装配
- 状态:Accepted
- 日期:2026-08-01
- 关联 RFCQL-RFC-0001 D-23、D-58、D-60、D-175、D-207、D-218、D-222
- 关联 ADRADR-0060、ADR-0061、ADR-0121、ADR-0234、ADR-0235、ADR-0236
## 背景
ADR-0061 已实现 Worker 本地 CSR、证书校验、原子 generation 安装和跨重启退避,
ADR-0234 又让 production credential provider 在每个 HTTPS request 前读取当前 active
identity。然而 production Worker 组合根没有调用续期协调器:部署即使提供外部 CA
adapter,也只能另建 timer、watcher 或旁路进程触发续期,无法继承既有 Session、drain、
错误隔离和 Edge/Node 资源预算。
把外部 CA 直接放入 `cluster-control` 会扩大常驻入口的签发权限;为续期再建 package、
sidecar 或 timer 又会把空闲成本推给低性能路由设备。需要在不改变 CA authority 边界的
前提下,把已有一次性续期操作接入唯一 production cadence,并证明正在运行的 Worker
可以在不替换 Session 的情况下完成信任收缩。
## 决策
### 1. 续期是可选 Profile capability,不是内建 CA
`ProductionWorkerHeadlessApplicationEnabledOptions` 接受可选的
`certificateRenewal.run()` capability。它只返回 ADR-0061 的低敏续期结果,不取得 CA
私钥、部署 Secret、listener 或数据库。
`runProductionWorkerProcess` 提供可选 `createCertificateRenewal(config, credentials)`
factory,让具体 Worker Profile 把 cert-manager、Vault、SPIFFE 或离线签发 adapter 与同一
证书目录组合。默认 CLI 未配置 adapter 时不构造 coordinator、不加载 enrollment PKI、
也不增加任何后台活动。
### 2. 复用唯一 cadence,固定先证书后网络业务
启动顺序固定为:
1. 取得 journal owner 并完成有界 startup reconciliation
2. 执行一次证书维护;
3. 只有 identity 可用时才注册 Worker Session
4. 启动既有唯一 `unref` cadence。
每个 cadence 的顺序固定为 certificate maintenance → Session heartbeat → execution
supervision/Pull。并发 tick 继续由 application 和 renewal coordinator 各自 coalesce
不新增 timer、watcher、Agent、socket、Pool、进程或 sidecar。
`not_due``renewed``backing_off` 继续业务;`retry_scheduled` 在旧证书仍有效时继续
业务并发出稳定的 `certificate_renewal_failed` diagnostic。issuer 原始错误、CSR、PEM、
路径和 token 不进入 diagnostic 或 process event。
### 3. 身份不可用必须本地失败关闭
续期抛错或返回 `unavailable` 时,production Session lifecycle 同时:
- 把 Session coordinator 置为 blocked,使 `current()` 不再向 execution plane 暴露可用
Session,阻止新的 Pull
- 把 capacity oracle 置零,阻止后续 heartbeat 宣告空闲容量;
- 发出 `certificate_unavailable` 低敏 diagnostic
- 仍运行 execution supervision,使已有本地执行可以按 durable Lease 到期停止,而不是
因证书失败永久逃逸监督。
本地安装新证书本身不能解除 fence。只有同一 Session 的后续真实认证 heartbeat 成功,
Session coordinator 才清除 blockedcapacity oracle 才重新 active。这样不会把“CA 已
签发”冒充“网络路径、服务端信任和业务 credential 均已恢复”。
### 4. 换证复用一个 Agent,但不能复用旧 TLS socket
credential provider 继续每请求读取 active generation。共享 HTTPS Agent 的 pool key 同时
绑定 certificate、private key 与 trust anchorsactive generation 改变后,下一请求使用
新的 pool key 和 TLS socket,不创建第二个 Agent。Ingress trust reload 继续轮换 TLS
generation、关闭 idle connection,并拒绝旧 generation socket。
### 5. CA overlap 与 trust contraction 由部署编排
常驻 QingLong 控制面仍不持有 CA 私钥,也不开放匿名 CSR endpoint。标准轮换顺序为:
1. ingress 先信任 CA-A + CA-B
2. Worker 通过外部 issuer 取得 CA-B 证书并原子切换 active generation
3. 下一次请求使用新 pool key;
4. ingress 显式 reload,只保留 CA-B
5. 证明 CA-A 旧证书被拒、CA-B 新证书被接受;
6. 同一 Worker Session/generation 继续 heartbeat、Pull、Lease 与 completion。
Edge 可省略 adapter,或用低频外部 maintenance/离线 ceremony 注入 capabilityCluster
节点可由 cert-manager、Vault、SPIFFE 或 Secret rollout controller 注入。两者共享相同
一次性协调语义和失败关闭策略,不为资源档位复制 runtime。
## 不采用方案
### 在 Worker 内再建续期 timer 或文件 watcher
拒绝。它会与 production cadence 竞争、增加 Edge 空闲资源,并在 Kubernetes 多文件
Secret 非原子更新时间窗读取混合材料。
### 由 cluster-control 在线签发 Worker 证书
拒绝。它把外部 transport 漏洞升级为设备根身份签发权,也混淆 transport possession 与
`ql3w`、Session、Lease 三层 authority。
### 续期成功后立即本地解除 Session fence
拒绝。证书可能尚未被 ingress 信任,网络或 credential 仍可能失败;恢复必须由真实
heartbeat 证明。
### 换证时重启 Worker 或创建第二个 Agent
拒绝。重启会扩大 Run/drain 恢复窗口,双 Agent 会增加 socket/RSS 并使旧证书连接的关闭
时点不清晰。credential-bound pool key 已能在单 Agent 内分离代际。
### 为 certificate lifecycle 新建 workspace package
拒绝。续期协调器、process composition、Session lifecycle 和 transport 已由同一 Worker
部署单元拥有;再拆包没有独立发布、authority 或 Profile 替换价值,只会恢复一文件一包。
## 影响
- workspace 保持 20 个 package
- 不新增生产依赖、migration、表、角色、端口、listener、Pool、连接、timer、watcher、
Agent、进程或 sidecar
- disabled Worker 与未注入 CA adapter 的 Worker 保持零续期常驻成本;
- Edge/Node 继续只用同一个 runtime,通过现有 cadence/并发/页预算区分资源档;
- `ProductionWorkerSessionLifecycle` 增加可选 fail-closed capability;只有配置 certificate
renewal 时才强制要求它,既有无续期 headless embedding 保持兼容;
- 外部 CA adapter、CA 私钥、Secret rollout 与 ingress reload authority 仍在部署层,不
进入 Worker 或 Cluster 常驻业务入口。
## 验证
1. GitNexus 刷新后包含 35,661 nodes、81,415 edges、285 flowsupstream impact 中
production headless composition 2 个直接调用者、production product root 0 个上游、
process root 2 个上游、Worker Session coordinator 4 个上游、renewal coordinator 7 个
上游,均为 LOW,未命中已知 execution flow。`detect-changes` 对 tracked compare 为 LOW、
0 flow;Git 本身不会把尚未跟踪的 QL3 文件归入 compare,因此另以 strict 编译、入口
测试、依赖审计和 live contract 补足;
2. Worker strict TypeScript closure check 通过;全量 132/132,覆盖启动前 identity
unavailable 清理、单 cadence 顺序、
unavailable fail-close、同 Session authenticated heartbeat 恢复和唯一 TLS Agent
3. `test:worker-postgres-live:ql3` 在 Linux Node `24.18.0` 与 PostgreSQL
`18.4 (Debian 18.4-1.pgdg13+1)` 上完成一次真实续期:issuer 调用 1 次,证书 SHA-256
改变,ingress TLS generation 变为 2CA-A 旧证书被拒,CA-B 新证书被接受;
4. trust contraction 后 Worker 保持同一 Session ID、generation 1,瞬时旧 socket 失败
只产生有界 `session_tick_failed` diagnostic;后续真实认证请求恢复,无 Session replacement
5. 最新一次同一合约随后完成 Offer→starting→running→67 次 Lease renew→31-byte Artifact→
completionRun/Attempt succeeded、Lease completed,并在运行中完成 `ql3w` credential
A→B、A revoke、最终 drain/offline 与三个数据库角色权限断言;`gates.passed=true`
6. 合约成功与失败路径均删除临时 PostgreSQL/Node 容器和私有证书目录,不保留 CA 私钥。
7. PostgreSQL 18.4 arm64 physical HA 独立复验 `remote_apply`、timeline 1→2、旧主先
fencing、未确认分区写在晋升后不存在、`pg_rewind` 只读同步重入和两个 fresh control
replicas,全部 gate `passed=true`;Worker/HA 临时容器与网络均已清理,既有
`ql3-cnpg-evidence-control-plane` 未修改。
## 仍未完成
- cert-manager、Vault PKI、SPIFFE/SPIRE 或离线 CA 的正式 adapter/部署模板;
- Kubernetes credential/identity generation 与单节点 PVC rollout 已由 ADR-0239 完成;仍缺
ingress reload controller、生产 RBAC、权限审计、CA overlap 分区与回滚;
- 证书到期、续期失败、blocked duration、TLS handshake 拒绝的 metric/alert
- 真实 Kubernetes 多节点 CSI PVC detach/reattach、node loss 与 production 360 秒长 drain
- TPM/PKCS#11/non-exportable private key provider
- 固定 x64/arm64 路由设备的时钟跳变、断电、文件系统和资源门禁。