真实事故复盘剧本:从指标抖动到根因的全链路追查
凌晨 02:14(本文时间均为 UTC+8 虚构)。PagerDuty
推送:CheckoutSLOBurnRateCritical1h
firing,slo:checkout:burnrate1h > 14.4
已持续 2 分钟。Grafana SLO 面板:p99 从 120ms 升至
2.3s,错误率 0.05% → 3.2%。
本文是虚构但可复现的排障教材:实验环境、指标名、PromQL/LogQL、kubectl 命令均可在 minikube/docker-compose 演练栈复现。不发明工具行为——命令与面板路径对齐本系列 Prometheus、SLO、告警、Traces、Logs、Events、内核追踪。
一、演练环境与前置条件
1.1 拓扑(虚构 ecommerce 生产)
| 组件 | 版本假设 | 可观测性 |
|---|---|---|
| checkout | Go 1.22, 12 replicas | OTel SDK → Collector |
| redis-cart | Redis 7 | redis_exporter |
| payment | Java 17 | OTel Java agent |
| inventory | Go | OTel |
| Prometheus | 2.51 | scrape + recording |
| Alertmanager | 0.27 | route → PagerDuty |
| Tempo | 2.4 | trace backend |
| Loki | 3.0 | {service="checkout"} |
| Grafana | 11 | SLO + RED dashboards |
1.2 复现仓库结构(建议)
incident-lab/
docker-compose.yml
prometheus/rules/slo-checkout.yaml
alertmanager/alertmanager.yml
otel-collector/config.yaml
apps/checkout/...
1.3 注入故障(与剧本一致)
# 模拟错误配置:将 redis maxclients 从 10000 改为 100 后滚动发布
kubectl patch configmap checkout-redis -n production --type merge -p '
{"data":{"redis.conf":"maxclients 100\ntimeout 300\n"}}'
kubectl rollout restart deployment/checkout-redis -n production
kubectl rollout restart deployment/checkout -n production预期现象:连接池耗尽 → p99 上升 → Burn Rate Page。与主剧本一致。
二、Golden Minute(T+0 — T+5)
T+0:Ack 与确认
- PagerDuty Ack;打开 19-alerting runbook URL。
- Grafana → Dashboard
slo-checkout→ 确认非维护窗口静默。
T+1:RED 定性
| 信号 | 正常基线 | 事故值 | 推断 |
|---|---|---|---|
| Rate | 1.2k rps | 1.15k rps | 流量未跌 |
| Errors | 0.05% | 3.2% | 服务在失败 |
| Duration p99 | 120ms | 2.3s | 尾部恶化 |
参考 指标体系 RED。
T+2:变更审查
并行打开 Events 面板,用下面命令把输出记入时间线。
kubectl:events
kubectl get events -n production --sort-by=.lastTimestamp | tail -20kubectl:rollout
kubectl rollout history deployment/checkout -n productionkubectl:diff
kubectl diff -f deploy/checkout.yamlkubectl:configmap
kubectl get configmap checkout-redis -n production -o yamlkubectl:pods
kubectl get pods -n production -l app=checkout -o wideT+3:角色
| 角色 | 职责 |
|---|---|
| Commander | 决策回滚/熔断 |
| Comms | Slack #incidents 状态 |
| Ops | 执行 PromQL/LogQL |
T+5:首次通报
[INC-2026-0618-0214] checkout p99 2.3s, err 3.2%. 疑 redis/连接池.
Investigating. ETA 30m update.
三、Metrics 阶段(T+5 — T+15)
3.1 两问定性
- 全实例还是单实例?
- 仅 checkout 还是下游同步恶化?
下列 PromQL 在 Grafana Explore 执行,窗口取告警前后 30 分钟,结果写入时间线。
步骤 1:基线 p99
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{service="checkout"}[5m])) by (le))
步骤 2:按 instance
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{service="checkout"}[5m])) by (le, instance))
步骤 3:按 endpoint
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{service="checkout",endpoint="/api/checkout/submit"}[5m])) by (le))
步骤 4:错误率
sum(rate(http_requests_total{service="checkout",status=~"5.."}[5m])) / sum(rate(http_requests_total{service="checkout"}[5m]))
步骤 5:Redis 客户端
histogram_quantile(0.99, sum(rate(redis_command_duration_seconds_bucket{service="checkout"}[5m])) by (le, command))
步骤 6:连接池
redis_pool_connections_waiting{service="checkout"}
步骤 7:Burn Rate 1h
slo:checkout:burnrate1h
步骤 8:下游 payment
histogram_quantile(0.99, sum(rate(http_client_duration_seconds_bucket{service="checkout",peer="payment"}[5m])) by (le))
3.2 与 SLO Recording Rules
确认 18-slo 规则已部署:
# prometheus/rules/slo-checkout.yaml(节选)
- record: slo:checkout:burnrate1h
expr: |
sum(rate(http_requests_total{service="checkout",status=~"5.."}[1h]))
/ sum(rate(http_requests_total{service="checkout"}[1h]))
/ (1 - 0.999)四、Trace 阶段(T+15 — T+25)
4.1 从 Exemplar 跳转 Tempo
Grafana → checkout RED 面板 → 点击 p99 尖峰 exemplar →
Trace ID
7f3a9c2e4b1d8f0a6e5c4b3a29180716(虚构固定值,实验可种子化)。
4.2 Span 分解(虚构 trace)
| Span | duration |
|---|---|
| checkout.submit | 2300ms |
| redis_get_cart | 850ms |
| payment.authorize | 45ms |
| inventory.reserve | 38ms |
判断:Redis span 占比高 → 下游或连接池。
4.3 采样不足时
按 10-traces 临时调 tail sampling:
# otel-collector tail_sampling 片段
policies:
- name: slow-checkout
type: latency
latency:
threshold_ms: 200等待 3–5 分钟再查 Tempo。
五、Log 阶段(T+20 — T+30)
trace_id 须在采集层注入,解析规则见 日志管道。
LogQL:trace 关联
{service="checkout"} | json | trace_id="7f3a9c2e4b1d8f0a6e5c4b3a29180716"
LogQL:连接池
{service="checkout"} |= "connection pool exhausted"
LogQL:Redis 超时
{service="checkout"} |~ "redis.*timeout|i/o timeout"
LogQL:部署标记
{service="checkout"} |= "config reload"
LogQL:结构化字段
{service="checkout"} | json | pool_wait_ms > 500
期望命中(合成日志行):
{"level":"error","service":"checkout","trace_id":"7f3a9c2e4b1d8f0a6e5c4b3a29180716","msg":"redis: connection pool exhausted","pool_wait_ms":843}若未命中:记为可观测性盲区——补 redis client span event。
六、Profile 与内核(T+30 — T+45)
6.1 Pyroscope/Parca
对照 12-profiling、16 Continuous Profiling:
# 虚构 CLI:拉取 15min CPU profile
parca query --pod=checkout-7d8f9c --range=15m --profile-type=cpu本剧本根因为 I/O 等待,CPU
火焰图不应热点在
json.Marshal——若热点在 GC 则转剧本 C。
6.2 bpftrace(排除内核/network)
bpftrace -e 'kprobe:tcp_connect { @ = hist(nsecs); } interval:s:5 { exit(); }'连接池问题通常在用户态日志已暴露;内核阶段用于剧本 B。
七、Events 与根因确认(并行 T+25 — T+40)
kubectl rollout history 显示 revision 42 在
T-45min 部署。configmap/checkout-redis 中
maxclients 100。根因:配置回滚错误导致
Redis 连接上限骤降。
八、缓解与修复
| 优先级 | 动作 | 命令 |
|---|---|---|
| P0 | 回滚 checkout-redis CM | kubectl rollout undo deployment/checkout-redis |
| P0 | 熔断可选 | 应用层 circuit breaker 开关 |
| P1 | 扩容 | kubectl scale deployment/checkout --replicas=16 |
缓解后观察 18-slo 面板 30 分钟,Burn Rate 回落。
九、主剧本完整时间线(分钟级)
| T | 动作 | 工具 | 系列参考 |
|---|---|---|---|
| 0 | Ack Page | PagerDuty | 19-alerting |
| 5 | RED | Grafana | 03-metrics |
| 12 | 按 instance PromQL | Prometheus | 06-prometheus |
| 18 | Tempo 慢 trace | Grafana | 10-traces |
| 25 | Loki trace_id | Loki | 08-logs |
| 35 | CM 变更 | kubectl | 13-events |
| 42 | 回滚 | kubectl | — |
| 75 | SLO 恢复 | Grafana | 18-slo |
十、剧本 A:MySQL 慢查询(初级)
注入:去掉索引或 chaos 注入查询延迟。Metrics 切分仍用第三节的 RED / Burn Rate,不要把 Redis 客户端延迟当成数据库证据。
Trace:mysql_query span
到秒级。Log:MySQL server has gone away
或慢查询日志。Profile:应用 CPU 正常,数据库侧
Threads_running 或 CPU
升高。根因常见为全表扫描占满连接池。存储引擎对照 MySQL InnoDB
系列。
十一、剧本 B:DNS 超时(中级)
Metric:错误率升、latency 正常。Trace:无 entry span。Log:无应用错误。用 17-kernel-tracing:
bpftrace -e 'uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo
/@[comm] = hist(nsecs);/'根因:CoreDNS 上游不可达。教训:用户态”正常”时查内核/DNS。
十二、剧本 C:JVM Full GC(中级)
周期性 p99 尖峰每 30min。16-continuous-profiling 对比 heap:
# 尖峰前 vs 尖峰时 byte[] 持有量(虚构对比表)
正常: 120MB
尖峰前: 890MB
根因:Kafka consumer max.poll.records
过大。
十三、Game Day 演练模板
## Game Day INC-DRILL-001
- 目标:30 分钟内完成主剧本 T+0 到缓解
- 角色:Commander / Ops / Comms
- 注入:redis maxclients patch
- 通过标准:Burn Rate Page < 5min ack; 根因文档化
- 失败项写入 reliability backlog与 22-chaos 对照:混沌实验应触发同等告警。
十四、事故后可观测性审计
| 问题 | 本次 | 改进 |
|---|---|---|
| Page 早于用户投诉? | 是 | — |
| trace_id 贯穿 Logs? | 是 | — |
| 连接池等待可日志化? | 部分 | 加 span event |
| 变更 45min 内关联? | 是 | Events 面板 |
十五、工程坑点
- 告警直接 Page CPU 阈值,与 SLO Burn Rate 脱节。
- 采样率在控制台改错(0 与 0.01 混淆),事故时没有慢请求样本。
- Runbook 链到过期文档,PagerDuty 一跳打不开。
- Game Day 未演练 Trace 采样上调与剧本 B/C。
- 变更审查不做
rollout history/ Events 对照,根因拖到 T+40。
十六、工具箱清单
| 阶段 | 工具 | 链接 |
|---|---|---|
| Alert | PagerDuty + AM | 19-alerting |
| Metrics | Grafana RED | 03, 06 |
| Trace | Tempo/Jaeger | 10-traces |
| Log | Loki | 08, 09 |
| Profile | Parca/Pyroscope | 12, 16 |
| Kernel | bpftrace | 17 |
| Events | K8s + 变更 | 13-events |
| 网络 | DeepFlow/Hubble | 15-network-obs |
十七、落地清单
- Game Day 是否覆盖主剧本与剧本 B/C
- Runbook URL 是否稳定、可从 PagerDuty 一跳打开
- 事故后是否输出可观测性盲区(缺 span / 缺 LogQL 字段 / 缺 kernel 证据)
- 变更审查是否纳入 Events 面板与
kubectl rollout history
十八、关键概念回顾
- 五阶递进:Alert → Metrics → Traces → Logs → Profile/Kernel。
- 缓解优先于根因;60–80% 关联变更。
- 每起事故输出可观测性盲区清单。
十九、下一步
排障能力验证后,用 自建 vs 托管 评估平台 TCO。
上一篇:中国厂商对比
下一篇:自建 vs 托管
参考资料
- Google, Site Reliability Engineering, Ch.14, O’Reilly, 2016
- PagerDuty, Incident Response Guide, https://response.pagerduty.com/
- Google, Site Reliability Workbook, Ch.5, O’Reilly
- Prometheus, Alerting Rules, https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- Grafana, Tempo TraceQL, https://grafana.com/docs/tempo/
- 本系列 01–22 各篇
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
可观测性工程
从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。全 25 篇。
【可观测性工程】数据模型:时间序列、日志、Span、Profile 的内部表达
拆解 Metrics、Logs、Traces、Profiles、Events 五大支柱在磁盘和内存中的内部数据模型。字段级对照 Prometheus TSDB block、Loki chunk、Tempo block,给出带假设的存储成本估算公式,并解释索引策略如何决定账单与查询延迟。
【可观测性工程】存储与成本:采样、下采样、冷热分层、对象存储
可观测性数据量持续增长,存储成本常超过计算成本。拆解四大支柱的成本结构、采样与保留期策略、冷热分层架构,以及带显式假设的成本估算 worksheet。
【可观测性工程】可观测性全景:Metrics、Logs、Traces、Profiles、Events 五大支柱
从控制论到云原生:拆解可观测性的五大信号支柱,对比监控与可观测性的本质区别,梳理开源/商业/SaaS 分类,以及国内互联网公司三大支柱落地现状与典型工程坑点。