feat(p6): production hardening with circuit breaker, backup, monitoring and chaos engineering
Some checks failed
CI Go / test (push) Has been cancelled
CI Python / test (push) Has been cancelled
CI TypeScript / test (push) Has been cancelled
CI Proto / lint (push) Failing after 8m7s

P6 生产硬化阶段交付物(46 文件):

## 1. API Gateway 中间件链(services/api-gateway/internal/middleware/)
- circuit-breaker.go: gobreaker v2 熔断器(5s 窗口/50% 错误率/30s OPEN→HALF_OPEN)
- ratelimit.go: 令牌桶限流(sync.Map + cleanup goroutine,默认 100rps/20 burst)
- cors.go: CORS 中间件(CORS_ORIGINS 环境变量)
- recovery.go: panic 恢复 + uuid request_id
- security.go: 安全头 + 请求体 10MB 限制
- requestid.go: 请求 ID 注入
- health/health.go: /healthz + /readyz 健康检查
- main.go: 重写注册全部中间件链(Recovery→RequestID→CORS→Security→BodyLimit→RateLimit→CircuitBreaker→Auth)

## 2. 基础设施硬化(infra/)
- backup/backup-mysql.sh: MySQL 全量备份(mysqldump+gzip,按服务独立)
- backup/restore-mysql.sh: 恢复脚本
- backup/backup-cron.sh: cron 调度入口(5 服务批量备份)
- alertmanager/alertmanager.yml: 告警路由(webhook + 邮件示例)
- prometheus/rules.yml: 8 条告警规则(服务可用性/性能/资源 3 组)
- grafana/dashboards/microservices-overview.json: 4 panel 仪表盘
- grafana/provisioning/: 数据源和仪表盘 provisioning
- k8s/namespace.yaml: 4 命名空间(edu-system/services/monitoring/ingress)
- k8s/api-gateway-deployment.yaml: Deployment + Service 骨架
- chaos/experiments.yaml: 3 个 Litmus 混沌实验(pod-kill/network-latency/disk-fill)
- docker-compose.monitoring.yml: 监控栈 profile
- security/secrets.example.env: 8 项密钥占位符
- security/waf-rules.conf: ModSecurity WAF 规则骨架

## 3. 业务服务健康检查 + 优雅停机(5 个 NestJS 服务)
- services/{iam,core-edu,content,msg,classes}/src/shared/health/: /healthz + /readyz
- services/{iam,core-edu,content,msg,classes}/src/shared/lifecycle/: OnModuleInit + OnApplicationShutdown

## 4. Python 服务健康检查
- services/{ai,data-ana}/src/health/health.py: FastAPI APIRouter

## 5. 运维文档
- docs/architecture/runbooks/p6-hardening.md: P6 总览 Runbook(9 章节)
- docs/architecture/runbooks/incident-response.md: 事件响应手册(5 章节)
- docs/architecture/004-p6-addendum.md: 004 架构补记 P6 章节
- docs/troubleshooting/known-issues-p6-addendum.md: 15 条 P6 场景→技术映射

## 验收信号
- RPO ≤ 15min(MySQL 备份 + binlog PITR)
- RTO ≤ 30min(K8s 滚动更新 + DNS 切换)
- P99 ≤ 500ms(熔断 + 限流 + 缓存)
- 熔断器错误率 > 50% 触发 OPEN
- 限流 100rps/20 burst
- 备份保留 7 天
- 混沌实验每月 1 次
This commit is contained in:
SpecialX
2026-07-08 02:16:58 +08:00
parent 7474a92e3b
commit e9ea34fe53
46 changed files with 3229 additions and 3 deletions

View File

@@ -0,0 +1,107 @@
## 15. P6 生产硬化(补记)
> 本章为 `004_architecture_impact_map.md` 的 P6 阶段补记,记录生产硬化引入的横切关注点与基础设施栈。
> 维护规则与正文一致:源码变更后同步 `npm run arch:scan` 更新 arch.db。
### 15.1 横切关注点矩阵
| 关注点 | NestJS 服务 | Python 服务 | Go 网关 | 实现位置 |
|--------|-------------|-------------|---------|----------|
| 健康检查 | HealthController | health.py | /healthz | shared/health, src/health |
| 优雅停机 | LifecycleService | FastAPI lifespan | enableShutdownHooks | shared/lifecycle |
| 熔断 | 经 Gateway | 经 Gateway | gobreaker v2 | api-gateway/middleware |
| 限流 | 经 Gateway | 经 Gateway | token bucket | api-gateway/middleware |
| 链路追踪 | tracer.ts | OpenTelemetry | OpenTelemetry | shared/observability |
| 指标 | metrics.ts | prometheus_fastapi | prometheus | shared/observability |
| 日志 | logger.ts | structlog | zap | shared/observability |
| 错误处理 | global-error.filter | exception handler | middleware | shared/errors |
### 15.2 API Gateway 中间件链
请求流经顺序(出向到下游服务):
```
请求入口
→ WAF规则匹配
→ CORS
→ 限流token bucket按 route+tenant
→ 熔断gobreaker v2按下游服务
→ 重试(指数退避,仅幂等)
→ 链路追踪注入
→ 转发到下游
→ 响应 → 指标记录 → 返回
```
### 15.3 可观测性栈
```
应用层NestJS / Python / Go
→ OpenTelemetry SDKtrace + metrics
→ OTLP exporter
→ 采集层
├─ Prometheusmetrics
├─ Tempo / Jaegertrace
└─ Loki / ELKlog
→ 展示层
├─ Grafana仪表盘
└─ Alertmanager告警路由
```
关键指标命名约定:
- `http_request_duration_seconds`histogram含 service/route/status 维度)
- `circuit_breaker_state`gauge0=Closed / 1=Open / 2=HalfOpen
- `rate_limiter_rejected_total`counter
- `db_connections_in_use`gauge
- `kafka_consumer_lag`gauge
### 15.4 安全栈
| 层 | 机制 | 配置位置 |
|----|------|----------|
| 边缘 | WAF + DDoS 防护 | Cloudflare / 入口 LB |
| 网关 | JWT 校验 + 限流 + CORS | api-gateway |
| 服务 | requirePermission 权限点 | modules/*/actions |
| 数据 | 字段加密 + 审计日志 | data-access |
| 密钥 | KMS + K8s Secret + 轮换 | deploy/k8s/secrets |
| 传输 | mTLS服务间可选+ TLS边缘 | mesh / ingress |
### 15.5 健康检查约定
- `GET /healthz`liveness仅返回进程存活不检查依赖避免滚动重启雪崩
- `GET /readyz`readiness检查 DB 等关键依赖,失败返回 503
- K8s 探针livenessProbe → /healthzreadinessProbe → /readyz
- Python 服务 readyz 简化为 ok + TODO待依赖客户端就绪后补全
- 无需鉴权,必须在路由白名单中放行
### 15.6 优雅停机约定
- NestJS`app.enableShutdownHooks()` 注册 SIGTERM/SIGINT 钩子
- LifecycleService 实现 OnApplicationShutdown按序关闭Kafka producer → Redis → DataSource
- K8s`terminationGracePeriodSeconds=60`preStop hook 可加 sleep 5s 摘流量
- PythonFastAPI lifespan shutdown 事件,关闭连接池
- 销毁顺序理由:先停外部消息生产(避免新事件),再关缓存,最后关 DB
### 15.7 灾难恢复策略
- 备份CronJob 每 15minPostgreSQL + Redis + Kafka offset
- 恢复:`scripts/restore/`,月度演练验证 RTO
- 多 AZPod 反亲和 + DB 同步复制 + Redis 哨兵 + Kafka ISR=2
- DNS 切换区域级故障TTL=60s季度演练
### 15.8 与正文章节的对应
| 本章小节 | 对应正文章节 |
|----------|--------------|
| 横切关注点 | 第 3 章 共享内核 |
| 中间件链 | 第 5 章 API Gateway |
| 可观测性 | 第 10 章 可观测性 |
| 安全栈 | 第 11 章 安全 |
| 健康检查 | 第 6 章 服务边界 |
| 灾难恢复 | 第 12 章 部署与运维 |
### 15.9 同步要求
新增导出符号需在落地到 Edu 仓库后运行 `npm run arch:scan` 更新 arch.db
- `HealthController``HealthModule`5 个 NestJS 服务)
- `LifecycleService`5 个 NestJS 服务)
- `health.py` routerai、data-ana

View File

@@ -0,0 +1,171 @@
# 事件响应手册
> 目标:规范生产事件的分级、响应、处置与复盘,确保 RTO ≤ 30min
> 关联:`docs/architecture/runbooks/p6-hardening.md`
## 1. 事件分级
| 级别 | 定义 | 影响 | 响应时效 | 升级 |
|------|------|------|----------|------|
| P0 | 全站不可用 / 核心数据损坏 | 全部用户 | 5 分钟内响应 | 立即升级至 CTO |
| P1 | 核心功能不可用 / 关键 SLO 破坏 | 大量用户 | 10 分钟内响应 | 升级至服务负责人 |
| P2 | 部分功能降级 / 非核心故障 | 部分用户 | 30 分钟内响应 | 服务负责人跟进 |
| P3 | 单点告警 / 潜在风险 | 少量/无用户 | 工作时间内响应 | On-Call 自行处理 |
### 1.1 分级示例
- P0网关全挂、主 DB 不可用且无法故障转移、数据丢失超过 RPO
- P1登录不可用、课程播放不可用、Kafka 生产阻塞
- P2消息推送延迟、报表生成失败、单个非核心服务宕机
- P3单 Pod 重启、磁盘使用率告警、慢查询告警
## 2. 响应流程
```
发现 → 确认 → 升级 → 处理 → 恢复 → 复盘
```
### 2.1 发现
- 告警来源Prometheus / Alertmanager / 用户反馈 / 人工巡检
- 第一动作:在 On-Call 群贴告警,标注收到时间
### 2.2 确认
- On-Call 5 分钟内确认告警真实性
- 排除误报(探针抖动、已知维护窗口)
- 初步定级创建事故工单Jira / 飞书项目)
### 2.3 升级
- 超出处置能力 → 立即升级
- P0/P1 → 拉事故群,通知相关服务 Owner
- 涉及外部公告 → 通知客服与公关
### 2.4 处理
- 遵循"先恢复,后定位"原则
- 优先使用预案:回滚、扩容、降级、熔断、切换
- 每个操作记录时间戳与执行人
- 关键决策需事故指挥确认
### 2.5 恢复
- 健康检查通过、SLO 恢复
- 观察 15 分钟确认稳定
- 关闭事故工单,进入复盘
### 2.6 复盘
- 48 小时内提交复盘报告
- 无 blame 文化:对事不对人
- 输出改进项,录入 `docs/architecture/roadmap/tech-debt.md`
## 3. On-Call 轮值
### 3.1 轮值制度
- 主备双人轮值,每周轮换
- 工作日9:00-21:00 主,其余备
- 节假日:全天主备
- 交接:周一 10:00 站会交接,遗留问题清单
### 3.2 联络方式
- 电话:主 + 备 + 升级链
- 即时通讯:飞书 On-Call 群
- 告警PagerDuty / 飞书机器人
### 3.3 响应要求
- P0/P1电话 5 分钟内接听
- 告警确认:群内 5 分钟内回复
- 无法响应:自动升级至备值
## 4. 沟通模板
### 4.1 事故通报(初报)
```
【事故通报】<P级别> - <简述>
时间:<YYYY-MM-DD HH:MM>
级别:<P0/P1/P2/P3>
影响:<受影响功能/用户范围>
现状:<已知信息>
负责人:<On-Call>
下一步:<计划动作>
```
### 4.2 进展更新(每 30 分钟或重大变化)
```
【进展更新】<事故标题>
时间:<HH:MM>
进展:<自上次以来发生/完成的事>
当前状态:<仍受影响的功能>
下一步:<接下来 30 分钟计划>
```
### 4.3 恢复通知
```
【恢复通知】<事故标题>
恢复时间:<HH:MM>
持续时长:<时长>
原因:<根因摘要>
影响:<最终影响评估>
后续:<复盘会时间>
```
### 4.4 复盘报告
```
【复盘报告】<事故标题>
时间:<起止时间>
级别:<P级别>
影响:<用户/功能/数据>
时间线:<关键事件时间轴>
根因:<5why 分析>
处置:<做了什么、有效/无效>
改进项:<TODO + 负责人 + 截止>
经验:<可沉淀到 known-issues / runbook 的内容>
```
## 5. 常见事故处置
### 5.1 DB 主从切换
1. 确认主库故障(健康检查、连接超时)
2. 触发自动故障转移Patroni / Orchestrator
3. 若自动失败,手动 `./scripts/db/failover.sh --service <svc>`
4. 更新连接配置 / 刷新连接池
5. 验证读写正常、数据位点
6. 旧主恢复后作为从库加入
### 5.2 Kafka 消费堆积
1. 查看堆积:`kubectl exec -- kafka-consumer-lag`
2. 定位慢消费者查日志、trace
3. 扩容消费者副本HPA 或手动)
4. 若处理逻辑慢:临时降级非核心处理
5. 堆积消化后恢复
6. 复盘:扩容阈值、消费者并发配置
### 5.3 服务雪崩
1. 确认雪崩源头(哪个下游故障)
2. 确认熔断器已打开Gateway 状态)
3. 若未打开:手动 `./scripts/gateway/circuit-breaker.sh open <service>`
4. 降级非核心功能
5. 扩容上游服务应对重试流量
6. 修复下游、半开试探、逐步恢复
7. 复盘:熔断参数、依赖隔离
## 附录:升级链
| 级别 | 第一响应 | 升级 1 | 升级 2 | 升级 3 |
|------|----------|--------|--------|--------|
| P0 | On-Call | 服务负责人 | 架构负责人 | CTO |
| P1 | On-Call | 服务负责人 | 架构负责人 | - |
| P2 | On-Call | 服务负责人 | - | - |
| P3 | On-Call | - | - | - |

View File

@@ -0,0 +1,264 @@
# P6 生产硬化 Runbook
> 阶段P6 生产硬化
> 目标指标RPO ≤ 15minRTO ≤ 30minP99 ≤ 500ms
> 维护者SRE 团队
> 关联文档:`docs/architecture/004_architecture_impact_map.md`、`docs/architecture/roadmap/tech-debt.md`、`docs/architecture/runbooks/incident-response.md`
## 1. 概览
P6 阶段围绕"稳定、可观测、可恢复"三大主题,对业务服务进行生产硬化,确保系统在流量峰值、依赖故障、区域级灾难下仍能满足核心 SLO。
### 1.1 目标指标
| 指标 | 目标 | 度量来源 |
|------|------|----------|
| RPO恢复点目标 | ≤ 15 分钟 | 备份调度日志 + WAL 位点 |
| RTO恢复时间目标 | ≤ 30 分钟 | 故障注入演练计时 |
| P99 延迟 | ≤ 500 ms | Prometheus http_request_duration_seconds |
| 可用性 | ≥ 99.9% | 多区域健康探针汇总 |
| 错误率 | ≤ 0.1% | Gateway 5xx 比率 |
### 1.2 交付物清单
- API Gateway 熔断/限流中间件Gogobreaker v2 + token bucket
- 备份与恢复脚本(`scripts/backup/``scripts/restore/`
- Prometheus 告警规则 + Alertmanager 路由配置
- Grafana 仪表盘服务总览、SLO、依赖健康
- 混沌工程实验库(`chaos/`
- K8s 部署 manifest`deploy/k8s/`
- 健康检查标准化(`/healthz``/readyz`
- 优雅停机(`lifecycle.service.ts` + `app.enableShutdownHooks()`
## 2. 熔断与限流
### 2.1 中间件使用
API GatewayGo在路由链路上统一接入
- 熔断器:`github.com/sony/gobreaker/v2`,按下游服务维度建桶
- 限流:基于 `sync.Map` 的令牌桶,按 `route + tenant` 维度限流
熔断器配置示例(伪代码):
```go
cb := gobreaker.NewCircuitBreaker[any](gobreaker.Settings{
Name: "core-edu",
MaxRequests: 5, // 半开态最大试探请求
Interval: 60 * time.Second, // 计数窗口
Timeout: 30 * time.Second, // 开启态冷却
ReadyToTrip: func(c gobreaker.Counts) bool {
return c.ConsecutiveFailures > 5 || c.TotalFailures/c.TotalRequests > 0.5
},
})
```
### 2.2 参数调优
| 参数 | 默认 | 推荐范围 | 调优依据 |
|------|------|----------|----------|
| MaxRequests半开试探 | 5 | 3-10 | 下游恢复速度 |
| Timeout冷却 | 30s | 10-60s | 下游故障平均恢复时间 |
| ConsecutiveFailures | 5 | 3-10 | 误报容忍度 |
| 限流 QPS租户级 | 1000 | 500-5000 | 租户 SLA 等级 |
| 桶清理周期 | 60s | 30-120s | 内存占用与精度权衡 |
### 2.3 故障演练流程
1. 在预发环境注入下游延迟tc/netem 500ms+
2. 观察熔断器状态切换Closed → Open → Half-Open → Closed
3. 验证限流桶在窗口边界正确清理sync.Map + ticker
4. 记录 P99 与错误率到演练报告
5. 回滚注入:`./chaos/rollback.sh <experiment>`
## 3. 备份与恢复
### 3.1 备份脚本
- 位置:`scripts/backup/backup-cron.sh`
- 调度K8s CronJob每 15 分钟一次(满足 RPO ≤ 15min
- 内容PostgreSQL 全量 + WAL 归档 + Redis RDB + Kafka topic offset 快照
- 产物写入对象存储MinIO/S3保留策略 7d日备 30d
执行:
```bash
./scripts/backup/backup-cron.sh --service iam --type full
./scripts/backup/backup-cron.sh --service iam --type incr
```
### 3.2 恢复演练流程
1. 在隔离环境拉起空集群
2. 执行 `./scripts/restore/restore.sh --service <svc> --backup-id <id>`
3. 校验数据一致性(行数、最新位点、校验和)
4. 记录恢复耗时,验证 RTO ≤ 30min
5. 演练频率:每月一次
### 3.3 RPO 验证方法
- 对比最近一次备份时间与当前时间差
- 查询 `backup_log``last_success_at` 字段
- 告警:连续 2 个周期30min未成功备份 → P1
## 4. 监控告警
### 4.1 Prometheus 规则
位置:`deploy/observability/prometheus/rules/`
关键规则:
- `HighErrorRate`5xx 比率 > 1% 持续 5min
- `HighLatencyP99`P99 > 500ms 持续 5min
- `CircuitBreakerOpen`:熔断器处于 Open 态 > 1min
- `BackupStale`:备份超过 30min 未成功
- `DBConnectionsExhausted`:连接池使用率 > 90%
- `KafkaConsumerLag`:消费堆积 > 10000 持续 5min
### 4.2 Alertmanager 路由
- P0/P1 → PagerDuty + 电话
- P2 → 飞书群 + 邮件
- P3 → 飞书群
- 抑制:同一服务 5min 内同告警只发一次inhibit + group_by
### 4.3 Grafana 仪表盘
| 仪表盘 | 用途 | 关键面板 |
|--------|------|----------|
| Service Overview | 服务总览 | QPS、P99、错误率、熔断状态 |
| SLO Dashboard | SLO 跟踪 | 可用性、错误预算消耗 |
| Dependency Health | 依赖健康 | DB/Redis/Kafka 连接与延迟 |
| Backup Status | 备份状态 | 最近备份、RPO、恢复演练 |
## 5. 混沌工程
### 5.1 实验清单
| 实验 | 注入方式 | 预期表现 | 频率 |
|------|----------|----------|------|
| DB 主节点宕机 | kill postgres | 自动故障转移RTO<30min | 月 |
| Redis 不可达 | iptables 拒绝 | 降级到本地缓存 | 月 |
| Kafka broker 宕机 | kill broker | 生产重试,消费堆积可控 | 月 |
| 下游服务延迟 | tc netem +500ms | 熔断器打开,错误率<1% | 周 |
| 网络分区 | iptables 隔离 | 多可用区切换 | 季 |
| 磁盘满 | fill disk | 告警触发,写入降级 | 季 |
### 5.2 执行流程
1. 选择实验 → 在 `chaos/` 选择对应 YAML
2. 预检:确认告警通道、回滚脚本就绪
3. 执行:`kubectl apply -f chaos/<experiment>.yaml`
4. 观察Grafana + 日志
5. 回滚:`./chaos/rollback.sh <experiment>`
6. 复盘:记录到 `docs/architecture/runbooks/incident-response.md`
## 6. 安全加固
### 6.1 WAF 规则
- SQL 注入、XSS 模式匹配
- 路径穿越、命令注入
- 限速:单 IP > 100req/s 拦截
- 地域封禁(按需)
### 6.2 密钥管理
- 密钥存储K8s Secret + 外部 KMSVault
- 轮换DB 密码每 90 天JWT 签名密钥每 180 天
- 注入:通过环境变量 / 挂载卷,禁止入镜像
- 审计:所有密钥访问记录到 audit log
### 6.3 CORS 策略
- 允许来源:白名单域名(生产环境严格)
- 允许方法GET/POST/PUT/PATCH/DELETE
- 凭证允许Cookie
- 预检缓存600s
## 7. K8s 部署
### 7.1 Manifest 说明
位置:`deploy/k8s/`,每个服务一组 manifest
- `deployment.yaml`:副本数、资源、探针、优雅停机
- `service.yaml`ClusterIP
- `hpa.yaml`CPU>70% 扩容min=3 max=20
- `poddisruptionbudget.yaml`minAvailable=2
- `networkpolicy.yaml`:限制出向
探针配置:
```yaml
livenessProbe:
httpGet: { path: /healthz, port: 3000 }
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet: { path: /readyz, port: 3000 }
initialDelaySeconds: 5
periodSeconds: 5
```
优雅停机:
```yaml
terminationGracePeriodSeconds: 60
```
### 7.2 Helm 化路线
- P6原生 manifest快速验证
- P7抽取 Helm Chartvalues.yaml 按环境区分
- P8引入 Argo CD GitOps 自动同步
## 8. 灾难恢复
### 8.1 RTO/RPO 目标
| 场景 | RTO | RPO |
|------|-----|-----|
| 单 Pod 故障 | 30s | 0 |
| 单节点故障 | 2min | 0 |
| 单可用区故障 | 10min | 0 |
| 区域级灾难 | 30min | 15min |
### 8.2 多可用区策略
- K8s 集群跨 3 可用区Pod 反亲和
- DB 主从跨可用区同步复制
- Redis 哨兵跨可用区
- Kafka min.insync.replicas=2跨可用区 broker
### 8.3 DNS 切换
- 区域级故障:通过全局 DNSCloudflare/Route53切换到备用区域
- 健康检查:每 10s 探测,连续 3 次失败自动切换
- TTL60s快速切换
- 演练:每季度一次 DNS 切换演练
## 9. 故障排查
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|------|----------|----------|----------|
| 5xx 激增 | 下游服务故障 | 查 Grafana 熔断状态、下游健康 | 确认熔断器已打开,扩容下游 |
| P99 升高 | DB 慢查询/连接耗尽 | 查 PG 慢日志、连接池 | 加索引/扩连接池/限流 |
| 消费堆积 | 消费者慢/宕机 | 查 Kafka lag、消费者日志 | 扩消费者、修 bug |
| 备份失败 | 存储/网络/凭证 | 查 backup-cron 日志 | 修凭证、清理旧备份 |
| 健康检查失败 | DB 不可达 | 查 DB 状态、网络 | 故障转移、恢复 DB |
| Pod 频繁重启 | OOM/探针失败 | 查 kubectl describe、内存 | 调资源/修探针 |
| 熔断不恢复 | 下游未恢复 | 查 Half-Open 试探结果 | 修复下游、调 Timeout |
| 限流误杀 | 桶配置过低 | 查限流日志、QPS | 调高桶容量 |
| DNS 切换无效 | TTL 缓存 | 查 DNS 解析链 | 等待 TTL / 清缓存 |
| 跨区延迟高 | 跨区流量 | 查网络拓扑 | 调亲和性就近访问 |
---
## 附录:关联文档
- 架构影响地图:`docs/architecture/004_architecture_impact_map.md`
- P6 架构补记:`docs/architecture/004-p6-addendum.md`
- 事件响应:`docs/architecture/runbooks/incident-response.md`
- 已知问题:`docs/troubleshooting/known-issues.md`
- P6 已知问题补丁:`docs/troubleshooting/known-issues-p6-addendum.md`
- 技术债务:`docs/architecture/roadmap/tech-debt.md`