4. 告警规则编写:PromQL基础、告警规则语法、规则分组与复用
告警规则怎么写,说白了就是两件事:查什么数据,以及怎么判断出问题。
我见过不少团队,Prometheus 搭得漂漂亮亮,但告警规则写得一塌糊涂。要么误报满天飞,要么该报的不报。嗯,今天咱们就把这块掰开揉碎了讲清楚。
4.1 PromQL 基础:你得会查数
PromQL 是 Prometheus 的查询语言。我个人习惯把它分成三类:即时查询、范围查询、聚合操作。
4.1.1 即时向量 vs 范围向量
先看个最简单的:
node_cpu_seconds_total
这条语句返回的是当前时刻所有 CPU 的瞬时值。这叫即时向量。
那如果我想看过去 5 分钟的数据呢?
node_cpu_seconds_total[5m]
这就是范围向量。注意,范围向量不能直接用于告警规则,你得配合 rate、avg_over_time 这类函数才行。
小提示: 我在项目中遇到过有人直接用
node_cpu_seconds_total[5m] 写告警,结果 Prometheus 直接报错。记住,告警规则里用的必须是即时向量。
4.1.2 常用函数
写告警规则,这几个函数你绕不开:
| 函数 | 作用 | 典型场景 |
|---|---|---|
rate() |
计算每秒增长率 | CPU 使用率、请求 QPS |
irate() |
计算瞬时增长率 | 对突发敏感的场景 |
avg_over_time() |
计算一段时间内的平均值 | 内存使用率平滑 |
max_over_time() |
取一段时间内的最大值 | 峰值告警 |
sum() |
聚合求和 | 多实例汇总 |
举个例子,计算 CPU 使用率:
100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
这条语句的意思是:先算过去 5 分钟 CPU 空闲时间的增长率,取平均值,然后用 100 减去它。说白了就是「CPU 有多忙」。
4.2 告警规则语法:怎么写一条好规则
Prometheus 的告警规则用 YAML 写。结构很简单:
groups:
- name: example
rules:
- alert: HighCPULoad
expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m
labels:
severity: critical
annotations:
summary: "CPU 使用率过高 ({{ $value }}%)"
description: "实例 {{ $labels.instance }} 的 CPU 使用率已超过 80%,持续 5 分钟。"
我来拆解一下每个字段:
- alert:告警名称,必须唯一。我习惯用驼峰命名,比如
HighCPULoad。 - expr:PromQL 表达式。返回非零值就触发告警。
- for:持续时间。比如
5m表示持续 5 分钟才触发。这个字段太重要了,能过滤掉很多毛刺。 - labels:附加标签。可以覆盖从指标继承的标签,也可以新增。
- annotations:告警信息。支持模板变量,比如
{{ $value }}是当前值,{{ $labels.instance }}是实例名。
注意: 我曾经见过有人把
for 设成 0,结果网络抖动一下,告警就炸了。建议至少设 1-3 分钟,除非你的场景对实时性要求极高。
4.3 规则分组:别把鸡蛋放一个篮子里
规则分组,说白了就是按业务或模块把规则归类。比如:
groups:
- name: node_exporter
rules:
- alert: HighCPULoad
...
- alert: HighMemoryLoad
...
- name: kubernetes
rules:
- alert: PodCrashLooping
...
- alert: NodeNotReady
...
为什么要分组?三个原因:
- 可读性:一眼就能看出哪些规则属于哪个模块。
- 评估间隔:每个组可以单独设置
interval。比如节点级别的规则可以 30 秒评估一次,业务级别的可以 1 分钟。 - 故障隔离:一个组的规则出问题,不会影响其他组。
小技巧: 我习惯把「基础设施」和「业务应用」分成两个大组。这样排查问题时,能快速定位是机器挂了还是应用挂了。
4.4 规则复用:别重复造轮子
你有没有遇到过这种情况:同样的 CPU 告警规则,要在 10 个不同的组里写一遍?
嗯,Prometheus 支持规则复用。有两种方式:
4.4.1 使用标签继承
比如你有一个通用的 CPU 告警规则:
- alert: HighCPULoad
expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
for: 5m
labels:
severity: critical
team: "{{ $labels.team }}"
只要你的指标上带了 team 标签,告警就会自动继承。这样你只需要写一条规则,就能覆盖所有团队。
4.4.2 使用模板变量
模板变量不光能用在 annotations 里,还能用在 expr 里?不行。但你可以通过 labels 传递上下文。
举个例子:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 2m
labels:
severity: warning
service: "{{ $labels.service }}"
annotations:
summary: "服务 {{ $labels.service }} 错误率超过 5%"
description: "当前错误率 {{ $value | humanizePercentage }}"
你看,service 标签是从指标里继承的,summary 和 description 里用了模板变量。这样一条规则就能监控所有服务。
避坑指南: 我曾经在模板变量里用了
{{ $labels.nonexistent }},结果告警直接不触发。Prometheus 不会报错,但变量会变成空字符串。所以写模板时,一定要确认标签存在。
4.5 知识体系图
下面这张图总结了本章的核心逻辑:
4.6 实战建议
最后,分享几个我在实战中总结的经验:
- 先写 PromQL,再写规则:我习惯先在 Prometheus 的 Graph 页面里调试好表达式,确认数据没问题,再复制到规则文件里。
- for 字段别省:除非你的场景对实时性要求极高,否则建议至少设 1-3 分钟。能过滤掉 90% 的误报。
- 分组别太细也别太粗:我见过有人一个组里放 100 条规则,也见过一个规则一个组。建议按模块分,每个组 5-15 条规则比较合适。
- 模板变量用起来:一条通用规则 + 模板变量,能覆盖几十个服务。省时省力,还容易维护。
核心要点: 告警规则不是写出来就完事了。你需要持续调优,根据实际告警情况调整阈值、持续时间和分组方式。我自己的经验是,每两周 review 一次告警规则,把误报的调低,把漏报的补上。