Prometheus告警规则设计:从入门到不半夜被叫醒

一、概述

凌晨 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% 是重复、无关或误报 → 真正有价值的告警被淹没
  • 最终后果:真正出问题时没人看告警

防疲劳三板斧:

  1. for 设置合理的持续时间:瞬时波动不告警,持续异常才触发
  2. 抑制规则(inhibition):节点宕机了就别再告诉我上面的服务不可用
  3. 静默窗口(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. 合理使用标签,但别滥用

instancejobseverity 是核心标签,每条规则必带。业务标签(servicenamespaceenv)按实际路由需要添加。

不要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 分钟做告警复盘:

  1. 导出过去 30 天所有告警记录
  2. 逐一过:这条告警触发后有人处理吗?处理有用吗?
  3. 没人处理且没影响的 → 删掉这条告警规则或降级为 info
  4. 告警频率过高但无实际影响 → 调高阈值或延长 for

这不是在偷懒,是在保护团队的告警敏感度。


六、总结

  • 所有告警加 for,根据不同场景设 2-10 分钟不等的持续时间
  • severity 三级分层(critical/warning/info),分别路由到不同渠道
  • 用百分比代替绝对值,适配不同规格的机器
  • annotations 写清楚什么出问题 + 怎么处理 + 文档链接
  • 配置抑制规则,根因告警触发后抑制衍生告警
  • 导入 SLO 燃尽率告警,从固定阈值进化到智能告警
  • 每月做一次告警复盘,删掉没用的、调优有用的

把这七条落地,你的告警系统会从一个"半夜叫醒你的东西"变成"帮你发现问题的东西"。Prometheus 的告警能力本身不差,差的是告警规则的设计思路。

规则对了,凌晨三点的手机才能安静。

相关推荐
1candobetter5 小时前
文件下载接口从预热到正式性能测试实践(JMeter + Prometheus + Grafana)
jmeter·grafana·prometheus
文青小兵7 小时前
Linux云计算——docker 告警(六)
linux·运维·docker·云计算·prometheus
成为你的宁宁8 小时前
【Kubernetes监控实战:NFS持久化存储 + Prometheus Operator + etcd监控】
kubernetes·prometheus·etcd
江南风月2 天前
WGCLOUD监控系统的Restful Http接口一览
运维·zabbix·运维开发·prometheus
文青小兵2 天前
Linux云计算——docker 监控(五)
linux·docker·云计算·grafana·prometheus
weixin_468466853 天前
Prometheus监控服务部署与实战指南
服务器·后端·python·docker·自动化·prometheus
江华森3 天前
Prometheus 全栈监控体系部署与使用指南
prometheus
codeejun4 天前
每日一Go-70、Prometheus + Grafana 从采集到告警的完整实战(Go + Kind)
golang·grafana·prometheus
Cat_Rocky6 天前
k8s-Prometheus的manifests 清单部署
linux·kubernetes·prometheus