【前情提要】
Grafana 装好了,Dashboard 也导入了,CPU、内存、磁盘、网络都能看了。老周说:“现在你能看到数据了,但你不能 24 小时盯着屏幕。你得让 Prometheus 自己判断,什么时候该告警。”小林打开 Prometheus 的告警规则配置,准备写第一条规则。但他不知道,expr、for、labels、annotations,每一个都有讲究。
一、Grafana 好看,但没人看
周一早上,小林打开 Grafana,看着自己配的 Dashboard,CPU、内存、磁盘曲线一路平稳。
“周哥,你看,监控配好了。”
老周走过来,看了一眼:“好看。”
“那当然。”
“但我问你一个问题。”老周说,“你周六晚上在干嘛?”
“打游戏啊。”
“那周六晚上,你博客 CPU 飙到 90%,你知道吗?”
小林愣住了:“我……不知道。”
“对。”老周说,“Grafana 再好看,也得有人看。没人看,它就是一张画。 你不能 24 小时盯着屏幕,但你得知道服务器什么时候出问题。这就需要告警。”
“那告警谁来发?”
“Prometheus。”老周说,“你给它定规则,它自己判断。条件满足了,它就‘喊人’。”
“喊谁?”
“先喊 Alertmanager,Alertmanager 再决定怎么通知你。”老周说,“但今天,你先学怎么让 Prometheus‘判断’。”
二、告警规则长什么样?
“告警规则,就是一个 YAML 文件。”老周说,“你在里面写:什么条件下,触发什么告警。”
“给我看看。”
老周拿过小林的笔记本,写了一个例子:
groups:
- name: example
rules:
- alert: HighCpuUsage
expr: 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) > 0.8
for: 2m
labels:
severity: warning
annotations:
summary: "CPU 使用率超过 80%"
description: "实例 {{ $labels.instance }} 的 CPU 使用率已持续 2 分钟超过 80%,当前值 {{ $value }}"
“一行一行解释。”老周说。
groups:规则组。一个文件可以有多个组,每组一个name。rules:规则列表。一组可以有多个规则。alert:告警名称。HighCpuUsage,你自己起。expr:表达式。PromQL,条件满足就触发。for:持续时间。条件满足后,持续多久才真正触发。2m表示 2 分钟。labels:标签。severity: warning,表示告警级别。annotations:注释。summary和description,告警内容,支持模板变量。
“那 {{ $labels.instance }} 是啥?”
“模板变量。”老周说,“$labels 是当前告警的标签,$value 是当前值。你写 {{ $labels.instance }},告警发出来时,会替换成具体的实例名。”
小林在笔记本上画了个表:
| 字段 | 作用 |
|---|---|
alert |
告警名称 |
expr |
PromQL 表达式,条件 |
for |
持续时间,条件满足多久才触发 |
labels |
告警标签,如 severity |
annotations |
告警内容,支持模板变量 |
三、写第一条规则
“现在写第一条。”老周说,“你先建一个规则文件。”
“放哪?”
“Prometheus 配置目录下,建个 rules 文件夹。”
sudo mkdir -p /etc/prometheus/rules
sudo nano /etc/prometheus/rules/node_rules.yml
“写什么?”
“写一条‘CPU 超过 80% 持续 2 分钟’的告警。”
小林写:
groups:
- name: node_alerts
rules:
- alert: HighCpuUsage
expr: 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) > 0.8
for: 2m
labels:
severity: warning
annotations:
summary: "CPU 使用率超过 80%"
description: "CPU 使用率已持续 2 分钟超过 80%,当前值 {{ $value | humanizePercentage }}"
“humanizePercentage 是啥?”
“模板函数,把 0.85 变成 85%。”老周说,“Prometheus 的模板支持一些函数,你以后慢慢学。现在先写这个。”
“那 for: 2m,如果我写 2 呢?”
“那就会报错。”老周说,“for 必须带单位。2m 是 2 分钟,30s 是 30 秒,1h 是 1 小时。不写单位,Prometheus 不认。”
小林记下:for 必须带单位,2m、30s、1h。
四、加载规则:让 Prometheus 知道
“规则文件写好了,但 Prometheus 不知道。”老周说,“你得告诉它。”
“在哪告诉?”
“prometheus.yml 里的 rule_files。”
小林打开:
sudo nano /etc/prometheus/prometheus.yml
找到 rule_files:
rule_files:
# - "first_rules.yml"
# - "second_rules.yml"
改成:
rule_files:
- "rules/*.yml"
“rules/*.yml 表示加载 rules/ 目录下所有 .yml 文件。”老周说,“你以后加新规则,不用改 prometheus.yml,直接放 rules/ 里就行。”
“那现在怎么让 Prometheus 生效?”
“先检查规则。”
promtool check rules /etc/prometheus/rules/node_rules.yml
输出:
Checking /etc/prometheus/rules/node_rules.yml
SUCCESS: 1 rule found
“SUCCESS,规则没问题。”老周说,“现在检查整个配置。”
promtool check config /etc/prometheus/prometheus.yml
输出:
Checking /etc/prometheus/prometheus.yml
SUCCESS: 1 rule files found
“现在重启 Prometheus。”
sudo systemctl restart prometheus
sudo systemctl status prometheus
输出:
Active: active (running)
“成了。”小林说。
五、看告警状态:Pending 和 Firing
“规则加载了,但你怎么知道它在工作?”老周说,“打开 Prometheus Web UI,点 Alerts。”
小林在浏览器里打开 http://localhost:9090,点顶部菜单的 Alerts。
页面出来了,列出了所有规则:
HighCpuUsage
expr: 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) > 0.8
for: 2m
labels: severity="warning"
annotations: summary="CPU 使用率超过 80%"
state: inactive
“state: inactive,说明条件没满足,没触发。”老周说,“现在你制造一下 CPU 负载。”
“怎么制造?”
“用 stress。”老周说,“先装。”
sudo apt install -y stress
“然后跑起来。”
stress --cpu 4 --timeout 300
“--cpu 4 表示用 4 个进程压 CPU,--timeout 300 表示持续 300 秒。”老周说,“现在你刷新 Prometheus 的 Alerts 页面。”
小林刷新,看到:
HighCpuUsage
state: pending
“pending 了。”小林说。
“对。”老周说,“pending 表示条件满足了,但还没到 for 的时间。你等 2 分钟。”
两分钟后,小林刷新:
HighCpuUsage
state: firing
“firing 了。”小林兴奋地说,“触发了!”
“对。”老周说,“inactive → pending → firing,这是告警的三个状态。 inactive 是正常,pending 是条件满足但还在等 for 时间,firing 是真正触发。”
“那 firing 之后,Prometheus 会干嘛?”
“它会把这个告警推给 Alertmanager。”老周说,“Alertmanager 再决定怎么通知你。但今天我们先把 Prometheus 这边搞定。”
小林记下:
inactive:条件不满足,正常pending:条件满足,等待for时间firing:真正触发,推给 Alertmanager
六、ALERTS 指标:用 PromQL 查告警
“除了在 Alerts 页面看,你还能用 PromQL 查告警状态。”老周说。
“怎么查?”
“ALERTS 指标。”
小林在查询框里敲:
ALERTS
结果:
ALERTS{alertname="HighCpuUsage", alertstate="firing", severity="warning"} 1
“看到了吗?”老周说,“alertname 是告警名,alertstate 是状态,severity 是级别,值 1 表示正在触发。”
“那 pending 状态呢?”
“ALERTS{alertstate="pending"}。”
ALERTS{alertstate="pending"}
“你现在可以查所有告警:”
ALERTS
“或者只查触发的:”
ALERTS{alertstate="firing"}
“这有啥用?”
“你以后可以在 Grafana 里加一个 Panel,专门显示当前触发的告警。”老周说,“或者用 PromQL 做告警的告警——比如‘如果超过 5 个告警同时触发,就发一条汇总告警’。”
小林记下:
ALERTS:查询所有告警状态ALERTS{alertstate="firing"}:只看触发的ALERTS{alertstate="pending"}:只看等待中的ALERTS的值:1表示该状态激活
七、常用告警规则模板
“你学会写一条了,我再给你几条常用的。”老周说,“你直接抄,改改阈值就行。”
内存使用率超过 85%:
- alert: HighMemoryUsage
expr: 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes > 0.85
for: 2m
labels:
severity: warning
annotations:
summary: "内存使用率超过 85%"
description: "内存使用率已持续 2 分钟超过 85%,当前值 {{ $value | humanizePercentage }}"
磁盘使用率超过 85%:
- alert: HighDiskUsage
expr: 1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "根分区使用率超过 85%"
description: "根分区使用率已持续 5 分钟超过 85%,当前值 {{ $value | humanizePercentage }}"
目标离线:
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 离线"
description: "实例 {{ $labels.instance }} 已离线超过 1 分钟"
“注意最后一条。”老周说,“up == 0,这是最基础的告警。你以后不管监控什么,第一件事就是配 up == 0。 目标挂了,其他指标都查不到,你得先知道它挂了。”
“那 severity: critical 和 warning 有啥区别?”
“级别。”老周说,“critical 是严重,warning 是警告。你以后在 Alertmanager 里配路由,critical 发邮件+短信,warning 只发邮件。分级是为了让你半夜只被真正重要的事情吵醒。”
小林把三条规则抄进 node_rules.yml。
八、排错挑战:规则文件加载失败
下午,小林想加一条“Nginx 服务挂了”的告警。他写:
- alert: NginxDown
expr: up{job="nginx"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Nginx 服务离线"
保存,检查:
promtool check rules /etc/prometheus/rules/node_rules.yml
输出:
Checking /etc/prometheus/rules/node_rules.yml
FAILED: yaml: line 25: did not find expected key
“第 25 行。”小林打开文件:
- alert: HighDiskUsage
expr: 1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"} > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "根分区使用率超过 85%"
description: "根分区使用率已持续 5 分钟超过 85%,当前值 {{ $value | humanizePercentage }}"
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 离线"
description: "实例 {{ $labels.instance }} 已离线超过 1 分钟"
- alert: NginxDown
expr: up{job="nginx"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Nginx 服务离线"
“缩进看起来没问题啊。”小林说。
老周看了一眼:“你 annotations 下面 summary 和 description 的缩进,和 labels 下面的 severity 对齐了吗?”
小林仔细看:
labels:
severity: critical
annotations:
summary: "Nginx 服务离线"
“对齐了啊,都是 10 个空格。”
“那你再看 expr 那行。”
小林看:
expr: up{job="nginx"} == 0
“这行也没问题啊。”
“你往上翻,看 InstanceDown 的 description 那行。”
小林翻上去:
description: "实例 {{ $labels.instance }} 已离线超过 1 分钟"
“也没问题啊。”
老周凑近看了一眼:“你 description 里的 {{ $labels.instance }},双引号里面又有双引号?不对,没有。等等,你 summary 里的引号是中文引号还是英文引号?”
小林仔细看:
summary: "实例 {{ $labels.instance }} 离线"
“英文引号啊。”
“那你把文件发我看看。”老周说。
小林把文件内容复制出来,老周在编辑器里打开,用“显示不可见字符”一看:
summary: "实例 {{ $labels.instance }} 离线"<0xA0>
“看到了吗?”老周说,“你行尾有个不换行空格,0xA0。这是从网页复制代码时带进来的。YAML 解析器把它当成了非法字符。”
“那我怎么删?”
“你 nano 里看不到,用 sed 删。”
sed -i 's/\xc2\xa0/ /g' /etc/prometheus/rules/node_rules.yml
“或者,你重新手敲一遍,别从网页复制。”老周说,“YAML 文件从网页复制,最容易带进不可见字符。 你以后写规则,尽量手敲,或者复制后检查一下。”
小林重新手敲了一遍,再次检查:
promtool check rules /etc/prometheus/rules/node_rules.yml
输出:
SUCCESS: 4 rules found
“成了。”小林松了口气。
排错思路总结:
- 规则文件加载失败,先
promtool check rules定位行号。 - 检查缩进:同一层级必须对齐,用空格不用 Tab。
- 检查引号:中文引号和英文引号不一样,YAML 只认英文。
- 检查不可见字符:从网页复制可能带
0xA0不换行空格,用sed -i 's/\xc2\xa0/ /g'替换。 - 最稳的方法:手敲,别复制。
- 改完先
promtool check rules,再restart。
“记住,”老周说,“YAML 的坑,一半是缩进,一半是复制。 你以后写 YAML,眼睛盯着,手敲,别偷懒。”
九、练习
- 练习一:创建规则文件
/etc/prometheus/rules/node_rules.yml,写一条HighMemoryUsage告警,条件为内存使用率超过 85% 持续 2 分钟。用promtool check rules检查通过后,在prometheus.yml中配置rule_files,重启 Prometheus。 - 练习二:在 Prometheus Web UI 的
Alerts页面,确认HighMemoryUsage状态为inactive。然后用stress制造内存负载(stress --vm 2 --vm-bytes 1G --timeout 300),观察状态从inactive→pending→firing的变化。 - 练习三:用 PromQL 查询
ALERTS和ALERTS{alertstate="firing"},确认正在触发的告警。然后用up == 0的规则,停掉node_exporter,观察告警触发。
参考答案
练习一:
sudo nano /etc/prometheus/rules/node_rules.yml
内容:
groups:
- name: node_alerts
rules:
- alert: HighMemoryUsage
expr: 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes > 0.85
for: 2m
labels:
severity: warning
annotations:
summary: "内存使用率超过 85%"
description: "内存使用率已持续 2 分钟超过 85%,当前值 {{ $value | humanizePercentage }}"
promtool check rules /etc/prometheus/rules/node_rules.yml
sudo nano /etc/prometheus/prometheus.yml
rule_files 改为:
rule_files:
- "rules/*.yml"
promtool check config /etc/prometheus/prometheus.yml
sudo systemctl restart prometheus
练习二:
sudo apt install -y stress
stress --vm 2 --vm-bytes 1G --timeout 300
刷新 Prometheus Alerts 页面,观察 HighMemoryUsage 状态变化。
练习三:
ALERTS
ALERTS{alertstate="firing"}
sudo systemctl stop prometheus-node-exporter
等待 1 分钟,刷新 Alerts 页面,确认 InstanceDown 触发。
sudo systemctl start prometheus-node-exporter
十、下集预告
告警规则写好了,Prometheus 能判断“什么时候该喊人”了。但 Prometheus 只会“喊”,不会“通知”。谁负责把告警发到你邮箱?老周说:“Alertmanager。”小林打开 Alertmanager 文档,准备配置。但他不知道,route、receiver、group_by、repeat_interval,每一个都有讲究。
下一集:《Alertmanager:告警的“调度中心”》