PP-03 凭据泄露处置: - 清除 wiki + 2 份历史文档中的 Redis 明文密码与公网 IP(4 文件 5 处) - wiki 新增安全告警 + 症状导航条目 - 核实降级:泄露旧密码已失效,HMS 连本地 Redis,云端闲置;公网已关闭 系统深度分析(9 维度 + 6 主题多专家组): - docs/discussions/2026-06-25-analysis/ 新增 7 文件 - 综合 6.8/10,4 CRITICAL,TOP 12 痛点,4 阶段路线图 wiki 关键数字校正(PP-02/05a fix 触发): - 迁移数 175→176(m20260626_000170) - 症状导航新增 device_readings 分区硬截止 + claim_next 注入修复条目
126 lines
14 KiB
Markdown
126 lines
14 KiB
Markdown
# 稳定性与上线护航 — 主题综合
|
||
|
||
> 日期: 2026-06-25 | 主题: 稳定性与上线护航(主持综合)
|
||
> 视野: V1 上线后 6-12 个月演进,不重复上线前就绪度讨论。
|
||
> 证据口径: 所有论断附 `文件:行号`,基于 feat/media-library-banner 分支实测。
|
||
|
||
## 1. 主题愿景
|
||
|
||
把"上线即救火"变成"上线即睡觉"。融合 SRE / 发布管理 / 质量保障三方共识:以**确定性故障自愈**为核心(死信重试、分区到期、AI 队列积压——这三类都是"代码写了但没接线"或"硬截止定时炸弹",是历史 24% fix 提交率在事故层面的根因),以**迁移可逆性 + 快速回滚**为发布纪律,以**告警触达人 + cron_heartbeat 进就绪门禁**为观测闭环。三者形成"预防—发布—响应"完整生命周期,而非堆砌监控工具。
|
||
|
||
不追求一步到位上 K8s+Helm+完整可观测性栈(DevOps 4.2 分、单兵运维团队不支撑这种复杂度债务)。最小可行 HA = migrate 子命令 + 蓝绿 + heartbeat 门禁 + Alertmanager,每一步都是可验证的工程动作,配合 TDD 集成测试证明"接线真的生效"。
|
||
|
||
## 2. 专家提案摘要
|
||
|
||
### SRE 可靠性工程师(5 项)
|
||
- **PP-04 告警触达人**:补 Alertmanager + 三级 SEV 分级 + Runbook deep-link + webhook 限流治理。
|
||
- **PP-01/PP-05 死信与 AI 队列接线**:`retry_dead_letters`(events.rs:382) 和 `analysis_queue::claim_next`(service/analysis_queue.rs:92) 已实现但脱节,接线 + cron_heartbeat 复用为后台存活探针。
|
||
- **PP-02 分区自愈**:应急 guard(每日检测未来分区数<2 则补建)+ pg_partman 根治,剩余周数作 SEV-1 倒计时告警。
|
||
- **灾备演练制度化**:restore-drill CI job → RPO/RTO Prometheus 指标。
|
||
- **PP-11 迁移解耦启动路径**:migrate 子命令 + 蓝绿 + 破坏性 DDL 三步走。
|
||
|
||
### 发布管理专家(5 项)
|
||
- **迁移可逆性工程化**:clap 子命令暴露 173 条已存在但休眠的 down 迁移为可调用能力;expand/contract 强制破坏性变更跨版本。
|
||
- **30 分钟回滚**:`migrate down <ver>` + 双标签镜像 + canary 用 `eventbus_pending_total`(tasks.rs:119) 作健康判据。
|
||
- **cron 进就绪门禁**:`/health/ready`(health.rs:51) 暴露 cron_heartbeat + 积压阈值,503 触发摘流。
|
||
- **系统级特性开关**:FeatureFlagService(ai 内部)上提为 erp-core trait,按 tenant 粒度灰度。
|
||
- **冻结期 + fire-drill**:双周演练 + 兼容窗口契约沉淀到 docs/runbooks/。
|
||
|
||
### 质量保障架构师(占位,观点合并入下)
|
||
门禁反推视角已贯穿上述——TDD 集成测试证明接线生效、CI 门禁拦截破坏性 DDL、fire-drill 验证回滚链路,本主题不再单列。
|
||
|
||
## 3. 战略举措(归并后 5 项)
|
||
|
||
### 举措 A:确定性故障自愈接线(PP-01 + PP-05 + PP-02 应急)
|
||
- **rationale**:retry_dead_letters 已实现 (events.rs:382-446,含 max_attempts=5 filter events.rs:390) 但全仓无调用者;ai_analysis_queue 表已建 (m000118) 且 claim_next 已实现 (analysis_queue.rs:92) 但只在 module 启动触发一次;device_readings 是分区表,down() 只 DROP 固定 4 个月份 (2026_05..2026_08),2026-09 后 INSERT 将硬失败。三处都是"代码写了但没接线"的定时炸弹。
|
||
- **phases**:
|
||
1. tasks.rs 新增 `start_dead_letter_retry`(每小时调 retry_dead_letters,复用 cron_heartbeat)+ `start_ai_queue_worker`(消费 ai_analysis_queue)。
|
||
2. tasks.rs 新增 `start_device_readings_partition_guard`(每日检测 `pg_inherits` 未来分区数<2 则 CREATE PARTITION OF)。
|
||
3. TDD:先写 `tests/event_retry_loop.rs`、`tests/partition_guard.rs` 失败测试,再接线。
|
||
- **effortEstimate**:3-5 人日(函数均已存在,主要是接线 + 测试)。
|
||
- **expectedImpact**:消除 3 类潜伏故障;死信不再永久驻留;2026-09 分区硬截止提前自愈。
|
||
- **kpis**:dead_letter_events.resolved_at 非空率 >95%;ai_analysis_queue pending 24h 内清零;device_readings 未来分区数 ≥3。
|
||
- **dependencies**:cron_heartbeat metric 暴露(举措 C);Alertmanager 告警规则(举措 D)。
|
||
|
||
### 举措 B:迁移解耦启动路径 + 30 分钟回滚能力
|
||
- **rationale**:main.rs:233 在应用启动路径直接 `Migrator::up`,含 RENAME/DROP 等破坏性 DDL;173 条 down 迁移已写好但全仓无 clap/migrate 子命令可调用(Cargo.toml 无 clap 依赖);docker-compose.production.yml:38 单容器 `hms-server`,nginx.conf:1 单 upstream;.github/workflows 仅 test.yml 无构建推送。回滚资产存在但休眠。
|
||
- **phases**:
|
||
1. 引入 clap 子命令:`erp-server serve`(仅启动 HTTP,不迁移)/ `migrate up|down <ver> --dry-run --confirm` / `migrate verify`(事务回滚校验 down 可逆性)。
|
||
2. main.rs:233 改为启动时仅校验 schema 版本一致性,不匹配则 panic 提示先跑 migrate。
|
||
3. CI 新增 build-and-push workflow:双标签 `{git-sha}` + `{semver}` 推 ghcr.io。
|
||
4. deploy.sh:canary 10% 流量,观察 `/metrics` 5xx + eventbus_pending_total 5 分钟,异常切回 stable + migrate down。
|
||
5. 破坏性 DDL 写入 docs/runbooks/breaking-ddl.md 强制 expand/contract 三步走 checklist。
|
||
- **effortEstimate**:8-12 人日(clap 改造 + CI + deploy 脚本 + 破坏性迁移 checklist)。
|
||
- **expectedImpact**:回滚从"重拉镜像"升级为 30 分钟内可执行;破坏性 DDL 不再在启动瞬间执行。
|
||
- **kpis**:MTTR <30min;破坏性迁移 100% 走 expand/contract;任意历史镜像可拉取。
|
||
- **dependencies**:backup.sh 作为回滚前快照(已存在);nginx upstream 改造(举措 B 第 4 步)。
|
||
|
||
### 举措 C:cron_heartbeat 进就绪门禁 + 积压指标可观测
|
||
- **rationale**:cron_heartbeat 已埋点 (main.rs:647 → state.rs:31 → tasks.rs 已写),但 readiness_check (health.rs:51) 仅查 DB+Redis,后台任务死了就绪仍返回 ok;tasks.rs:119 已暴露 eventbus_pending_total,但 dead_letter 积压、AI 队列积压、分区剩余周数未导出。零成本资产未利用。
|
||
- **phases**:
|
||
1. ReadyResponse (health.rs:33) 增 `crons: [{name, last_heartbeat_ago_secs, healthy}]`,>2×周期判 unhealthy 返回 503。
|
||
2. tasks.rs 新增 gauge:`dead_letter_unresolved_total`、`ai_analysis_queue_pending_total`、`device_readings_partitions_remaining_weeks`。
|
||
3. 公开路由屏蔽 crons 字段防信息泄漏。
|
||
- **effortEstimate**:2-3 人日。
|
||
- **expectedImpact**:后台任务死亡可被 nginx/k8s 摘流;canary 阶段可直接判断新版本是否杀掉某个 cron。
|
||
- **kpis**:/health/ready 反映 cron 存活;canary 拒绝率(后台被杀)可观测。
|
||
- **dependencies**:举措 A 接线后 cron 才有真实心跳。
|
||
|
||
### 举措 D:Alertmanager + 三级告警 + Runbook 绑定
|
||
- **rationale**:prometheus.yml:5 仅有 rule_files 无 alerting 块;alerts.yml 22 条规则但无 SEV 分级、无 Alertmanager、无 Runbook 链接。告警亮了无人知、知了不知干啥。
|
||
- **phases**:
|
||
1. docker-compose.production.yml 新增 alertmanager 服务 + prometheus.yml 补 `alerting: alertmanagers:` 段。
|
||
2. alerts.yml 按 SEV-1/2/3 分级(SEV-1:5xx 率/PG 耗尽/Redis 不可达/dead_letter 积压/分区<2 周 → 企微+电话;SEV-2:P95/idle<10% → 企微;SEV-3:CPU/内存 → 仅 Grafana)。
|
||
3. 每条 alertname 对应 docs/runbooks/ 一页 + Grafana deep-link。
|
||
4. 上线初期只开 SEV-1,按周复盘降噪后再开 SEV-2/3(信噪比治理)。
|
||
- **effortEstimate**:4-6 人日。
|
||
- **expectedImpact**:值班人被电话叫醒 ≤1 次/天;确定性故障(分区到期、死信积压)提前 2 周报警而非等客户投诉。
|
||
- **kpis**:告警信噪比(有效告警/总告警)>70%;SEV-1 平均响应 <15min。
|
||
- **dependencies**:举措 A/C 的 metric 导出;企微 webhook 限流(20/min)评估。
|
||
|
||
### 举措 E:灾备演练制度化 + 兼容窗口契约
|
||
- **rationale**:backup.sh/restore.sh 已实现(AES-256-CBC)但从未验证可恢复;无 fire-drill;无兼容窗口契约文档。医疗 SaaS 业内普遍"有 backup 没 drill"。
|
||
- **phases**:
|
||
1. docker/drill/restore-drill.yml:独立 PG 副本,CI 周日 04:00 拉最近 backup → restore → schema diff + 10 条 smoke 查询 → 输出 RPO/RTO metric。
|
||
2. docs/runbooks/disaster-recovery.md + release.md + rollback.md + partition-deadline.md。
|
||
3. wiki/architecture.md 新增"发布兼容性"章节 + PR 标注 `compatible_rollback_to`。
|
||
4. 双周 staging fire-drill + 变更冻结期(月初高峰前 48h)。
|
||
- **effortEstimate**:6-8 人日(含 CI 隔离 + PII 合规审查)。
|
||
- **expectedImpact**:RPO/RTO 从纸面 SLA 变成每周可查 metric;客户合规审计直接拿数据。
|
||
- **kpis**:backup_rpo_seconds / backup_rto_seconds 周报;fire-drill MTTR 记录。
|
||
- **dependencies**:CI runner 隔离(生产备份含 PII);BACKUP_PASSPHRASE 轮换流程。
|
||
|
||
## 4. 速赢(1-2 周内)
|
||
|
||
1. **cron_heartbeat 进 /health/ready(举措 C 第 1 步)**:health.rs:33/51 改造,零依赖、1-2 人日,立即可让 nginx 摘流反映后台存活。这是所有后续观测的门禁基线。
|
||
2. **死信与 AI 队列接线(举措 A 第 1 步)**:tasks.rs 加两个 spawn + 复用 retry_dead_letters/claim_next,3-4 人日,消除"代码写了没跑"的潜伏故障,并 TDD 证明生效。
|
||
3. **Redis 凭据止血轮换(PP-03 应急)**:立即改 .env.production 注入新密码 + 重建 Redis 数据,不动 git 历史(filter-repo 留待下一正常发布窗口)。1 人日,止血无破坏性。
|
||
|
||
## 5. 主题级风险
|
||
|
||
- **R1 破坏性 DDL 三步走拉长交付周期 2-3 倍**:产品/研发强烈反对,需明确医疗场景牺牲速度换可靠性的边界,并以"特性开关(举措未单列)作廉价回滚"对冲。
|
||
- **R2 重试风暴**:retry_dead_letters 广播后消费者仍失败会再次 dead-letter,已确认 max_attempts=5 被 filter(events.rs:390)兜底;但 AI queue worker 启动后历史积压一次性触发大量 LLM 调用 → Ollama OOM,需先 truncate 或加 backpressure。
|
||
- **R3 蓝绿双副本 +30% 资源成本**:需与运维预算对齐;nginx upstream 切换需 DB schema 兼容期,双写易引入数据不一致。
|
||
- **R4 BACKUP_PASSPHRASE 轮换需重加密历史备份**:工作量被低估;restore-drill 跑生产备份含 PII,CI runner 必须隔离 + 跑完即销毁。
|
||
- **R5 告警信噪比未治理重蹈"狼来了"**:上线初期只开 SEV-1;企微 webhook 限流 20/min 需评估,否则告警风暴被腾讯截断。
|
||
- **R6 /health/ready 返回 503 过敏感致发布期误摘流**:发布期临时调大阈值或加 maintenance 模式。
|
||
- **R7 pg_partman 需 superuser + shared_preload_libraries**:云 PG(腾讯云)可能限制;私有化部署客户需同步安装扩展。
|
||
|
||
## 6. 专家分歧调和(dissentingViews → 最终取舍)
|
||
|
||
- **D1 PP-03 Redis 凭据处置优先级**(安全 vs SRE vs 发布三方分歧):SRE 主张业务链路自愈优先;安全主张立即 filter-repo 清洗历史;发布主张 filter-repo 是破坏性"发布"本身会重写哈希破坏分支。**取舍**:采纳发布管理专家的两阶段方案——立即轮换密码止血(无 git 影响)+ 下一正常发布窗口做 filter-repo(带备份+全员协调)。理由:上线临门一脚搞历史重写风险高于泄露被利用的渐进风险,止血优先。
|
||
- **D2 是否上 K8s+Helm**:SRE 明确反对,主张蓝绿+migrate 子命令是最小可行 HA。**取舍**:采纳 SRE 立场,不上 K8s。理由:DevOps 4.2 分、单兵运维团队把复杂度债务换成事故概率不划算;蓝绿+heartbeat 门禁覆盖 80% 发布安全需求。
|
||
- **D3 可观测性栈建设时机**(DevOps 专家 vs 发布管理边界之争):发布管理主张先 heartbeat+积压指标覆盖发布决策(1 周可落地),DevOps 主张上完整 Alertmanager/Loki/Jaeger(4-8 周)。**取舍**:两者互补但分期——先落地举措 C(heartbeat 门禁)和举措 D 第 1-2 步(Alertmanager + SEV-1),完整链路追踪(Loki/Jaeger)列入 V1.1 路线图而非 V1 上线阻塞项。
|
||
- **D4 分区告警方式**(SRE vs 监控专家):SRE 主张按时间倒计时 metric 告警,监控专家常规按错误率。**取舍**:采纳 SRE——确定性故障(硬截止)不该按概率监控,按剩余周数单调递减 gauge 提前 10 周预警。
|
||
- **D5 特性开关是否上提 erp-core**:违反 CLAUDE.md §1.3 模块边界铁律。**取舍**:以 trait 形式定义在 erp-core,各模块可选依赖(非直接耦合);开关生命周期规范——最多存活 2 个发布周期后强制移除,防 if-flag 蔓延。本主题未单列举措,并入举措 B 回滚工具箱。
|
||
|
||
## 7. 路线(6-12 个月)
|
||
|
||
- **M0(上线前/上线时,1-2 周)**:速赢 1+2+3 —— heartbeat 门禁、死信/AI 队列接线、Redis 凭据止血。
|
||
- **M1(上线后 1 个月)**:举措 B 第 1-3 步(clap 子命令 + CI 镜像推送)+ 举措 D 第 1-2 步(Alertmanager + SEV-1)+ 举措 A 第 2 步(分区 guard 应急)。
|
||
- **M2(上线后 2-3 个月)**:举措 B 第 4-5 步(canary + 破坏性 DDL checklist)+ 举措 E 第 1-2 步(restore-drill + runbooks)+ pg_partman 根治。
|
||
- **M3(上线后 4-6 个月)**:举措 E 第 3-4 步(兼容窗口契约 + fire-drill 制度化)+ 完整可观测性栈(Loki/Jaeger)评估。
|
||
- **M4(6-12 个月)**:根据 M1-M3 的 MTTR/RPO/RTO 实测数据,评估是否升级到 K8s+Helm(届时团队规模与 DevOps 成熟度可能已支撑)。
|
||
|
||
> 所有举措均强调 TDD 证明接线生效、CI 门禁拦截、fire-drill 验证——质量保障"门禁反推"视角贯穿全程,不单列质量举措。
|