一、概述
凌晨 3:17,手机屏幕亮起:"FIRING NodeMemoryUsage > 90%"。你迷迷糊糊爬起来,打开 Grafana 一看------内存确实飙到了 91%,但 2 分钟后自动回落了。服务没挂,用户没感知,你却已经醒了。
这条告警有什么问题?阈值定得太死、没有设置 for 持续时间、发给了不该在半夜收到的人。
Prometheus 告警规则不是把阈值往上一填就完事。一条好的告警规则,能让你踏踏实实睡一宿;一条烂的告警规则,能让你对告警彻底麻木。
本文从 Prometheus 告警体系出发,给出一套可以直接用在生产环境的告警规则模板,以及从"天天被叫醒"到"只收到真正有意义的告警"的优化思路。
二、Prometheus 告警体系速览
Prometheus 告警链路有三层:
指标采集(Exporter/Pushgateway)
↓
规则评估(Prometheus Server 定期计算 PromQL)
↓
告警路由(Alertmanager → 分组/抑制/静默 → 钉钉/微信/邮件/PagerDuty)
- Recording Rules(记录规则):预计算复杂查询,加速仪表盘加载
- Alerting Rules(告警规则):PromQL 返回非空结果就触发告警
- Alertmanager:负责去重、分组、抑制、静默、路由到通知渠道
本文重点在后两者的配合------规则写得好不好,决定了告警质量的上限。
三、告警规则设计三大原则
1. 原则一:告警必须可操作
每一条告警触发后,接收者必须知道该做什么。
反例:
yaml
# ❌ 坏规则:CPU 高于 80% 就告警
- alert: HighCPUUsage
expr: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
问题:80% 算高吗?要立刻处理还是等一等?是突发还是持续?没有任何上下文。
改进:
yaml
# ✅ 好规则:CPU 持续高负载,且已影响请求延迟
- alert: NodeCPUHigh
expr: |
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 10m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} CPU 使用率 > 90% 持续 10 分钟"
description: "当前值 {{ $value | humanize }}%,请检查 top 进程,必要时扩容"
加上 for: 10m 和清晰的 annotations 后,接收者一看就知道怎么回事。
2. 原则二:分级告警,不搞一刀切
不是所有告警都要半夜把人叫醒。用 severity 标签分层:
| severity | 含义 | 通知方式 | 响应时间 |
|---|---|---|---|
critical |
服务不可用,影响用户 | 电话 + 钉钉 + PagerDuty | 5 分钟 |
warning |
即将出问题,暂时不影响 | 钉钉 + 企业微信 | 30 分钟 |
info |
值得关注,不急 | 邮件/归档 | 工作时间处理 |
在 Alertmanager 中按 severity 路由到不同渠道:
yaml
routes:
- match:
severity: critical
receiver: 'pagerduty-oncall'
repeat_interval: 5m
- match:
severity: warning
receiver: 'dingtalk-ops'
repeat_interval: 30m
- match:
severity: info
receiver: 'email-daily'
repeat_interval: 24h
关键配置:repeat_interval --- critical 告警每 5 分钟重复一次直到 acknowledged,info 告警一天发一次就够了。
3. 原则三:避免告警疲劳
告警疲劳(Alert Fatigue)是所有监控团队的头号杀手。几个数字:
- 一个运维每天收到 50+ 告警 → 3 周后对告警彻底麻木
- 告警里 70% 是重复、无关或误报 → 真正有价值的告警被淹没
- 最终后果:真正出问题时没人看告警
防疲劳三板斧:
for设置合理的持续时间:瞬时波动不告警,持续异常才触发- 抑制规则(inhibition):节点宕机了就别再告诉我上面的服务不可用
- 静默窗口(silence):计划内变更提前静默,别让运维操作触发一堆告警
四、实战告警规则模板
以下规则均经过生产环境验证,可直接使用。命名遵循 {层级}_{指标}_{条件} 惯例。
1. 节点级告警
CPU 告警:
yaml
- alert: NodeCPUHigh
expr: |
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 10m
labels:
severity: warning
annotations:
summary: "节点 CPU > 90%"
description: "实例 {{ $labels.instance }} CPU 使用率 {{ $value | humanize }}%,持续 10 分钟"
内存告警 ------ 注意用 available 而非 free:
yaml
- alert: NodeMemoryLow
expr: |
(node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 10
for: 5m
labels:
severity: warning
annotations:
summary: "节点可用内存 < 10%"
description: "{{ $labels.instance }} 可用内存仅 {{ $value | humanize }}%"
很多人用 node_memory_MemFree_bytes,这是错的------Linux 会把空闲内存用于缓存,MemFree 看起来很小但 MemAvailable 其实充裕。用 MemAvailable 才是真实的可用内存。
磁盘告警 ------ 区分系统盘和数据盘:
yaml
- alert: NodeDiskFull
expr: |
(node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "根分区磁盘空间 < 10%"
description: "{{ $labels.instance }} 根分区可用 {{ $value | humanize }}%"
节点宕机告警:
yaml
- alert: NodeDown
expr: up{job="node-exporter"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 已宕机"
description: "节点失联超过 2 分钟,请立即检查"
2. K8s 集群告警
Pod 频繁重启:
yaml
- alert: K8sPodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[15m]) > 0
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 频繁重启"
description: "容器 {{ $labels.container }} 在 15 分钟内重启 {{ $value | humanize }} 次"
节点 NotReady:
yaml
- alert: K8sNodeNotReady
expr: kube_node_status_condition{condition="Ready",status="true"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "K8s 节点 {{ $labels.node }} NotReady"
description: "节点处于 NotReady 状态超过 5 分钟,请检查 kubelet 和网络"
PV 容量告警:
yaml
- alert: K8sPersistentVolumeFillingUp
expr: |
(kubelet_volume_stats_available_bytes / kubelet_volume_stats_capacity_bytes) < 0.15
for: 10m
labels:
severity: warning
annotations:
summary: "PV {{ $labels.persistentvolumeclaim }} 即将写满"
description: "命名空间 {{ $labels.namespace }},可用 {{ $value | humanize }}%"
3. 应用层告警(RED 方法论)
RED 方法论:Rate(请求速率)、Errors(错误率)、Duration(延迟),每个服务都应覆盖这三项。
yaml
# 错误率告警
- alert: AppHighErrorRate
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "服务 {{ $labels.service }} 错误率 > 5%"
description: "5xx 错误率 {{ $value | humanizePercentage }}"
# P99 延迟告警
- alert: AppHighLatency
expr: |
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "服务 {{ $labels.service }} P99 延迟 > 1s"
description: "P99 延迟 {{ $value }}s"
4. 中间件告警
MySQL 连接数:
yaml
- alert: MySQLTooManyConnections
expr: |
mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL 连接数 > 80%"
description: "{{ $labels.instance }} 当前连接数占比 {{ $value | humanizePercentage }}"
Redis 内存告警:
yaml
- alert: RedisMemoryHigh
expr: |
(redis_memory_used_bytes / redis_memory_max_bytes) * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Redis 内存使用 > 80%"
description: "{{ $labels.instance }} 内存使用率 {{ $value | humanize }}%"
5. SLO 预算告警(高级)
这是"不被半夜叫醒"的终极武器。基于 SLO(Service Level Objective)的告警比固定阈值智能得多。
多窗口燃尽率告警(Google SRE 推荐):
yaml
- alert: SLOBurnRateHigh
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h])) by (service)
/
sum(rate(http_requests_total[1h])) by (service)
) > 14.4 * 0.001
labels:
severity: critical
annotations:
summary: "服务 {{ $labels.service }} 错误预算燃尽率过高"
description: >
1 小时窗口燃尽率 {{ $value | humanize }},
按此速度 {{ .Labels.service }} 的错误预算将在 2 小时内耗尽
这里的 14.4 是 Google SRE 的推荐系数:1 小时窗口 × 14.4 倍燃尽 = 需要立即响应。换成 6 小时窗口 × 6 倍 = warning 级别。这种告警只在"真的快把 SLO 预算烧光了"时才触发,大大减少了误报。
五、告警规则优化六条铁律
1. 给 for 一个合理的值
| 场景 | 建议 for 值 |
|---|---|
| 服务宕机(up=0) | 1-2 分钟 |
| 资源类告警(CPU/内存) | 5-10 分钟 |
| 磁盘告警 | 5-15 分钟 |
| 证书过期 | 7 天 |
| 连接数告警 | 5 分钟 |
经验之谈:如果某个告警在过去一个月里,90% 的触发都在 3 分钟内自动恢复了,那 for 至少设 5 分钟。
2. 用百分比替代绝对值
yaml
# ❌ 坏:16GB 机器和 128GB 机器共用 2GB 阈值
expr: node_memory_MemAvailable_bytes < 2147483648
# ✅ 好:按各自总内存的比例计算
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 10
3. 合理使用标签,但别滥用
instance、job、severity 是核心标签,每条规则必带。业务标签(service、namespace、env)按实际路由需要添加。
不要 把 instance 级别的差异信息放进同一个告警组------那会让 Alertmanager 的 group_by 失效。
4. 写好 annotations
annotations 是告警通知里最直观的信息。好的 annotation:
summary:一句话说清什么出问题了(15 字以内)description:包含具体数值、影响范围、建议操作(50-100 字)runbook_url:指向处理方案的文档链接
yaml
annotations:
summary: "{{ $labels.service }} P99 延迟过高"
description: "P99 延迟 {{ $value }}s,阈值 1s。影响接口 /api/order。请检查数据库慢查询和下游服务耗时"
runbook_url: "https://wiki.internal/runbooks/high-latency"
5. 设置 Alertmanager 抑制规则
当节点宕机时,上面跑的 Pod、服务都会报不可用------但你只需要一条"节点宕机"告警。
yaml
inhibit_rules:
- source_match:
alertname: NodeDown
severity: critical
target_match_re:
alertname: "K8s.*|App.*|MySQL.*"
equal: ['instance']
含义:当 NodeDown 为 critical 时,同 instance 上所有 K8s/App/MySQL 告警全部抑制。
6. 定期复盘告警,做减法
每个月花 30 分钟做告警复盘:
- 导出过去 30 天所有告警记录
- 逐一过:这条告警触发后有人处理吗?处理有用吗?
- 没人处理且没影响的 → 删掉这条告警规则或降级为 info
- 告警频率过高但无实际影响 → 调高阈值或延长
for
这不是在偷懒,是在保护团队的告警敏感度。
六、总结
- 所有告警加
for,根据不同场景设 2-10 分钟不等的持续时间 severity三级分层(critical/warning/info),分别路由到不同渠道- 用百分比代替绝对值,适配不同规格的机器
- annotations 写清楚什么出问题 + 怎么处理 + 文档链接
- 配置抑制规则,根因告警触发后抑制衍生告警
- 导入 SLO 燃尽率告警,从固定阈值进化到智能告警
- 每月做一次告警复盘,删掉没用的、调优有用的
把这七条落地,你的告警系统会从一个"半夜叫醒你的东西"变成"帮你发现问题的东西"。Prometheus 的告警能力本身不差,差的是告警规则的设计思路。
规则对了,凌晨三点的手机才能安静。