第11篇 监控报告编写:SLO/SLI 与数据分析方法

0 阅读14分钟

经过前几篇的逐步建设,监控体系已具备完整的采集、展示与告警能力。现在还缺最后一环:如何把监控数据翻译成可对外沟通的报告。

故障处理完毕,Leader 问「这次影响有多大?」;月底复盘,需要回答「上个月的可用性达标了吗?」;新系统上线前,业务方问「这个服务的 SLO 是多少?」。这些问题单靠看 Dashboard 无法回答,需要一份结构化的监控报告。

本篇目标:讲清楚 SLO/SLI 的定义方法,给出基于现有 Prometheus 指标计算可用性的 PromQL,并提供一套可复用的监控报告模板,让监控数据最终服务于决策。


一、SLO 与 SLI:先定义「好不好」

1、基本概念

术语全称含义一句话理解
SLIService Level Indicator服务等级指标,即一个可量化的测量值你测量的东西
SLOService Level Objective服务等级目标,即 SLI 的目标阈值你承诺的值
SLAService Level Agreement服务等级协议,即未达标时的后果你承诺不兑现的代价

⚠️ 本篇只讲 SLI 与 SLO。SLA 涉及合同、赔偿等法务内容,不在监控工程师的职权范围内,但监控数据是 SLA 核算的事实依据。

关系公式:

SLI(实际值)≤ SLO(目标值)→ 服务达标
SLI(实际值)> SLO(目标值)→ 服务未达标

2、SLI 的选择原则:USE → RED → 业务

选择哪些指标作为 SLI,应遵循方法论逐层推导。

第一层:基础设施 SLI(USE 方法)

资源SLI数据来源PromQL
CPU利用率 ≤ 80%node_exporter100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
内存利用率 ≤ 80%node_exporter100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
磁盘使用率 ≤ 80%node_exporter(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"}) * 100
网络丢包率 ≤ 0.1%node_exporterrate(node_network_receive_errs_total[5m]) / rate(node_network_receive_bytes_total[5m]) * 100

第二层:应用 SLI(RED 方法)

维度SLI数据来源PromQL
Rate(流量)QPS 在预期范围内Micrometer / Nginxsum(rate(http_server_requests_seconds_count[5m])) by (job, svc)
Errors(错误)5xx 错误率 ≤ 1%Micrometersum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (job, svc) / sum(rate(http_server_requests_seconds_count[5m])) by (job, svc) * 100
Duration(延迟)P99 延迟 ≤ 500msMicrometer(需开启 histogram)histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))
Duration(延迟)成功请求的平均响应时间 ≤ 500ms(延迟折中方案)Micrometersum(rate(http_server_requests_seconds_sum{status=~"2.."}[5m])) by (job, svc) / sum(rate(http_server_requests_seconds_count{status=~"2.."}[5m])) by (job, svc) * 1000

第三层:业务 SLI(自定义埋点)

此层业务属性强,不在本篇展开。典型业务 SLI 如:订单成功率 ≥ 99.9%、支付完成耗时 ≤ 2s。

3、SLO 的设定方法

SLO 不是拍出来的,而是基于历史数据 + 业务容忍度计算出来的。

三步法:

  1. 回顾历史:拉取过去 30 天的 SLI 数据,观察 P50 / P95 / P99 分布;
  2. 设定宽松目标:以 P95 值为基准,取整作为 initial SLO;
  3. 迭代收严:稳定运行 1~2 个月后,逐步向 P90 靠拢。

示例:某服务过去 30 天 P99 延迟分布

百分位值
P50120ms
P95380ms
P99650ms
最大值2.1s

初始 SLO 可以设为 P99 ≤ 800ms(给 P99 留 20% 余量),稳定后收严到 P99 ≤ 500ms。

4、SLI → SLO 配置示例

以下是一个配置示例,涵盖主机、应用、依赖三层,但注意还未完整验证过,后续再写一篇专门介绍 SLO 的文章:

# SLO 配置示例
slos:
  # 主机层
  - name: "主机 CPU 可用性"
    sli: "avg_over_time((100 - (avg by(instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100))[1h:1m])"
    target: "< 80"
    period: "30d"
    severity: "warning"

  # 应用层
  - name: "应用 5xx 错误率"
    sli: "avg_over_time((sum(rate(http_server_requests_seconds_count{status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) * 100)[1h:1m])"
    target: "< 1"
    period: "30d"
    severity: "critical"

 # - name: "应用 P99 延迟"
 #   sli: "avg_over_time((histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)))[1h:1m])"
 #   target: "< 0.5"
 #   period: "30d"
 #   severity: "warning"
  - name: "应用平均响应时间"
    sli: "sum(rate(http_server_requests_seconds_sum{status=~\"2..\"}[5m])) by (job, svc) /  sum(rate(http_server_requests_seconds_count{status=~\"2..\"}[5m])) by (job, svc) * 1000 "
    target: "< 500"
    period: "30d"
    severity: "warning"

  # 依赖层
  - name: "外部探测可用率"
    sli: "avg_over_time(avg_over_time(probe_success[5m])[1h:1m]) * 100"
    target: ">= 99.9"
    period: "30d"
    severity: "critical"

三、用 PromQL 计算可用性

SLO 不是静态值,需要定期计算「实际 SLI 达标了多少时间」。以下给出两类常用计算方法。

1、基于时间的可用性(最常用)

# 计算过去 30 天 CPU 使用率 ≤ 80% 的占比
avg_over_time(
  (
    100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
    <= bool 80
  )[30d:1m]
) * 100
  • <= bool 80:达标返回 1,未达标返回 0
  • [30d:1m]:按每分钟采样一次
  • avg_over_time(...) * 100:达标时间百分比

示例:计算应用 5xx 错误率 ≤ 1% 的可用性

avg_over_time(
  (
    (sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m]))
     / sum(rate(http_server_requests_seconds_count[5m]))
     * 100)
    <= bool 1
  )[30d:1m]
) * 100

2、基于请求量的可用性(更精确)

# 成功请求数 / 总请求数
sum(
  increase(http_server_requests_seconds_count{status!~"5.."}[30d])
)
/
sum(
  increase(http_server_requests_seconds_count[30d])
)
* 100

3、多维度 SLI 汇总视图

在 Grafana 中可以用 Stat 面板展示多个 SLI 的当前状态,用 Threshold 设置颜色:

SLI达标值警告值严重值
CPU 利用率≤ 80%> 80%> 90%
内存可用率≥ 20%< 20%< 10%
5xx 错误率≤ 1%> 1%> 5%
P99 延迟≤ 500ms> 500ms> 1000ms

四、监控报告的三种场景与模板

监控报告的价值在于把数据翻译为结论,而非罗列曲线。以下给出三种常见场景的报告模板。

场景一:日报 / 值班交接报告

报告用途:值班人员交接时快速了解夜间发生了哪些事件、当前各系统健康状况。

报告结构:

┌──────────────────────────────────────────┐
│         【日报】监控运行报告               │
│  日期:2026-09-21 18:00 ~ 2026-09-22 18:00│
├──────────────────────────────────────────┤
│  一、整体健康状态(红/黄/绿)             │
│     ● 主机层:🟢 全部正常                │
│     ● 应用层:🟡 1 条警告(见下)        │
│     ● 依赖层:🟢 全部正常                │
├──────────────────────────────────────────┤
│  二、告警摘要                            │
│  告警数:8 条(Critical:1 / Warning:7)   │
│  已恢复:7 条 / 未恢复:1 条              │
│                                          │
│  #1 [Critical] 10.0.2.15 CPU > 85%       │
│     触发:03:12 → 恢复:03:45            │
│     根因:夜间离线任务高峰期               │
│                                          │
│  #2 [Warning] nodb 应用 5xx > 1%         │
│     触发:03:15 → 恢复:03:50            │
│     关联:CPU 过载导致的超时             │
├──────────────────────────────────────────┤
│  三、关键 SLI 当日表现                    │
│  · 主机 CPU 可用性:99.97%  ✅            │
│  · 应用 5xx 错误率:0.23% ✅             │
│  · 应用 P99 延迟:320ms  ✅              │
│  · 外部探测可用率:100%  ✅              │
├──────────────────────────────────────────┤
│  四、Top 5 资源消耗                       │
│  · CPU 最高:nodb(65%)                  │
│  · 内存最高:nodb(72%)                  │
│  · 磁盘 I/O 最高:/data 分区(30%)      │
├──────────────────────────────────────────┤
│  五、待办事项                            │
│  □ 确认离线任务是否需要优化               │
├──────────────────────────────────────────┤
│  编写人:xxx    审核人:xxx               │
└──────────────────────────────────────────┘

对应的 PromQL 填充模板:

# 当日主机 CPU 可用性(过去 24h)
avg_over_time(
  (
    100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
    <= bool 80
  )[1d:1m]
) * 100

# 当日告警总数(从 Alertmanager 拉取)
# 如果有 Alertmanager 指标暴露,如 alertmanager_alerts{state="firing"}
count(alertmanager_alerts{state="firing"})

# 当日 Top CPU 消耗实例
topk(5, avg by(instance) (rate(node_cpu_seconds_total{mode!="idle"}[1d])))

场景二:月度 SLO 报告

报告用途:向技术和业务方汇报本月服务等级达标情况,用于复盘和改进规划。

报告结构:

┌──────────────────────────────────────────┐
│        【月报】SLO 达标报告               │
│  周期:2026-09-01 ~ 2026-09-30           │
├──────────────────────────────────────────┤
│  一、SLO 达标总览                         │
│                                          │
│  SLI           目标        实际    状态   │
│  ─────────────────────────────────────  │
│  主机可用性    ≥ 99.9%    99.97%   ✅   │
│  应用错误率    ≤ 1%       0.31%    ✅   │
│  P99 延迟      ≤ 500ms    412ms    ✅   │
│  探测可用率    ≥ 99.5%    99.82%   ✅   │
│                                          │
│  ✅ 本月四项 SLO 全部达标                  │
├──────────────────────────────────────────┤
│  二、未达标事件分析(如有)               │
│  (本节本月无内容)                       │
├──────────────────────────────────────────┤
│  三、可用性趋势图(P99 延迟逐日)         │
│  → 参见 Grafana Dashboard 附图           │
│  说明:本月 P99 延迟中位 380ms,           │
│  峰值出现在 9/14 19:00(620ms),          │
│  其余时间保持在 400ms 以下。              │
├──────────────────────────────────────────┤
│  四、错误预算消耗                          │
│  错误预算 = (1 - SLO) × 总分钟数          │
│                                          │
│  例:P99 延迟 SLO=99.9%,                  │
│  当月总分钟数=43200                        │
│  错误预算 = (1 - 0.999) × 43200 = 43.2 分钟│
│  本月实际消耗 = 12 分钟(P99>500ms 时长)   │
│  剩余错误预算 = 31.2 分钟(72.2%)         │
├──────────────────────────────────────────┤
│  五、改进建议                            │
│  1. 9/14 延迟峰值待排查,考虑扩容         │
│  2. 建议下月收紧 P99 SLO 至 400ms        │
├──────────────────────────────────────────┤
│  编写人:xxx    审核人:xxx               │
└──────────────────────────────────────────┘

对应的 PromQL 填充模板:

# 月度主机可用性
avg_over_time(
  (
    100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
    <= bool 80
  )[30d:1m]
) * 100

# 月度 P99 延迟 P50 值(中位趋势)
quantile_over_time(0.5,
  avg_over_time(
    histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))
  [1d])[30d:1d]
)

# 月度 P99 延迟超出 500ms 的时间占比(错误预算消耗)
avg_over_time(
  (
    histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))
    > bool 0.5
  )[30d:1m]
) * 100

场景三:故障复盘报告

报告用途:重大故障后的根因分析与改进跟踪。

报告结构:

┌──────────────────────────────────────────┐
│       【复盘报告】故障分析                │
│  故障编号:INC-20260922-001              │
├──────────────────────────────────────────┤
│  一、故障概要                            │
│  时间:2026-09-22 14:23 ~ 14:58(35min) │
│  影响:nodb 服务 5xx 错误率飙升至 12%     │
│  影响范围:3 个下游服务出现级联超时        │
│  等级:P1                                │
├──────────────────────────────────────────┤
│  二、时间线                              │
│  14:23 告警触发:nodb 5xx 错误率 > 5%   │
│  14:25 值班确认                          │
│  14:28 定位到数据库连接池耗尽             │
│  14:35 紧急扩容连接池                     │
│  14:45 错误率开始下降                     │
│  14:58 确认恢复                          │
├──────────────────────────────────────────┤
│  三、根因分析                            │
│  · 直接原因:数据库慢查询导致连接长期占用  │
│  · 间接原因:连接池 max=20 过低           │
│  · 深层原因:慢查询未纳入日常巡检          │
├──────────────────────────────────────────┤
│  四、数据佐证(附 PromQL / 截图)         │
│  1. 错误率飙升曲线                        │
│     sum(rate(http_server_requests_seconds_count{  │
│       job="nodb", status=~"5.."}[1m]))    │
│  2. 连接池使用率                          │
│     hikaricp_connections_active /         │
│     hikaricp_connections_max * 100        │
│  3. P99 延迟飙升                          │
│     histogram_quantile(0.99, ...)         │
├──────────────────────────────────────────┤
│  五、改进措施                            │
│  #  措施                负责人  截止日期  │
│  1  增加连接池监控告警    xxx    9/25    │
│  2  慢查询巡检纳入月报    xxx    9/30    │
│  3  扩容连接池上限        xxx    9/23    │
├──────────────────────────────────────────┤
│  编写人:xxx    审核人:xxx              │
└──────────────────────────────────────────┘

五、数据分析方法:从数据到洞察

报告不止是堆数字,关键是从数据中提炼出可行动的信息。以下是三种常用的数据分析方法。

1、基线对比法(同比/环比)

对比当前周期与历史周期的指标变化,快速识别异常波动。

对比类型窗口公式(PromQL)用途
日环比当前 1h vs 昨日同时段avg_over_time(QPS[1h]) / avg_overetime(QPS[1h] offset 1d)检测小时级波动
周同比今天 vs 上周同日avg_over_time(QPS[1h]) / avg_over_time(QPS[1h] offset 1w)排除周末效应
基线偏离与过去 7 天中位对比avg_over_time(QPS[10m]) / quantile_over_time(0.5, avg_over_time(QPS[10m])[7d:10m])检测异常偏离

实战示例:

# 应用 QPS 日环比波动(> 50% 触发告警)
(
  avg_over_time(sum(rate(http_server_requests_seconds_count[5m]))[1h:1m])
  /
  avg_over_time(sum(rate(http_server_requests_seconds_count[5m] offset 1d))[1h:1m])
) * 100

2、趋势分析法

使用 deriv() 或 predict_linear() 对未来趋势进行预测,实现主动预警。

# 磁盘可用空间预测(预计满盘时间)
(time() - predict_linear(node_filesystem_avail_bytes{mountpoint="/data"}[7d], 0) / predict_linear(node_filesystem_avail_bytes{mountpoint="/data"}[7d], 86400*7))
# 内存可用量下降趋势(MB/h,连续 3h 下降 > 200MB/h 提示内存泄漏)
- deriv(node_memory_MemAvailable_bytes[3h]) / 1024 / 1024

3、关联分析法

通过多指标同屏对比,建立因果链条。

下钻链路:

总览 → 发现 5xx 错误率上升
   → 看延迟:P99 同步上升
       → 看 CPU:iowait 升高
           → 看磁盘 I/O 延迟:读写延迟 > 20ms
               → 结论:磁盘 I/O 瓶颈导致应用响应变慢

在报告中,用时间对齐的多图对比呈现这种链路:

┌───── 时间轴 ──────────────────────────┐
│  图1:5xx 错误率 curve                │ ▲ 第一个动
│  图2:P99 延迟 curve                  │ ▲ 同步上升
│  图3:CPU iowait curve                │ ▲ 同类波动
│  图4:磁盘读写延迟 curve              │ ▲ 根因
└───────────────────────────────────────┘

4、异常检测的「三个漏斗」

在报告中对告警做三层收敛,避免「一页纸全是告警,一个都看不进去」:

原始事件 → 第1层:Prometheus 规则匹配
    ↓
告警条目 → 第2层:Alertmanager 分组+抑制
    ↓
  通知   → 第3层:人工降噪(静默、合并、关闭)
    ↓
有效告警 → 写入复盘报告

报告中只呈现第 3 层过滤后的有效告警,附上抑制和被静默的数量说明。例如:

本月产生告警 83 条,经 Alertmanager 分组合并后发出 31 条通知,其中 12 条经人工确认为有效告警并纳入复盘,19 条为已知模式或重复通知。


六、自动化报告生成

手动撰写报告容易遗漏数据、难以保持格式一致。建议将报告模板化,并通过 Grafana 的 Reporting 功能或脚本定时生成。

1、Grafana Reporting(企业版功能)

Grafana 企业版支持定时导出 PDF 报告。开源版可借助 Image Renderer 插件手动导出:

# 安装 Image Renderer(见第1篇引用)
docker run -d --name=grafana-renderer \
  --network my-bridge \
  -e HTTP_HOST=renderer \
  -e ENABLE_METRICS=true \
  grafana/grafana-image-renderer:latest

2、基于 Python 的定制报告脚本

用 Python + Prometheus API 拉取数据,生成 Markdown / HTML 报告并定时推送。

伪代码架构:

import requests
import datetime

PROMETHEUS_URL = "http://localhost:9090"

def query_promql(promql):
    resp = requests.get(f"{PROMETHEUS_URL}/api/v1/query", params={"query": promql})
    return resp.json()["data"]["result"]

def generate_daily_report():
    # 拉取 SLI 数据
    cpu_avail = query_promql("avg_over_time((...CPU可用性...)[1d:1m]) * 100")
    error_rate = query_promql("avg_over_time((...5xx错误率...)[1d:1m]) * 100")

    # 填充模板
    report = f"""
# 日报 {datetime.date.today()}

## SLI 状态
- CPU 可用性:{cpu_avail[0]["value"][1]}%
- 5xx 错误率:{error_rate[0]["value"][1]}%
    """
    return report

if __name__ == "__main__":
    print(generate_daily_report())

3、利用 Alertmanager API 获取告警统计

# 获取当前 firing 的告警列表
curl -s http://localhost:9093/api/v2/alerts?state=active | jq '.'

七、SLO 持续治理:错误预算与改进循环

1、什么是错误预算

错误预算 = (1 - SLO) × 总时间,量化了「系统允许出多长时间的错」。该预算是决策的依据:

错误预算状态应对策略
富裕(> 50%)可正常发版,继续观察
紧张(20%~50%)控制变更节奏,优先稳定性优化
耗尽(< 20%)冻结新功能上线,全力修复
已超支(0%)启动故障复盘

2、在 Grafana 中展示错误预算

# 错误预算消耗率(以 P99 延迟为例)
# SLO 目标:P99 ≤ 500ms,对应 99.9% 可用性
# 当月总分钟数
(avg_over_time(
  (histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) > bool 0.5)[30d:1m]
) * 100)

# 剩余错误预算
100 - (上一条查询结果)

在 Grafana 中用 Gauge 面板展示,绿色 >50%、黄色 20-50%、红色 <20%。

3、SLO 的季度复盘节奏

每季度末
    ↓
回顾本季度所有 SLI 实际值
    ↓
与上一季度对比趋势
    ↓
判断是否需要收严或放宽 SLO
    ↓
更新 SLO 配置,纳入下一季度报告模板

八、小结

本篇核心要点:

  1. SLI 从 USE → RED → 业务逐层推导,不硬凑指标;
  2. SLO 基于历史数据的 P95 设定,先宽松再收严,逐步逼近真实能力边界;
  3. 三份报告模板覆盖日报、月报、故障复盘,每份报告都有对应的 PromQL 和 Grafana 面板方案;
  4. 数据分析四法——基线对比、趋势预测、关联分析、三层漏斗,提供从数据到洞察的方法路径;
  5. 错误预算是连接监控与决策的关键桥梁,让团队从「追着告警跑」变为「理性管控风险」。

​