- Create SECURITY.md with vulnerability disclosure policy - Create docs/README.md as documentation index page - Create docs/observability.md with metrics/logs/traces/alerting/SLO specs - Create docs/runbook.md with troubleshooting scenarios (RB-01 to RB-05) - Create docs/security.md with security design and compliance - Create docs/release.md with versioning and rollback strategies - Create docs/database.md with database design and migration strategy - Create docs/adr/0005-cache-error-handler.md for cache error handling ADR - Update README.md with documentation index section - Update docs/architecture.md to update B-7 rule and add trust boundary section - Create docs/api-guide.md with API specifications - Fix method names in OrderDao.java to match OrderMapper interface - Add Spring Boot Actuator dependency to tmpl-infrastructure - Fix Redis ping method in RedisHealthContributor.java - Fix syntax errors and method names in OrderAppService.java Co-authored-by: Qwen-Coder <[email protected]>
292 lines
8.9 KiB
Markdown
292 lines
8.9 KiB
Markdown
# 运维排查手册 (Runbook)
|
||
|
||
本文档覆盖 tmpl-api 服务最常见的故障场景。每个场景包含:触发条件、排查步骤、
|
||
恢复操作和事后改进建议。
|
||
|
||
> **适用角色**:SRE、运维工程师、值班开发人员。
|
||
> **前置条件**:拥有 Kubernetes 集群 `kubectl` 权限、Grafana/Prometheus 访问权限、
|
||
> 数据库与 Redis CLI 访问权限。
|
||
|
||
---
|
||
|
||
## RB-01 数据库连接池耗尽
|
||
|
||
### 触发条件
|
||
|
||
告警 `DatabaseConnectionExhaustion` 触发(`hikaricp_connections_pending > 5` 持续 1 分钟)
|
||
或应用日志出现 `HikariPool-1 - Connection is not available, request timed out after 30000ms`。
|
||
|
||
### 排查步骤
|
||
|
||
**Step 1 — 确认连接池状态**
|
||
|
||
```bash
|
||
curl -s http://<pod-ip>:9090/actuator/metrics/hikaricp.connections.active | jq .
|
||
curl -s http://<pod-ip>:9090/actuator/metrics/hikaricp.connections.pending | jq .
|
||
```
|
||
|
||
若 `active` 接近 `max`(默认 10)且 `pending` 持续增长,确认连接池耗尽。
|
||
|
||
**Step 2 — 检查数据库慢查询**
|
||
|
||
```sql
|
||
SELECT pid, now() - pg_stat_activity.query_start AS duration, query, state
|
||
FROM pg_stat_activity
|
||
WHERE (now() - pg_stat_activity.query_start) > interval '5 seconds'
|
||
AND state != 'idle'
|
||
ORDER BY duration DESC;
|
||
```
|
||
|
||
**Step 3 — 检查是否存在长事务**
|
||
|
||
```sql
|
||
SELECT pid, age(clock_timestamp(), xact_start) AS xact_age, query
|
||
FROM pg_stat_activity
|
||
WHERE xact_start IS NOT NULL
|
||
ORDER BY xact_age DESC
|
||
LIMIT 10;
|
||
```
|
||
|
||
**Step 4 — 确认数据库最大连接数**
|
||
|
||
```sql
|
||
SHOW max_connections;
|
||
SELECT count(*) FROM pg_stat_activity;
|
||
```
|
||
|
||
### 恢复操作
|
||
|
||
| 严重程度 | 操作 |
|
||
|----------|------|
|
||
| 中等 | 终止长事务:`SELECT pg_terminate_backend(<pid>);` |
|
||
| 严重 | 重启受影响的 Pod:`kubectl rollout restart deployment/tmpl-api -n <ns>` |
|
||
| 持续 | 提升 HikariCP `maximumPoolSize`(配合数据库 `max_connections` 调整) |
|
||
|
||
### 事后改进
|
||
|
||
检查是否有缺少索引的查询导致全表扫描;确认 `@Transactional` 范围是否过大;考虑引入连接池监控 Dashboard 面板和更低的超时阈值。
|
||
|
||
---
|
||
|
||
## RB-02 Redis 连接抖动 / 缓存降级
|
||
|
||
### 触发条件
|
||
|
||
告警 `RedisDown` 触发,或日志中出现 `GracefulCacheErrorHandler` 的 WARN 级别条目
|
||
(`Cache 'order' get failed`),health 端点显示 `redisPing: DOWN`。
|
||
|
||
### 排查步骤
|
||
|
||
**Step 1 — 确认 Redis 实例状态**
|
||
|
||
```bash
|
||
redis-cli -h <redis-host> -p 6379 PING
|
||
redis-cli -h <redis-host> -p 6379 INFO server | grep uptime
|
||
redis-cli -h <redis-host> -p 6379 INFO clients | grep connected_clients
|
||
```
|
||
|
||
**Step 2 — 检查网络连通性**
|
||
|
||
```bash
|
||
kubectl exec -it <pod> -- nc -zv <redis-host> 6379
|
||
```
|
||
|
||
**Step 3 — 检查 Redis 内存与驱逐**
|
||
|
||
```bash
|
||
redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human|evicted_keys"
|
||
```
|
||
|
||
**Step 4 — 查看应用日志确认降级行为**
|
||
|
||
```bash
|
||
kubectl logs <pod> --since=5m | grep -i "cache.*fail\|redis\|GracefulCacheErrorHandler"
|
||
```
|
||
|
||
### 恢复操作
|
||
|
||
| 场景 | 操作 |
|
||
|------|------|
|
||
| Redis 实例宕机 | 重启 Redis Pod / 故障转移到从节点 |
|
||
| 网络问题 | 检查 NetworkPolicy、Service 配置 |
|
||
| 内存满 | 调整 `maxmemory` 或优化 key TTL |
|
||
| 应用侧连接池满 | 调整 `spring.data.redis.lettuce.pool.max-active` |
|
||
|
||
> **关键认知**:当 `app.cache.enabled=true` 且 Redis 不可用时,`GracefulCacheErrorHandler`
|
||
> 会记录 WARN 并回退到数据库查询。服务不会宕机,但 DB 负载会上升。若 DB 也接近瓶颈,
|
||
> 应优先恢复 Redis。
|
||
|
||
### 事后改进
|
||
|
||
考虑引入 Redis Sentinel / Cluster 高可用方案;评估是否需要 Resilience4j CircuitBreaker 包裹缓存调用;确认缓存 key TTL 设置合理。
|
||
|
||
---
|
||
|
||
## RB-03 Flyway 迁移失败
|
||
|
||
### 触发条件
|
||
|
||
应用启动失败,日志出现 `FlywayMigrateException` 或 `Migration checksum mismatch`。
|
||
Pod 处于 CrashLoopBackOff 状态。
|
||
|
||
### 排查步骤
|
||
|
||
**Step 1 — 查看 Flyway 日志**
|
||
|
||
```bash
|
||
kubectl logs <pod> | grep -i "flyway\|migration"
|
||
```
|
||
|
||
**Step 2 — 检查迁移历史表**
|
||
|
||
```sql
|
||
SELECT version, description, success, checksum, installed_on
|
||
FROM flyway_schema_history
|
||
ORDER BY installed_rank DESC
|
||
LIMIT 10;
|
||
```
|
||
|
||
**Step 3 — 确认失败类型**
|
||
|
||
| 类型 | 日志关键词 | 说明 |
|
||
|------|------------|------|
|
||
| Checksum 不匹配 | `checksum mismatch` | 已应用的迁移文件被修改 |
|
||
| SQL 语法错误 | `ERROR: syntax error` | 迁移脚本有 SQL 错误 |
|
||
| 冲突版本号 | `Found more than one migration with version` | 版本号重复 |
|
||
| 手动修改了库 | `Detected resolved migration not applied` | 库结构被手动改动 |
|
||
|
||
### 恢复操作
|
||
|
||
| 场景 | 操作 |
|
||
|------|------|
|
||
| SQL 语法错误 | 修复脚本,手动清理 `flyway_schema_history` 中失败记录:`DELETE FROM flyway_schema_history WHERE success = false;` |
|
||
| Checksum 不匹配 | **切勿修改已部署的迁移文件!** 使用 `flyway repair` 仅在确认数据一致时更新 checksum |
|
||
| 手动修改了库 | 创建补偿迁移脚本(新版本号),使 schema 回到预期状态 |
|
||
|
||
> **重要**:Flyway 迁移本质上是不可逆的(forward-only)。每次修复后必须创建新的迁移脚本
|
||
> 而非修改历史脚本。回滚需通过编写反向 DDL 的新迁移实现。
|
||
|
||
### 事后改进
|
||
|
||
所有迁移脚本必须在 CI 中通过 Testcontainers 验证;禁止在 `main` 分支合并后修改迁移文件;命名规范参见 [database.md](database.md)。
|
||
|
||
---
|
||
|
||
## RB-04 P95 延迟飙升
|
||
|
||
### 触发条件
|
||
|
||
告警 `HighLatencyP95` 触发(P95 > 500ms 持续 3 分钟),或 Grafana 延迟面板出现
|
||
突增尖峰。
|
||
|
||
### 排查步骤
|
||
|
||
**Step 1 — 定位慢端点**
|
||
|
||
在 Prometheus 中查询:
|
||
|
||
```promql
|
||
topk(5,
|
||
histogram_quantile(0.95,
|
||
sum(rate(http_server_requests_seconds_bucket[5m])) by (le, uri)
|
||
)
|
||
)
|
||
```
|
||
|
||
**Step 2 — 排除基础设施因素**
|
||
|
||
检查 HikariCP 等待连接数(→ RB-01)、Redis 延迟(→ RB-02)、Pod CPU/内存使用率、
|
||
GC 暂停时间:
|
||
|
||
```promql
|
||
rate(jvm_gc_pause_seconds_sum[5m]) / rate(jvm_gc_pause_seconds_count[5m])
|
||
```
|
||
|
||
**Step 3 — 检查上游依赖**
|
||
|
||
若服务调用外部 API 或消息队列,检查对应 Timer 指标或日志中的耗时。
|
||
|
||
**Step 4 — 检查缓存命中率**
|
||
|
||
```promql
|
||
sum(rate(cache_gets_total{result="hit"}[5m]))
|
||
/
|
||
sum(rate(cache_gets_total[5m]))
|
||
```
|
||
|
||
命中率骤降可能导致大量 DB 查询,引发延迟升高。
|
||
|
||
### 恢复操作
|
||
|
||
| 根因 | 操作 |
|
||
|------|------|
|
||
| DB 慢查询 | 添加索引、优化 SQL、终止长事务 |
|
||
| GC 抖动 | 检查堆内存配置、排查内存泄漏 |
|
||
| 缓存击穿 | 恢复 Redis、预热缓存 |
|
||
| 流量突增 | HPA 扩容、启用限流 |
|
||
|
||
### 事后改进
|
||
|
||
设置 P99 延迟告警;在慢查询日志中启用 `log_min_duration_statement`;考虑在 API 层引入超时和熔断。
|
||
|
||
---
|
||
|
||
## RB-05 容器反复重启 (CrashLoopBackOff)
|
||
|
||
### 触发条件
|
||
|
||
`kubectl get pods` 显示 Pod 状态为 `CrashLoopBackOff`,Restart count 持续增加。
|
||
|
||
### 排查步骤
|
||
|
||
**Step 1 — 查看 Pod 事件和日志**
|
||
|
||
```bash
|
||
kubectl describe pod <pod-name> -n <ns>
|
||
kubectl logs <pod-name> -n <ns> --previous
|
||
```
|
||
|
||
**Step 2 — 分类退出原因**
|
||
|
||
| Exit Code | 含义 | 常见原因 |
|
||
|-----------|------|----------|
|
||
| 0 | 正常退出 | 主线程结束(配置错误,未启动 Web 服务器) |
|
||
| 1 | 应用异常 | Bean 创建失败、数据库不可达、Flyway 失败 |
|
||
| 137 | OOMKilled | 容器内存超出 `resources.limits.memory` |
|
||
| 143 | SIGTERM | 被 kubelet 杀死(探针超时) |
|
||
|
||
**Step 3 — 针对性检查**
|
||
|
||
若 Exit Code = 137:检查 `resources.limits.memory` 是否过小,JVM `-Xmx` 是否超出容器限制,堆外内存(Metaspace、DirectBuffer)是否泄漏。
|
||
|
||
若 Exit Code = 1:检查 `application.yml` 配置是否正确,数据库/Redis 连接信息是否正确注入(Secret 挂载),Flyway 是否失败(→ RB-03)。
|
||
|
||
若 Exit Code = 143:检查 startup/liveness 探针配置,`initialDelaySeconds` 是否足够。
|
||
|
||
### 恢复操作
|
||
|
||
| 根因 | 操作 |
|
||
|------|------|
|
||
| OOMKilled | 提升 `resources.limits.memory`,调整 JVM 参数 `-XX:MaxRAMPercentage=75` |
|
||
| DB 不可达 | 确认 Secret 中连接 URL 正确,DB 实例正常,NetworkPolicy 允许流量 |
|
||
| 探针超时 | 延长 `initialDelaySeconds`、`timeoutSeconds` |
|
||
| Flyway 失败 | 参见 RB-03 |
|
||
|
||
### 事后改进
|
||
|
||
建立 Pod 资源 baseline(通过 VPA 推荐或历史监控数据);在 CI/CD 中验证配置文件完整性;为 startup 探针留足应用冷启动时间(至少 60s)。
|
||
|
||
---
|
||
|
||
## 通用排查工具速查
|
||
|
||
| 用途 | 命令 |
|
||
|------|------|
|
||
| Pod 日志 | `kubectl logs <pod> -n <ns> -f --since=10m` |
|
||
| 前一次崩溃日志 | `kubectl logs <pod> -n <ns> --previous` |
|
||
| 进入 Pod | `kubectl exec -it <pod> -n <ns> -- /bin/sh` |
|
||
| 端口转发 | `kubectl port-forward <pod> 9090:9090 -n <ns>` |
|
||
| 健康检查 | `curl http://localhost:9090/actuator/health | jq .` |
|
||
| Prometheus 指标 | `curl http://localhost:9090/actuator/prometheus` |
|
||
| Redis CLI | `redis-cli -h <host> -p 6379 INFO` |
|
||
| PostgreSQL | `psql -h <host> -U <user> -d <db> -c "SELECT 1;"` | |