适用场景
这篇文章适合已经使用 Prometheus 和 Alertmanager,但值班群里经常出现同一告警在几分钟内“触发—恢复—再次触发”的团队。典型场景包括:
- 接口错误率在阈值附近上下波动;
- 抓取偶发失败,表达式短暂返回空向量;
- 应用滚动发布后计数器重置,瞬时比率失真;
- 规则计算过慢或远端数据到达较晚,评估结果出现断点;
- 告警规则只有触发阈值,没有持续时间、恢复缓冲和数据缺失告警。
治理目标不是让告警“安静”,而是让一次真实故障对应一个可追踪的告警事件,同时把采集故障与业务故障分开表达。
现象描述
以 API 5xx 错误率告警为例,初始规则可能如下:
groups:
- name: api.rules
rules:
- alert: ApiHighErrorRate
expr: |
sum(rate(http_requests_total{job="api",status=~"5.."}[1m]))
/
sum(rate(http_requests_total{job="api"}[1m]))
> 0.05
labels:
severity: page
annotations:
summary: "API 5xx 错误率超过 5%"
这条规则看起来直观,却存在三个问题:
1m窗口容易被少量请求或短时尖峰放大;- 没有
for,第一次越过阈值就进入 firing; - 一旦表达式不再返回满足条件的序列,告警立即恢复。
如果评估周期为 30 秒,错误率依次为 4.8%、5.2%、4.9%、5.3%,值班人员可能连续收到触发和恢复通知。若其中一次抓取失败导致分子、分母都没有数据,告警还可能产生一次错误恢复。
先分清三类抖动
1. 业务值在阈值附近回摆
这是最常见的情况。流量较小、窗口过短或固定阈值不符合业务基线,都会导致表达式结果反复跨过边界。
先在 Prometheus 表格视图确认返回的标签集合,再用图形视图观察至少一个业务周期:
sum by (service) (
rate(http_requests_total{job="api",status=~"5.."}[5m])
)
/
clamp_min(
sum by (service) (rate(http_requests_total{job="api"}[5m])),
0.001
)
clamp_min 只用于避免分母为零造成无穷值,不能替代最低请求量判断。低流量服务即使只有一个失败请求,也可能得到很高的百分比。
2. 指标短暂缺失
PromQL 的即时向量在当前评估点找不到可用样本时,通常返回空向量,而不是数值 0。对于告警规则,空向量表示没有活跃告警实例,这会被解释为条件消失。
同时检查抓取状态和样本是否连续:
up{job="api"}
count_over_time(http_requests_total{job="api"}[10m])
absent_over_time(http_requests_total{job="api"}[5m])
up == 0 表示 Prometheus 已尝试抓取但失败;absent_over_time 适合判断一段时间内完全没有目标序列。二者应建立独立告警,不要把“监控看不见了”误报成“业务恢复了”。
3. 规则评估本身不稳定
规则组执行时间超过评估间隔时,后续评估会被跳过。应检查 Prometheus 自监控指标:
increase(prometheus_rule_group_iterations_missed_total[15m]) > 0
prometheus_rule_group_last_duration_seconds
/
prometheus_rule_group_interval_seconds
> 0.8
第一条确认最近是否漏评估,第二条找出执行时间已经逼近周期的规则组。若使用远端写入接收或查询链路,还要考虑数据到达延迟,必要时为规则组设置 query_offset,不要用无限增大 for 掩盖计算能力不足。
一套可落地的稳定规则
下面的规则把请求量、错误率、持续触发和恢复缓冲拆开处理:
groups:
- name: api-availability.rules
interval: 30s
rules:
- record: service:http_requests:rate5m
expr: |
sum by (service) (
rate(http_requests_total{job="api"}[5m])
)
- record: service:http_5xx:rate5m
expr: |
sum by (service) (
rate(http_requests_total{job="api",status=~"5.."}[5m])
)
- record: service:http_5xx:ratio5m
expr: |
service:http_5xx:rate5m
/
service:http_requests:rate5m
- alert: ApiHighErrorRate
expr: |
service:http_5xx:ratio5m > 0.05
and on (service)
service:http_requests:rate5m > 1
for: 5m
keep_firing_for: 10m
labels:
severity: page
team: backend
annotations:
summary: "{{ $labels.service }} 的 API 5xx 错误率持续超过 5%"
description: "当前五分钟错误率为 {{ $value | humanizePercentage }},且请求速率超过 1 req/s。"
runbook_url: "https://runbooks.example.com/api-high-error-rate"
- alert: ApiMetricsMissing
expr: absent_over_time(http_requests_total{job="api"}[5m])
for: 2m
labels:
severity: warning
team: platform
annotations:
summary: "API 请求指标已连续缺失"
description: "先检查服务发现、抓取目标、网络和指标端点,不能据此判断业务健康。"
关键字段的含义如下:
interval: 30s:该规则组每 30 秒评估一次;for: 5m:条件必须在连续评估中保持活跃 5 分钟,告警才从 pending 进入 firing;keep_firing_for: 10m:条件消失后继续保持 firing 10 分钟,减少短暂恢复或数据断点造成的恢复通知;rate(...[5m]):用五分钟窗口降低短时噪声,计数器重启也由rate处理;service:http_requests:rate5m > 1:只有流量达到最低门槛时才按错误率分页;and on (service):只按service标签匹配两侧序列,避免标签集合不一致导致结果为空。
for 和 keep_firing_for 解决的是不同问题:前者过滤短暂异常,后者过滤短暂恢复。两者都不应随意设成很长时间,否则会延迟真实告警或掩盖恢复状态。
如何选择时间窗口
时间参数应从 SLO、采集周期和故障响应目标推导,而不是照搬示例。
假设抓取周期为 30 秒,目标是在持续异常 8 分钟内通知:
- 查询窗口可先用 5 分钟,至少包含 10 个采样点;
for可从 3 分钟开始,使总检测延迟约为 3 至 8 分钟;keep_firing_for应覆盖常见的短暂数据缺口和自动恢复抖动,例如 5 至 10 分钟;- 对低流量服务增加请求量门槛,或改用固定时间窗内的失败请求数;
- 对高优先级故障保留更短窗口,但必须通过历史回放验证误报率。
不要把不同用途的时间混为一谈:查询窗口决定统计平滑程度,for 决定触发确认时间,keep_firing_for 决定恢复确认时间,Alertmanager 的 group_wait 和 repeat_interval 则控制通知聚合与重复发送。
用 promtool 做静态检查和规则测试
上线前先检查语法:
promtool check rules ./rules/api-availability.rules.yml
再用单元测试验证“短尖峰不触发、持续异常会触发”。测试文件示例:
rule_files:
- api-availability.rules.yml
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: 'service:http_requests:rate5m{service="checkout"}'
values: '10x20'
- series: 'service:http_5xx:ratio5m{service="checkout"}'
values: '0.01x2 0.08x3 0.01x15'
alert_rule_test:
- eval_time: 5m
alertname: ApiHighErrorRate
exp_alerts: []
- interval: 1m
input_series:
- series: 'service:http_requests:rate5m{service="checkout"}'
values: '10x20'
- series: 'service:http_5xx:ratio5m{service="checkout"}'
values: '0.01x2 0.08x10 0.01x8'
alert_rule_test:
- eval_time: 8m
alertname: ApiHighErrorRate
exp_alerts:
- exp_labels:
alertname: ApiHighErrorRate
service: checkout
severity: page
team: backend
exp_annotations:
summary: "checkout 的 API 5xx 错误率持续超过 5%"
description: "当前五分钟错误率为 8%,且请求速率超过 1 req/s。"
runbook_url: "https://runbooks.example.com/api-high-error-rate"
执行测试:
promtool test rules ./rules/api-availability.test.yml
eval_time 是相对测试起点的时间。第一组只有三分钟高错误率,不满足五分钟 for;第二组持续时间足够,应产生告警。实际项目还应补充指标缺失、最低流量边界、标签匹配和恢复缓冲测试。
灰度发布与现场验证
不要直接覆盖所有生产规则。推荐按以下顺序操作:
- 在测试环境运行
promtool check rules和promtool test rules; - 在生产以新告警名并行观察,暂不接入分页接收器;
- 对比新旧规则一周内的触发次数、持续时间和人工确认结果;
- 确认新规则稳定后再切换 Alertmanager 路由;
- 保留旧规则配置和回滚步骤,观察期结束后再删除。
运行时可查询 Prometheus 自动生成的告警序列:
ALERTS{alertname="ApiHighErrorRate",alertstate=~"pending|firing"}
同时记录以下指标,才能判断治理是否有效:
- 每周告警事件数和恢复事件数;
- 同一标签集合在 30 分钟内重复触发次数;
- 告警从 pending 到 firing 的耗时;
- 指标缺失告警和业务告警的重叠情况;
- 被值班人员标记为无须处理的比例。
常见错误
用 or vector(0) 粗暴补零
or vector(0) 生成的零值通常没有原业务标签,可能让缺失序列看起来像健康,还可能改变向量匹配结果。数据缺失应由单独规则表达;确需补零时,必须明确标签来源和匹配关系。
只调整 Alertmanager 的重复通知间隔
repeat_interval 只能减少同一 firing 告警的重复通知,不能阻止 Prometheus 先恢复、再创建一个新的 firing 事件。告警状态抖动应先在 PromQL、for 和 keep_firing_for 层治理。
按百分比告警却没有流量门槛
低流量时百分比极不稳定。分页告警应同时满足错误率和最低请求量;低流量服务可以改用更长窗口或失败请求数。
用超长 for 掩盖坏指标
如果根因是抓取失败、规则漏评估或标签高基数,延长 for 只会推迟发现问题。先修复采集与规则性能,再设定业务可接受的确认窗口。
预防措施
- 每条业务告警都明确“异常持续多久才需要人工介入”;
- 业务指标缺失、抓取失败和规则漏评估分别告警;
- 为比例告警设置最低样本量或最低流量门槛;
- 用记录规则固定复杂表达式,减少重复计算和标签错配;
- 把
promtool check rules与promtool test rules加入持续集成; - 监控规则执行时长、漏评估次数和每条规则产生的序列数量;
- 变更告警时保留静默观察期,用历史数据和真实事件复盘参数;
- 在告警注解中提供当前值、影响对象和运行手册地址。
总结
Prometheus 告警抖动通常不是单一参数问题,而是统计窗口、触发确认、恢复确认、指标完整性和规则执行能力共同作用的结果。可靠的治理顺序是:先确认返回的标签集合和数据连续性,再区分业务回摆、指标缺失与规则漏评估,最后用合理的查询窗口、for、keep_firing_for 和最低流量门槛稳定状态。
告警的价值不在于数量多,而在于每次触发都能清楚回答三个问题:发生了什么、影响谁、现在该做什么。
Discussion
评论