news 2026/10/15 1:28:06

Prometheus告警链路详解:从规则评估到Alertmanager推送的实践与排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Prometheus告警链路详解:从规则评估到Alertmanager推送的实践与排查

做过几年监控系统的人应该都有同感:告警链路看起来简单,真到了线上出问题时,最难排查的往往不是“阈值设没设对”,而是“明明规则已经触发了,为什么消息迟迟没出来”,或者反过来,“为什么告警停了还在重复轰炸”。

所以这次就专门把 Prometheus 评估告警策略、以及如何推送告警消息到 Alertmanager 这条链路从头到尾拆一遍。内容分三块:评估的底层逻辑、推送的机制细节、以及一套能直接落到生产环境的配置示例和排查手段。适合正在搭监控体系、被告警延迟困扰、或者刚开始写 Prometheus 规则的运维和开发同学。

1. 先搞清楚一套告警策略的整体链路

1.1 评估与推送是完全分开的两段逻辑

很多新手会误以为“Prometheus 发出告警消息”是一件事,但实际上它至少横跨了两个独立组件、两段独立逻辑。

第一段是Prometheus 本身的告警规则评估。Prometheus 按照你在规则文件里声明的表达式,周期性去查询时序数据,判断某个条件当前是否满足。满足之后,规则会经历 pending、firing 这样的状态变化。这一步完全不涉及消息发送。

第二段是Prometheus 向 Alertmanager 推送。当一个告警进入活跃状态后,Prometheus 会把这条告警以 HTTP 请求的形式推送到 Alertmanager 的 API 接口。推送动作本身也很单纯,就是把带标签、带时间信息的告警对象 POST 过去。

真正的“发邮件、发企微、打电话、钉钉通知”这类行为,发生在 Alertmanager 这一侧,由它的路由树决定把告警发给哪个 receiver,以及按什么节奏发。

理解这个分工是排障的前提。常见的问题是:Prometheus 页面里明明已经显示告警 firing,但群里的机器人没反应,这时候你要清楚去哪查——先查 Prometheus 到 Alertmanager 的推送,再查 Alertmanager 路由是否命中,而不是对着规则文件反复看。

1.2 为什么这套机制值得理解

原因很简单,线上事故的恢复时间,往往直接跟告警链路的透明程度挂钩。我见过某团队告警延迟了将近十分钟,最后发现不是规则写得慢,而是规则的评估周期是 5 分钟,for 子句又设了 10 分钟,两者叠加导致告警发现被拉长到十五分钟以上。

还有一次某项目线上出现告警风暴,上百条消息刷屏,根因是 Alertmanager 的分组配置没写,每一条独立实例告警都单独发一条通知。这些都不是 Prometheus“失灵”,而是你还没把评估和推送这两个环节的节奏设计好。

所以这篇文章会尽量把每个参数背后的逻辑讲明白,而不是简单给一份配置让读者复制。知道你设了for: 5m意味着什么、知道repeat_interval控制的是什么,你才能调出适合自己业务的告警策略。

2. 评估:Prometheus 如何判定一条告警真的发生

2.1 规则加载与周期调度的运行模型

Prometheus 的告警规则并不是“实时监听”数据流,而是走一个固定的周期调度模型。

你先在prometheus.yml里通过rule_files指定规则文件路径,Prometheus 启动时会加载这些文件并解析出规则组(rule group)。配置方式大概是这样的:

rule_files: - /etc/prometheus/rules/*.yml

每个规则组里可以配置自己的评估间隔,比如:

groups: - name: example-rules interval: 30s rules: - alert: HighErrorRate expr: ...

如果规则组没有显式写interval,就会使用全局配置里的evaluation_interval。这个字段在 Prometheus 主配置里,默认是 1 分钟。

Prometheus 内部会对每个规则组启动独立的评估循环。每隔一个 interval,它就在同一个评估时间点上,对该组内所有规则执行 PromQL 查询。需要注意的是,这不是流式计算,而是“每 30 秒看一次当前状态”的采样式判断。

这一个机制直接决定了告警的最小发现延迟。假如你的数据要经过 1 分钟的采集周期落入 TSDB,而你的评估间隔又是 1 分钟,那么从指标异常到规则第一次发现条件满足,中间会有一个比较明显的窗口。如果业务对告警实时性要求很高,建议把评估间隔缩短到 15 秒或 30 秒,但也要注意评估本身有开销,间隔太短会加大查询压力。

2.2 PromQL 表达式怎么才算“满足”

规则里最关键的是expr字段,它是一条 PromQL 查询表达式。Prometheus 每个评估周期会执行这条查询,并根据返回结果来判断告警是否激活。

官方文档里的说法是:如果查询结果返回的瞬时向量至少包含一个样本,并且样本的值不为 0,那么这个规则就被判定为满足。

这句话里有个容易忽略的点:Prometheus 并不是把表达式当布尔判断来用,而是看有没有“非零样本”返回。所以下面几种写法效果是等价的:

expr: up == 0 expr: up < 1 expr: up != 1

只要查询结果中出现了一条满足条件的序列,该规则就进入活跃状态。反过来,如果查询结果为空向量,或者所有样本值都是 0,规则就不满足。

这里有个非常实际的问题:当某个实例彻底挂了、它的指标序列消失之后,up == 0这种表达式因为查不到任何样本,反而会认为“条件不满足”,从而无法触发告警。这是用 Prometheus 做故障监控时最经典的坑之一。解决办法是用absent或absent_over_time这类专门检测“序列不存在”的函数。

比如你想监控某个关键任务的指标是否消失,可以写成:

expr: absent(process_alive{job="my-job"})

这个表达式在process_alive序列不存在时返回 1,存在时返回空向量,逻辑刚好和常规阈值告警相反。

2.3 for 子句与告警状态机:pending 到 firing 的转变

Prometheus 告警规则里有一个非常重要但经常被轻视的字段:for。它表示“该条件必须持续满足多长时间,才真正对外发出告警”。

为什么需要for?最直接的原因是防止抖动误报。监控指标天然有毛刺,网络偶发延迟、GC 停顿、容器调度抖动,都可能导致阈值瞬间被突破。如果一碰到阈值就立刻告警,值班同学会被无关消息淹没,最终反而忽略真实事故。

for子句的作用可以类比成:不是温度计刚碰到 100 度就叫消防,而是持续加热 5 分钟确认是真起火才报警。

Prometheus 的告警状态机也跟着for走:

  • inactive:条件不满足,一切正常。
  • pending:条件已经满足,但还没持续够for指定的时长,此时界面里能看到,但还不会推送给 Alertmanager 做正式通知。
  • firing:条件持续满足超过for时长,告警正式激活,会推送到 Alertmanager。

比如这条规则:

- alert: CPUUsageHigh expr: cpu_usage_percent > 90 for: 5m

当 CPU 超过 90% 后,Prometheus 不会立刻让它 firing,而是记下开始满足的时间。如果下一次评估(比如 30 秒后)条件仍然成立,就继续累积时间;一旦哪次评估发现 CPU 掉回 90% 以下,计时就会重置,回到 inactive 状态。只有连续 5 分钟内每次评估都满足,才进入 firing。

需要注意的是,for的计时严格依赖评估精度。如果评估间隔是 30 秒,for: 5m实际是最多会经过 10 个评估周期才能转为 firing。这是完全正常的,理解了这一点,就不会在排查时觉得“Prometheus 好像卡住了”。

2.4 一个具体案例:从阈值触发到恢复的完整时间线

拿一个实际场景推演一下,假设规则如下:

groups: - name: app.rules interval: 30s rules: - alert: HTTPErrorRateHigh expr: | sum by (instance) ( rate(http_errors_total{code="500"}[5m]) ) > 50 for: 2m

业务在 10:00:00 开始出现大量 500 错误,rate计算出的每秒错误数超过 50。Prometheus 的下一次评估可能是 10:00:30,此时条件满足,告警进入 pending 状态。

接下来 10:01:00、10:01:30、10:02:00 三次评估条件都继续满足,到 10:02:30 左右,持续时长满 2 分钟,告警转为 firing,Prometheus 才会把它推送给 Alertmanager。

假设业务在 10:03:00 恢复,错误率掉回 50 以下,那么 10:03:30 的评估会发现问题已恢复,Prometheus 会把这条告警标记为 resolved,并推送给 Alertmanager。如果配置了恢复通知,群里就会收到“告警已恢复”的消息。

这套时间线逻辑极其重要。很多人调整告警延迟时只盯着阈值,却忘了真正的延迟是由 “评估间隔 + for 时长 + 推送频率 + Alertmanager 分组等待时间” 叠出来的。看到这条链路之后,你就能在配置时统一规划这几个参数,而不是等出问题后再手忙脚乱。

3. 推送到 Alertmanager 的那一跳

3.1 配置入口与连接模型

当告警进入 firing 状态后,Prometheus 需要把告警对象交给 Alertmanager。这一步的配置在 Prometheus 主配置的alerting段里:

alerting: alertmanagers: - static_configs: - targets: - alertmanager:9093

这里说明了两件事:一是 Prometheus 会主动连接 Alertmanager,而不是 Alertmanager 来拉取;二是你可以配置多个 Alertmanager 地址,Prometheus 会向所有地址都发送同一份告警,以支持高可用部署。

实际场景中,有些团队把 Alertmanager 部署在内网其它机器,或者前面挂了负载均衡,这时需要把targets改成对应的地址。如果 Alertmanager 暴露了/api/v2/alerts接口,Prometheus 推送时的默认 API 版本是 v2,一般不需要额外指定。如果你的 Alertmanager 版本比较老,可能需要在alerting.alertmanagers下加一句:

api_version: v1

不过新版本基本都不需要了。注意还有一个容易被忽视的能力:alerting.alertmanagers下同样支持relabel_configs。你可以在这里对出站的告警做标签裁剪,比如把内部的大标签集精简掉一部分再发送,减少 Alertmanager 侧存储压力。这属于进阶玩法,初期不用刻意用。

3.2 推送内容与重复告警问题

Prometheus 推送告警时,每个告警对象包含的基本字段有:labels、annotations、startsAt、endsAt、generatorURL。其中generatorURL是 Prometheus 自动生成的,指向该规则在 Prometheus UI 里的查询页面,点开就能看到当时的表达式和图表,调试时非常有用。

推送协议是 HTTP POST,路径是/api/v2/alerts,消息体是 JSON 数组。这也是为什么你可以在排查时用命令手动模拟推送,验证 Alertmanager 是否正常配置。比如:

curl -H "Content-Type: application/json" \ -d '[{"labels":{"alertname":"TestAlert"},"annotations":{"summary":"test"},"startsAt":"2024-01-01T00:00:00Z","endsAt":"2024-01-01T01:00:00Z"}]' \ http://localhost:9093/api/v2/alerts

接下来聊重复告警的问题。很多人以为 Prometheus 只在告警第一次触发时推送一次,实际上 Prometheus 会在每次评估后,把状态变化的告警同步给 Alertmanager。也就是说,只要告警还处于活跃状态,Prometheus 就会在后续评估中持续推送同一条告警,直到它恢复。

这种设计看起来有点“笨”,但它保证了消息不丢失。Prometheus 与 Alertmanager 之间的网络抖动、接收方短暂不可用,都不会让告警永远丢失——下一个评估周期会再次推送当前状态。

当然,正因为 Prometheus 会重复推送,Alertmanager 内部必须做去重和分组。不然每次评估推送一次,每个评估周期都发一条群消息,值班群肯定要炸。去重和分组的逻辑在 Alertmanager 配置里,我们下一节展开。

3.3 可调参数与性能边界

Prometheus 推送告警时有几个可调参数值得了解。

timeout表示单次推送到 Alertmanager 的超时时间,默认是 10 秒。如果 Alertmanager 响应慢,这个值可以适当调大,但没必要太大,因为 Prometheus 的告警推送不是同步阻塞的关键路径,超时报错后,下个周期还会继续推送。

另外一个隐藏指标是 Prometheus 内部会按告警数量维护一个发送队列,可以通过这几个指标观察健康度:

  • prometheus_notifications_queue_length:当前队列长度。
  • prometheus_notifications_queue_capacity:队列容量。
  • prometheus_notifications_failed_total:推送失败累计次数。
  • prometheus_notifications_dropped_total:因队列溢出丢弃的告警数量。

如果看到prometheus_notifications_dropped_total持续增长,说明告警数量已经超过 Prometheus 的发送能力,要么减少告警数量,要么调整队列参数,要么增加 Alertmanager 消费能力。这个问题在告警规则写得特别多、或者每个规则产生大量独立告警的项目里经常出现,值得在监控大盘里带上这些指标。

4. 实操:一套可以直接抄走的配置

4.1 告警规则的完整写法

写告警规则时我建议从一开始就规范三个意识:规则分组清晰、labels 用于身份标识、annotations 用于人类阅读。

一个完整的规则文件大概是这样的:

groups: - name: web-availability interval: 30s rules: - alert: WebDown expr: up{job="web"} == 0 for: 1m labels: severity: critical team: web annotations: summary: "Web 服务不可用" description: "实例 {{ $labels.instance }} 已经持续 1 分钟无法采集到指标,请立即检查。" - name: web-performance interval: 30s rules: - alert: WebHighLatency expr: | histogram_quantile(0.95, sum by (le, instance) ( rate(http_request_duration_seconds_bucket{job="web"}[5m]) )) > 2 for: 5m labels: severity: warning team: web annotations: summary: "Web P95 延迟超过 2 秒" description: "实例 {{ $labels.instance }} 当前 P95 延迟为 {{ $value }} 秒。"

注意几个细节。

labels的作用是给告警打上标识,它会跟随告警一起进入 Alertmanager,路由匹配、静默、抑制都是基于labels来的。所以severity、team这类字段要稳定,不要用时间、实例名这种会变的标签做身份标识。

annotations是给人看的信息,里面的{{ $labels.instance }}、{{ $value }}是 Prometheus 模板语法,会在推送前渲染成具体内容。description里尽量写清楚“什么实例、什么指标、当前值是多少、已经持续多久”,这样收到通知的人不需要再打开 Grafana 就能判断处理优先级。

不建议把所有的规则都塞进一个大文件。规则多了以后,Prometheus 是按组调度的,合理的做法是按业务模块拆分规则组,把有依赖关系的规则放在同一组,不相关的拆开,让评估节奏互相独立。

4.2 Alertmanager 路由、分组与通知配置

Alertmanager 收到 Prometheus 推送来的告警后,第一件事是把它丢进路由树,由配置一步步判断要交给哪个 receiver。

一个最简但完整可用的配置:

route: receiver: default group_by: ["alertname", "team"] group_wait: 10s group_interval: 2m repeat_interval: 4h routes: - matchers: - severity = critical receiver: page-receiver continue: false receivers: - name: default webhook_configs: - url: http://webhook-service/webhook/send send_resolved: true - name: page-receiver webhook_configs: - url: http://webhook-service/webhook/page send_resolved: true

group_by决定了哪些告警会被合并成一条通知发送。上面的配置表示按alertname和team分组,这意味着同一团队的同名告警会合并成一个条目,不会每个实例单独轰炸一遍。不分组的话,一旦有 20 台机器同时挂了,页面就会出现 20 条独立告警。

group_wait是分组后等待多长时间再发送第一条通知。它的作用是收集同一组内短时间内到达的告警,避免“10:00:00 报一台机器,10:00:05 又报另一台”造成连续刷屏。一般设 10 到 30 秒比较合理。

group_interval控制同一组告警在状态没有变化时,至少隔多久再发一次。已经有告警在通知列表里,后续又有新的同组告警进来,不会马上发,要等这个时间。注意它和repeat_interval的区别:repeat_interval是“同一条告警如果一直没恢复,隔多久重新提醒一次”,一般设 4 小时甚至更长,防止已成事实的故障反复打扰。

routes里的matchers就是告警标签匹配规则,按severity = critical走紧急通道,其余走默认通道。continue: false表示匹配上后不再继续往下层匹配,逻辑比较清晰。

4.3 让数据尽量好排查:annotations 与标签设计

配置 Alertmanager 只是第一步,真正让这套系统“好用”的,是提前把告警数据设计得便于排查。

我的习惯是每个告警规则的annotations默认包含这样几项:

  • summary:一句话说明是什么问题。
  • description:哪些实例、当前值多少、已经持续多久。
  • runbook_url:处理手册地址,可以是 wiki 链接,也可以是运维平台的工单模板。
  • dashboard_url:对应的 Grafana 面板链接,值班同学点开就能看趋势。

比如:

annotations: summary: "数据库连接数过高" description: "实例 {{ $labels.instance }} 连接数已达到 {{ $value }},超过阈值 500 持续 5 分钟。" runbook_url: "https://your-wiki.example/database-connection-overflow" dashboard_url: "https://grafana.example/d/xxx?var-instance={{ $labels.instance }}"

这些信息在告警推送后,会被模板引擎渲染成文本,发送给 webhook 或邮件。减少人肉切换上下文的时间,就是减少 MTTR。

还有一条经验:不要在labels里写动态变化的字段。有些同学习惯把当前实例 IP 放进 labels,结果一台机器变化后,同一条告警会被 Prometheus 当作新告警创建,反而触发一次新的通知和一次 resolved 通知。需要体现当前实例位置的话,放进 annotations 就够了。

5. 问题排查与避坑实录

5.1 从日志和指标定位评估异常

出现告警不触发或触发延迟时,先别急着改阈值,按下面顺序排查。

第一步看 Prometheus 日志。Prometheus 正常评估时会输出msg="Evaluating rule"之类的调试日志(需要开启 debug 级别),推送到 Alertmanager 失败时则会有明显记录,关键词是Error sending alert或Error sending alerts to alertmanager。日志方向对了,问题就解决了一半。

第二步看 Prometheus 自监控指标。重点看:

  • prometheus_rule_evaluation_duration_seconds:规则评估耗时,如果长期很高说明查询太重。
  • prometheus_rule_group_last_duration_seconds:每个规则组最近一次评估耗时。
  • prometheus_rule_group_last_evaluation_timestamp_seconds:最近一次评估时间戳,和当前时间差太多说明调度堵塞。
  • prometheus_alertmanager_notifications_failed_total:推送失败次数。

第三步直接调 Prometheus UI 的 Alerts 页面,看告警是处于 pending 还是 firing。如果一直是 pending 且时间很长,检查for子句设置,以及评估间隔是否太长。如果已经 firing 但收不到推送,问题大概率在 Alertmanager 侧。

5.2 常见问题速查表

整理几个我见过的高频问题,做成表格方便对照排查。

现象可能原因排查与解决方法
告警一直 pending 不 firingfor 子句过长,或评估间隔导致时长未连续累计缩短 for,或查看上次触发时间与当前时间差
Prometheus UI 显示已 firing 但没收到通知Alertmanager 路由没匹配上,或 webhook URL 配置错误在 Alertmanager UI 的 Alerts 里查看是否收到,测试路由匹配
告警恢复后没收到 resolved 消息send_resolved 未开启,或路由匹配不到恢复告警在 receiver 配置中开启 send_resolved,确认 resolved 告警的标签没变
群消息每隔几分钟刷一次repeat_interval 设置太短,或 group_interval 过小按业务需要把 repeat_interval 调到 2h-24h
告警间隔出现 5-10 分钟延迟评估间隔、for 时长、数据采集间隔叠加导致统一调整采集间隔、评估间隔与 for 的组合
Prometheus 日志提示推送超时Alertmanager 负载过高或网络不通检查 Alertmanager 的 /-/healthy,调整 timeout 参数
一个实例产生多条重复告警规则中用了不含聚合的表达式,每个样本都触发独立告警使用 sum by / topk 聚合减少独立告警数

5.3 几个让我印象深刻的现场案例

第一个案例是某项目配置了for: 10m的告警规则,用来监控慢查询。结果业务方反馈“数据库都卡了二十分钟才发现”,值班同学一脸无辜。排查下来发现评估间隔是 1 分钟,慢查询在 10 分钟内时断时续,导致 for 计时反复重置,告警迟迟没有进入 firing。后来把 for 改成 3 分钟,才在事故发生 5 分钟内发出通知。这个案例说明,for不是越大越好,它是用来滤抖动的,不是用来拖时间的。

第二个案例是线上一夜之间收到上千条告警消息,所有人都被轰炸。看 Alertmanager 配置发现group_by写的是空数组,意味着每条实例告警都单独成一个组,单独发一条通知。改成group_by: ["alertname"]之后,同类型问题合并成一条内容,列表里能看到全部实例,群里清净很多。分组参数在故障风暴时真的是救命配置。

第三个案例比较隐蔽。Prometheus 推送到 Alertmanager 一直报 404,日志里也看到/api/v1/alerts路径。检查后发现配置升级后 API 版本没同步,Alertmanager 已经启用 v2,而 Prometheus 还在用默认 v1。再加上某些部署里 path 前缀配置不当,就会路径不匹配。遇到这类问题,先 curl 一下 Alertmanager 的 API 路径确认版本,再回头检查配置。

写在最后的一点经验

告警系统最理想的状态是“该响的时候一定响,不该响的时候别乱响”。这两个目标经常是矛盾的,for太长会漏掉快速故障,太短又会产生噪声,所以没有放之四海皆准的参数,只有结合业务对故障容忍度的取舍。

我个人实际配置时的默认习惯是:常规业务告警的评估间隔 30 秒,for设置 2-5 分钟,Alertmanager 的group_wait设为 30 秒,repeat_interval设为 4 小时以上。对于核心链路或严重影响用户的故障,可以单独建一个规则组,评估间隔缩到 15 秒,for缩到 1 分钟。同时一定给 Prometheus 的自监控指标配上告警,否则告警链路自己挂了都没人知道,设备都还是坏的,监控却先死了。

最后再分享一个小技巧:所有告警规则的annotations里,建议都放一条可点击的日志查询链接。排查告警时,能少敲一条命令就是一条。这套链路理解了,以后再遇到“告警延迟”和“消息重复”这类问题,基本都能顺着评估和推送两个环节快速定位。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 1:28:03

MP2642GR-Z+PIC32MZ实现2S锂电池均衡的软硬件设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:28:01

光照度传感器HAL层设计:BH1750可移植驱动从零实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:26:52

树莓派4安装ROS Noetic实战指南:轻量化部署与避坑详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:26:49

STM32CubeMX配置GPIO点亮LED:从零到一掌握嵌入式开发基础

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 1:26:41

Apollo-11 AGC 实时调度与容错重启:从2MHz到现代嵌入式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华