Files
qinglong/docs/adr/ADR-0045-cluster-http-readiness-admission-and-drain.md
T

101 lines
6.2 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-0045Cluster HTTP Readiness、Admission 与 Drain
- 状态:Proposed
- 日期:2026-07-19
- 关联:QL-RFC-0001 D-36/D-37/D-39/D-43、ADR-0039、ADR-0040、ADR-0042、ADR-0044
## 上下文
仅有 database readiness 和 composition rootKubernetes 仍无法区分“进程未启动”“正在审计 schema”“恢复未收敛”和“可以接收业务请求”。但如果先启动完整 API router,旧 schema、过度授权 role 或未完成 recovery 的副本可能在 ready 前产生副作用。
停止阶段也存在隐藏竞态:同步卸载 router 只能拒绝新请求,不能证明已经进入 handler 的请求结束。若随后停止 lifecycle、关闭 Repository/Pool,在途请求会与资源关闭竞争;若 handler 无视 Abort 却从 in-flight 集合移除,系统会伪报已 drain。
## 决策
### 1. Probe Listener 与业务 Admission 分离
只有显式 `enabled=true` 且 Profile 为 `cluster-control` 时才能绑定 HTTP listener。listener 可以早于数据库 readiness 启动,但此时只允许:
- `GET/HEAD /livez` 返回进程 listener 存活;
- `GET/HEAD /readyz` 返回 503 `not_ready`
- `/api/v3` 在读取 request body 前返回 503。
只有 schema/history/role readiness、startup recovery 和全部 lifecycle 成功后,才原子安装 `/api/v3` admission handler,并让 `/readyz` 返回 200。readiness failure 必须关闭 listener 与数据库,不能回退 SQLite。
### 2. HTTP Surface 必须硬性有界
首个 host 使用 Node 核心 HTTP,不引入 Express 或 legacy Controller。固定上限覆盖:
- URL、header bytes/count、每 socket 请求数;
- JSON request/response bytes
- in-flight admission 数;
- request timeout 与 shutdown drain timeout。
只接收 `/api/v3`、受限 method 和 JSON body;压缩 request、非法 length/content-type/JSON、超限 body/response 均返回稳定低敏错误。handler 异常不得把 SQL、连接 URI、Secret 或用户 payload 写入 response/diagnostic metadata。
### 3. Admission Disposer 是异步所有权边界
撤销 admission 的顺序固定为:
1. 同步清除全局 handler,使 `/readyz` 立即变 503,新 `/api/v3` 立即拒绝;
2. 向每个在途 handler 传播 AbortSignal
3. 等待 handler 的真实 Promise settle,而不是只等待超时 response 已发送;
4. drain 成功后才允许 activation 停止 stack 和 Pool。
客户端可以在 request timeout 或 draining 时提前收到稳定 503/504,但该 response 不等于 handler 已结束。忽略取消的 handler 会产生显式 `ClusterControlAdmissionDrainTimeoutError`activation 仍执行 best-effort 反向清理并报告失败,不能宣称 clean stop。
### 4. Runtime 配置先 Gate、后 Secret
公开 config loader 先读取 `QL_DEPLOYMENT_PROFILE``QL3_CLUSTER_CONTROL_ENABLED`。禁用时返回 disabled config,不读取 `QL3_POSTGRES_RUNTIME_URL`。启用时必须:
- Profile 精确为 `cluster-control`
- 使用 runtime role opener,不导出 migration role 默认值;
- PostgreSQL TLS 默认 `verify-full`
- 禁用 TLS 同时要求 `QL3_POSTGRES_TLS_MODE=disable``QL3_POSTGRES_ALLOW_INSECURE=true`
- HTTP/Pool 参数全部在硬上限内。
application/config 导入闭包只能经过 `@qinglong/cluster-postgres/runtime`,不得加载 executable migration、Drizzle schema、legacy `back/**` 或 UI。
## 当前孵化状态
`@qinglong/cluster-control/http` 已实现 probe、bounded JSON admission、低敏错误、容量拒绝、request Abort 和真实 handler drain`@qinglong/cluster-control/application` 已把 listener、readiness-first bootstrap、真实 PostgreSQL RunRepository、recovery/lifecycle/admission 和反向关闭串联;`@qinglong/cluster-control/config` 已实现 Profile-first、TLS-safe runtime 配置。真实 loopback tests 覆盖 starting not-ready、active admission、oversized/invalid request、handler error、容量耗尽、cooperative drain、non-cooperative drain timeout、readiness failure 和 listener/Pool 关闭顺序。
当前没有可公开的认证业务 router。`ClusterControlApplicationStack.handleAdmission` 仍是必须由后续 Identity/Policy/API vertical slice 注入的端口;在该实现、独立镜像和 PostgreSQL 16/18 远端证据完成前,cluster-control 继续 production unreachable。
## 影响
正面影响:
- Kubernetes 可以观察启动过程而不提前开放业务流量;
- readiness、admission 和数据库证据属于同一次 activation
- 在途请求不会因同步 router removal 被误判为已排空;
- cluster host 不继承 legacy Express/Controller/Sequelize 依赖;
- URL/body/concurrency/time budget 可审计。
代价与风险:
- handler 必须响应 AbortSignal,并使自身副作用保持幂等/可恢复;
- 不合作 handler 会让 shutdown 显式失败或超时;
- 当前核心 HTTP adapter 只支持 bounded JSON,不覆盖 Artifact 流式下载、SSE 或 WebSocket
- probe listener 早于 DB ready 存在端口占用,需要在启动失败时可靠关闭。
## 未选择的方案
1. **readiness 前启动完整 Express router**:业务中间件或 controller 可能提前产生副作用,拒绝。
2. **数据库 ready 后才监听任何端口**:无法区分启动中与进程死亡,也不能提供 liveness,拒绝。
3. **同步删除 router 后立即关 Pool**:忽略在途请求,拒绝。
4. **超时 response 即视为 handler 完成**:会伪造 drain 证据,拒绝。
5. **无界读取 JSON body 或 response**:放大内存和 DoS 风险,拒绝。
6. **默认明文 PostgreSQL**:破坏 cluster Secret/网络边界,拒绝。
## 验证
- disabled/错误 Profile 不监听端口、不打开 Pool、不读取数据库 Secret
- readiness 期间 live=200、ready=503、API=503 且 body 不被读取;
- recovery/lifecycle 完成后 ready 与 API admission 同步开放;
- invalid/oversized body、超限并发和 handler error 返回稳定低敏响应;
- stop 先 ready=503,再等待真实 handler,之后 stack→Pool→listener
- handler 忽略 Abort 时 drain timeout 可见且不得报告 clean stop
- application/config import closure 不含 migration DDL、Drizzle schema 或 legacy 根;
- Node 24、PostgreSQL 16/18、x64/arm64 CI 继续通过。