资讯动态

Prometheus告警规则从入门到实战:链路、语法与排障指南

发布时间:2026/10/16 6:22:39 来源:尧图企业网站定制
前阵子同事半夜给我发消息说某个节点磁盘快满了Prometheus却一声不吭直到业务方反馈才被发现。我登上去看了那条规则问题一眼就出来了for设成了 5 分钟表达式里又没过滤系统保留分区规则列表里一直是pending根本没机会切到firing。这种场景在接触 Prometheus 告警规则配置的同学里太常见了——规则看起来无非几行 YAML可真要让它在生产环境里扛住风雨背后的链路、语法、阈值设计得完整吃透才行。这篇内容适合两类人一类是刚上手 Prometheus想给基础监控和业务指标配上告警的初学者另一类是已经写了规则但总遇到“该报没报、不该报乱报”的运维同行。我会从告警链路讲起再拆规则语法给可直接抄的实战规则最后聊我踩过的坑和排查思路目标是让你看完能独立写出一套像样的告警规则。1. 告警规则配置前先把整套运行链路想清楚很多人一上来就打开告警规则文档盯着expr怎么写这其实跳过了最重要的基础认知。Prometheus 告警不是“规则文件一拍通知就自己飞出去”它是一条完整链路规则只负责其中“判断”这一环。1.1 从指标到通知告警要经过哪些环节一条指标数据从产生到变成你手机里的告警通知大致是这条路径采集端把指标抓进 Prometheus 的 TSDB 存储引擎这一步由scrape_interval控制。Prometheus 每隔一个评估周期evaluation_interval对规则文件里的表达式做一次求值。表达式结果为 true 时规则产生一条告警状态先进入pending持续满足for设置的时间窗口后才转为firing。firing的告警转入 Alertmanager由它的路由、分组、抑制、接收器配置决定最终用邮件、Webhook 还是企业微信等方式通知到人。这套链路里告警规则文件管的是“什么情况算异常、要报警”。判断错了后面的 Alertmanager 配置得再精巧也没用反过来规则写得再好接收端路由不给力告警照样淹没在群里没人响应。所以你配置规则时心里得始终有这条链路的全景图改规则时才能定位问题到底出在“判断”还是“送达”环节。这里想强调一个细节很多人以为改完规则文件重载一下 Prometheus 配置就万事大吉。实际上规则文件的校验、加载机制和主配置不完全一样第 4 部分我会重点讲怎么避免“改了没生效”的假象。1.2 规则文件怎么被加载rule_files 与评估周期规则文件通过主配置文件里的rule_files字段引入典型写法如下global: scrape_interval: 15s evaluation_interval: 15s rule_files: - /etc/prometheus/rules/*.yml这里的evaluation_interval指规则评估周期和scrape_interval是完全独立的。采集周期只管多久抓一次指标评估周期才决定多久计算一次告警表达式。你完全可以把采集周期设为 15s评估周期设成 1 分钟这在高基数场景下能明显降低规则计算带来的 CPU 开销。但刚开始不建议乱改全局评估间隔保持默认就好单个规则组内部还可以用interval字段覆盖全局值灵活性足够。规则文件本身支持多份与通配符。我建议生产环境按职责拆分node-rules.yml物理机、虚机的基础资源告警。kubernetes-rules.yml集群与容器相关告警。business-rules.yml业务指标、接口成功率、SLO 相关告警。一份几千行的规则文件不是不能跑但要定位某个告警的归属团队、调某个阈值时就非常痛苦。拆分之后还能配合不同的评估间隔比如业务规则需要秒级响应就可以把该 group 的interval设短些。2. 规则文件语法拆解每个字段到底在控制什么规则文件是 YAML 格式核心对象是groups和rules。看起来简单但细节不少。我按一个最小可用的规则块讲起再逐个字段拆开。2.1 从 group 到 alert规则块的骨架一个最基础的告警规则文件长这样groups: - name: node_basic_alerts interval: 30s rules: - alert: NodeCPUHigh expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100) 85 for: 10m labels: severity: critical team: platform annotations: summary: 节点 {{ $labels.instance }} CPU 使用率过高 description: 节点 CPU 使用率已达 {{ $value | humanizePercentage }}持续超过 10 分钟我拆开说几个容易被忽略的点name规则组名称在同一 Prometheus 实例里建议全局唯一否则日志里会看到组重名告警。interval这个组的评估周期覆盖全局evaluation_interval。注意它只管评估频率不影响采集频率。alert告警规则名称同一规则组内不能重复。exprPromQL 表达式返回结果不是简单数值而是一组带标签的时序数据。只有满足条件的序列才算是“触发”状态。for表达式持续为真的时长用来消除瞬时抖动。labels给这条告警附加的标签会在告警传到 Alertmanager 后作为路由匹配依据。annotations注解信息通常用来写摘要、详细描述、操作链接。这里要特别点一下expr的类型问题。PromQL 表达式最终结果是瞬时向量还是标量对告警是否触发影响很大。例如node_load1 4里的node_load1是瞬时向量每个节点都会产生一条判断但如果表达式写成了sum(node_load1) 4就只有一个总负载序列告警只会在总体超阈值时触发节点级的异常就被吞了。这是很多“写了一条规则却不报”的根本原因。2.2 expr、for、labels、annotations 的配合逻辑for字段是我见过被用坏最多的一个。for设得太短比如 30s网络抖动、瞬时毛刺会频繁触发告警值班同学变成“狼来了”。for设得太长比如 1 小时真故障发生后你要等一小时才收到通知业务都凉透了。我的经验是基础资源类告警CPU、内存、磁盘通常 5~10 分钟比较合理业务类告警接口错误率、超时率如果想 early warning可以设 1~3 分钟决定是否切流、重启的高危动作类告警至少 15 分钟以上避免误动作。labels和annotations的定位区别要搞清楚。labels是“给机器看的”会跟随告警进入 Alertmanager用于路由分组annotations是“给人看的”负责写清楚什么故障、影响谁、该怎么处理。不要把操作指引写在labels里那只会让路由匹配异常混乱。annotations里的模板可以直接引用标签和时间序列的当前值比如{{ $labels.instance }}、{{ $value }}。$value在 Prometheus 里格式化显示可能很长比如 0.8566666667可以配合humanizePercentage这类模板函数转成 85.67%。模板函数的使用需要看具体版本老的 2.x 版本对部分函数支持不全写之前先确认版本。2.3 标签继承与写入规则规则产生的告警最终带着两组标签一组来自expr结果序列自身的标签另一组来自规则里的labels字段。这两组标签会合并。如果labels里显式写了一个与结果序列同名的标签规则块里的labels会覆盖时序自带的值。这个特性可以用来统一告警标签体系。比如我的采集器暴露的标签是host但告警路由希望按instance区分就可以在labels里显式把instance重写。不过要提醒一句不要过度在labels里添加高基数标签。什么是高基数标签就是取值非常多、变化非常频繁的标签比如用户 ID、订单号、Pod 名称。如果你把 Pod 名写进告警labels每个滚动更新后的 Pod 都会产生一条独立告警Alertmanager 的分组去重基本失效告警风暴会直接刷屏。正确的做法是把 Pod 名放进annotations的描述里labels只保留severity、team、namespace这类稳定信息。3. 从基础到生产几个可以直接上手的规则配置场景光懂语法还不够规则写得是否符合实际场景直接决定监控价值。下面三组规则我从节点、容器、业务三个层面各挑几个典型场景代码块都是我实际用过的写法你可以参考后结合自己环境调整。3.1 节点层CPU、内存、磁盘、探活节点层是所有告警的底线规则相对固定。CPU 高用率规则我通常这么写- alert: NodeCPUHigh expr: | 100 - (avg by (instance) (rate(node_cpu_seconds_total{modeidle}[5m])) * 100) 85 for: 10m labels: severity: warning annotations: summary: 节点 {{ $labels.instance }} CPU 使用率超 85% description: 节点 CPU 使用率当前 {{ $value | humanizePercentage }}持续 10 分钟这里用了rate(..., [5m])代表取最近 5 分钟的 CPU 空闲时间变化速率再换算成使用率。为什么不用node_cpu_seconds_total直接相减因为 counter 类型指标是累积的必须用rate或irate计算每秒变化率拿原始值比较完全没意义。内存告警要注意别直接拿node_memory_MemTotal_bytes和node_memory_MemFree_bytes做减法。Linux 下MemFree只是真正空闲的内存不包含缓存部分通常观察可用内存要用node_memory_MemAvailable_bytes。- alert: NodeMemoryLow expr: | (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 90 for: 10m labels: severity: critical annotations: summary: 节点 {{ $labels.instance }} 可用内存不足 description: 节点内存使用率已达 {{ $value | humanizePercentage }}磁盘告警最经典的坑是忽略只读挂载点和临时文件系统。一个常见的过滤写法是- alert: NodeDiskUsageHigh expr: | (node_filesystem_size_bytes{fstype!~tmpfs|overlay|squashfs} - node_filesystem_free_bytes{fstype!~tmpfs|overlay|squashfs}) / node_filesystem_size_bytes{fstype!~tmpfs|overlay|squashfs} 0.85 for: 30m labels: severity: warning annotations: summary: 节点 {{ $labels.instance }} 挂载点 {{ $labels.mountpoint }} 磁盘使用率过高这里的关键是fstype正则排除。tmpfs和overlay这类文件系统通常只占内存或临时用途把它们的容量算进磁盘使用率会一直报假警。for设成 30 分钟是我个人偏好磁盘和 CPU 不同容量积压是个缓慢过程不需要秒级响应反而要排除掉日志清理脚本短暂触发的高峰。节点探活我习惯用对端上报时效来判断而不是依赖 Ping- alert: NodeExporterDown expr: up{jobnode-exporter} 0 for: 2m labels: severity: criticalup指标是 Prometheus 每轮抓取后自动生成的值为 1 表示抓取正常0 表示抓取失败。这个规则看着简单但在多集群场景下很容易因为 job 名不统一写错。建议先把up输出查一遍确认每个 target 的job标签值。3.2 容器与 Kubernetes 层Kubernetes 场景下需要关注的不只是节点资源还有 Pod 的重启、Pending、集群组件状态。重启告警是很多人最先需要的- alert: PodFrequentRestart expr: | increase(kube_pod_container_status_restarts_total[15m]) 3 for: 5m labels: severity: warning annotations: summary: Pod 频繁重启: {{ $labels.namespace }}/{{ $labels.pod }}increase(...,[15m])取的是 15 分钟内重启次数的增量比直接看总重启数更准确。注意如果目标 Pod 在重启期间被重建restarts_total的标签可能变化导致增量永远看不到这就需要配合 Pod 名称聚合。实际生产里我更倾向于基于容器重启次数设置告警而不是基于 Pod 名称因为 Kubernetes 本身会重建 Pod按 Pod 名盯容易漏。改用- alert: ContainerRestartHigh expr: | sum by (namespace, pod) ( increase(kube_pod_container_status_restarts_total[30m]) ) 5 for: 10m节点不可达和集群组件状况也很重要。集群组件方面如果采集了 kube-controller-manager、kube-scheduler 等服务的 metrics最直接的告警是- alert: KubeControllerManagerDown expr: up{jobkube-controller-manager} 0 for: 5m节点 NotReady 的告警一般用事件或者自定义指标更通用的做法是结合kube_node_status_condition- alert: NodeNotReady expr: | kube_node_status_condition{conditionReady,statustrue} 0 for: 10m labels: severity: critical这条规则的风险在于如果采集链路出现问题kube_node_status_condition也可能消失表达式结果变成“无数据”告警反而不会触发。Prometheus 里“无数据”和“值为 0”是两回事前者不会产生任何告警。要防这种情况就得靠absent()之类的手法或者依赖up指标。生产环境里我会同时配一条kube_node_status_condition指标消失的告警但表达式设计要很小心不然节点短暂失联会让告警刷屏。3.3 业务层黄金信号与 SLO 导向业务层监控是告警规则里最有价值也最难写对的部分。黄金信号通常包括请求量、错误率、延迟、饱和度。以前端 HTTP 服务为例- alert: HttpErrorRateHigh expr: | sum by (service, route) ( rate(http_requests_total{code~5..}[5m]) ) / sum by (service, route) ( rate(http_requests_total[5m]) ) 0.05 for: 5m labels: severity: critical annotations: summary: 服务 {{ $labels.service }} 5xx 错误率超 5%错误率这种派生指标最怕的是分母为零。当某条路由在 5 分钟内完全没有请求量rate(http_requests_total[5m])也是 0除法结果要么是 NaN要么无限大Prometheus 对这类结果的告警计算行为并不直观很容易产生误报。我在生产里通常会加一个最低请求量的过滤条件expr: | ( sum by (service) (rate(http_requests_total{code~5..}[5m])) ) / ( sum by (service) (rate(http_requests_total[5m])) 10 ) 0.05 10这个条件写在分母里作用是当 5 分钟请求量小于等于 10 时整个序列变成空告警就不触发。这避免了冷门接口被“0 除”误报的尴尬。延迟类告警需要区分聚合方式。对 Web 服务一般用直方图指标配合histogram_quantile比如 P99 延迟- alert: HttpP99LatencyHigh expr: | histogram_quantile(0.99, sum by (service, le) (rate(http_request_duration_seconds_bucket[5m]))) 0.5 for: 10m labels: severity: warning annotations: summary: 服务 {{ $labels.service }} P99 延迟超过 500mshistogram_quantile对数据量的要求比较高如果指标采集频率过低或标签维度太碎计算出的分位数波动很大。这类规则我会把for适当调长比如 10~15 分钟否则它本身就会变成告警风暴的源头。4. 排障实录规则不触发、老误报问题大多出在这里写完了规则不等于规则能正常工作。我在使用过程中积累了一些典型问题的排查思路整理成方法分享出来。4.1 用 promtool 校验先把语法这关过牢Prometheus 自带的promtool是规则配置的贴身工具我每次都先用它做静态检查。检查命令很简单promtool check rules /etc/prometheus/rules/*.yml有语法错误会直接输出行号。在正式版本里我强烈建议把这条命令加进 CI/CD 中规则文件和主配置一起走发布流程。另一个强大能力是规则单元测试。promtool test rules可以让你喂一组输入时序数据验证某个表达式是否在预期时刻触发。我第一次用的时候非常惊艳业务指标马上要迭代我先把新老规则放进测试文件里跑确认阈值变化不会误伤正常请求。一个典型的 test 文件结构rule_files: - business-rules.yml evaluation_interval: 1m tests: - name: HttpErrorRateHigh 测试 interval: 1m input_series: - series: http_requests_total{serviceapi,code500} values: 0 5 15 30 - series: http_requests_total{serviceapi,code200} values: 0 100 100 100 alert_rule_test: - eval_time: 4m alertname: HttpErrorRateHigh exp_alerts: - exp_labels: service: api exp_annotations: summary: 服务 api 5xx 错误率超 5%这套测试机制能大幅减少“写完规则自我感觉良好上线后被下一波故障打脸”的情况。4.2 状态卡在 pending始终不 firing告警一直 pending通常有两类原因。第一类是for时间没到这是设计使然。但如果你发现半小时过去了还在 pending就要怀疑表达式是否持续为真。第二类原因是 PromQL 表达式本身有数据缺口。举个常见例子表达式里用了(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)当某个节点 exporter 挂了相关序列消失表达式在这个节点上就没有输出自然不会有告警。这种“被静默的故障”比明确的故障更危险排查时我会先打开 Prometheus 的 Graph 页面用和规则中相同的表达式手动跑一遍看是不是每个目标节点都有数值输出。还有一类原因是标签匹配问题。规则里写了jobnode-exporter但你的采集配置里 job 名可能叫node或node_exporter。在 Prometheus 的 Status-Targets 页面能看到实际 job 标签对不上就改规则或者采集配置我见过太多团队在 job 名上耗了大半天。卡 pending 还有一个隐含问题规则是pending时不会传给 Alertmanager。如果你依赖 Alertmanager 的静默、抑制功能提前生效就要把静默的匹配标签写准确因为只有firing的告警才会进入路由。4.3 高基数标签与告警风暴告警风暴的本质是标签基数控制失败。一个常见的错误是把 Pod 名称写到labels里Kubernetes 中 Pod 每次滚动都会生成新名字告警列表里于是堆满了不同 Pod 名的同类告警Alertmanager 再怎么做分组都没意义。我处理这类问题的经验是告警labels只保留稳定标签severity、namespace、team、service最多再加instance。动态信息全放annotations人看得见机器不拿它做路由维度。分组聚合在表达式里尽量收敛用sum by (...)而不是裸序列。真遇到告警风暴可以先用 Alertmanager 的静默功能把持续轰炸的告警按标签批量静默然后在规则层面重构。静默只能救急规则设计不修风暴还会回来。4.4 阈值选择与抖动抑制阈值选错是最没技术含量却最影响体验的问题。我见过两种极端一种是阈值设得太低CPU 超过 50% 就告警业务正常波动直接刷爆值班群另一种是设得太高99% 才告警结果告警来的时候已经不可收拾。阈值设计要参考历史数据。Prometheus Graph 页面选一个较长区间把指标走势拉出来看正常波动的上缘把阈值放在上缘加 15%~30% 的余量。比如 CPU 平时稳定在 30%~50%那 70%~75% 作为 warning 比较合理如果平时就在 70%~80% 跑你去报 85% 基本等于摆设。抖动抑制除了for之外还可以用rate和irate的窗口选择。irate对瞬时变化更敏感适合看毛刺rate的平滑效果更好更适合告警规则。一般规则里我全部用rate把短时抖动交给for消化。5. 与 Alertmanager 联动规则写对了告警才能真正落地规则只是把“有问题”转换成了一条条告警消息这一步做完系统里可能同时出现数十条相关告警。Alertmanager 的价值就是把这些告警整理成人能处理的通知流。5.1 路由树与分组策略Alertmanager 的配置核心是路由树。它的匹配顺序是从根节点逐层向下从第一个匹配的子节点继续匹配。一个简化配置route: receiver: default-receiver group_by: [alertname, instance] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: team: platform receiver: platform-receiver - match: severity: critical receiver: critical-receivergroup_by决定哪些告警合并成一条通知。我建议至少把alertname加进去否则几十个节点的同类告警会产生几十条通知没人受得了。group_wait是第一批告警在组内等的缓冲时间目的是收集该组内的更多告警一起发repeat_interval决定相同告警多久重复提醒一次设太短会被当成噪音。路由匹配用的就是规则里的labels。所以第 2 节强调labels设计很重要它直接决定告警峰值时哪些人会被打扰。5.2 抑制与静默减少无关通知抑制用于在父级告警触发时隐藏子级告警。比如某个节点挂了那么该节点上的所有 Pod 级告警一并触发是灾难我就用抑制规则让节点不可达时自动屏蔽该节点上的容器告警inhibit_rules: - source_matchers: - alertnameNodeDown target_matchers: - instance~.*这条规则的效果是只要NodeDown告警在firing同一instance上的其他告警都会被抑制。配置时要小心别把所有告警全抑制了否则节点故障恢复后你完全看不到其它真实问题。静默则是运维主动把某些告警定时关闭比如上线窗口期。它和规则配置不直接相关但排查告警问题时值得记住如果你发现告警突然消失了先到 Alertmanager 的 Silences 页面看看有没有人挂了静默这是经常被忽略的排查项。6. 我的实操体会写规则前要耐得住性子最后分享几个个人经验。我在生产环境维护监控告警已有较长时间发现最影响告警质量的不是 PromQL 技巧而是配置前是否花了足够时间理解业务系统和历史数据。第一个体会先花几天时间梳理指标再写规则。把要监控的 Exporter 指标列表拉出来对照业务架构图先确定哪些指标是系统稳定运行的核心。别一上来就写十几条规则那会让你在误报中迅速迷失。我一般先配 3~5 条最有把握的跑一周再逐步加。第二个体会每次告警触发之后记录它“值不值得”。值得的标准是收到这条告警时你是否做了一次有意义的人工干预。如果持续收到某条告警但每次都是看一眼就忽略要么阈值有问题要么这条规则根本不该存在。定期清理告警规则比写新规则更重要告警疲劳是真实存在的。第三个体会告警规则不是一次配置永久有效的。业务的峰值变化、集群扩容、服务重构都会让原阈值失真。我建议每个月抽时间过一遍所有 firing 过的告警历史结合当月故障记录把阈值、for、订阅关系重新校准。第四个体会宁可少告警不可乱告警。告警系统的公信力建立很难摧毁却只需要几次狼来了。宁可让高危故障准确送达也别让无关紧要的波动把人叫醒。所以我现在的原则是所有规则的初始配置都偏保守跑一段时间后根据实际准确率再放宽或收窄。希望这篇文章能让你配置 Prometheus 告警规则时少走弯路。把链路想清楚把语法吃透把阈值校准再配合 Alertmanager 做好收敛告警系统才能真正成为一个可靠的“守夜人”而不是噪音制造器。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑