告警的艺术:从 node_exporter 指标到生产级规则

凌晨 3 点,手机震动。

makefile 复制代码
告警:server-01 CPU 使用率超过 80%
触发时间:03:12:45
持续时长:2 分钟

你睁开惺忪的眼睛,登录服务器查看,发现只是 Jenkins 在跑定时编译任务。

这已经是本周第 12 次误报。

问题不在 Prometheus,也不在 node_exporter,而在告警规则的设计


《从 node-exporter 学如何写出可复用的监控指标》中,我们学习了 node_exporter 的设计哲学:暴露事实,不是观点。

  • CPU 暴露的是累计时间,不是使用率
  • 内存暴露的是多个 Gauge,不是一个"使用率"
  • 磁盘容量和 IO 分开设计

这些"事实"很完整,但问题来了:有了指标,怎么变成告警?

这不是简单设个阈值就行。你真的需要在 CPU 达到 80% 时立即告警吗?还是应该等它持续 10 分钟再打扰你?

本文聚焦一个核心问题:**基于 node_exporter 的数据,如何设计一套生产级的虚拟机告警规则?

1. 为什么需要告警规则的设计智慧

1.1 Alertmanager 的局限

很多人觉得 Prometheus 有 Alertmanager,为什么还要自研告警引擎?

Alertmanager 的三个痛点:

yaml 复制代码
# Alertmanager 的配置
route:
  group_by: ['alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'team-pager'
问题 表现 影响
配置复杂 YAML 难理解 运维成本高,改规则要重启
无状态可见 不知道当前有哪些告警 无法追溯告警历史
难以扩展 加功能要改源码 无法融入业务逻辑

一个真实场景:

arduino 复制代码
运维:"CPU 使用率高的告警触发了多少次?"
Alertmanager:"不知道,我不存历史。"

运维:"这个告警持续多久了?"
Alertmanager:"不知道,我只管通知。"

运维:"能不能在发布窗口屏蔽告警?"
Alertmanager:"可以,但要写复杂的 YAML。"

自研的价值:

  • Web UI 管理规则,不用改 YAML
  • 数据库存储历史,可以分析趋势
  • 灵活扩展,融入业务场景(如发布窗口自动静默)

1.2 Google 的两个方法论

Google SRE 给了我们两个黄金法则:

USE 方法(资源监控):

flowchart LR A[Utilization
利用率] --> B[提前预警] C[Saturation
饱和度] --> D[确认拥塞] E[Errors
错误] --> F[确认故障] style A fill:#fff4e1 style C fill:#ffe1f5 style E fill:#ffcccc
  • Utilization:资源用了多少?(CPU 80% → 需要关注)
  • Saturation:是否有排队?(IO 队列长 → 性能下降)
  • Errors:是否有错误?(磁盘错误 → 硬件故障)

四个黄金信号(服务监控):

虽然虚拟机不是"服务",但可以映射:

  • Latency:IO 延迟高 → 影响上层应用
  • Traffic:网络流量异常 → 可能被攻击
  • Errors:硬件错误 → 需要介入
  • Saturation:资源饱和 → 需要扩容

启示:告警不是简单设阈值,而是要理解系统在不同阶段的表现。

1.3 从指标到告警的核心挑战

node_exporter 给了我们"事实":

ini 复制代码
node_cpu_seconds_total{mode="user"} 123456   # CPU 累计时间
node_memory_MemAvailable_bytes 8388608000    # 可用内存
node_filesystem_avail_bytes 53687091200      # 磁盘可用空间

但这些是 Counter 和 Gauge,怎么变成告警?

三个核心问题:

  1. 阈值怎么定? ------ 80% 合适还是 90% 合适?
  2. 怎么过滤毛刺? ------ 短暂峰值要不要告警?
  3. 如何提前预警? ------ 等到真出问题才告是不是太晚了?

接下来,我们用 CPU、内存、磁盘、网络四个维度,看看如何设计告警规则。


2. CPU 告警:不只是设个 80%

2.1 错误示例

很多人第一反应是这样:

promql 复制代码
# 错误示例:CPU 使用率 > 80% 就告警
(1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m]))) * 100 > 80

会发生什么?

makefile 复制代码
10:00 - CPU 85%(代码编译,正常)  → 告警
10:01 - CPU 60%(编译结束)        → 恢复
10:05 - CPU 85%(定时任务,正常)  → 告警
10:06 - CPU 50%                    → 恢复

一天收到几十条告警,都是正常的业务波动。

问题:

  • 没有时间维度,瞬时峰值也告警
  • 80% 的阈值太低,正常繁忙就会触发
  • 不区分"临时忙"和"持续忙"

2.2 正确示例 1:加上时间维度

promql 复制代码
# 正确示例:CPU 持续 10 分钟 > 90%
(1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)) * 100 > 90
for: 10m

改进点:

参数 作用
[5m] 评估窗口 平滑 15 秒抓取间隔的抖动
> 90 阈值提高 留 10% 缓冲,避免误报
for: 10m 持续时长 过滤临时波动(编译、备份)
flowchart LR A[首次超过 90%] --> B[观察 10 分钟] B --> C{持续超过?} C -->|是| D[触发告警] C -->|否| E[取消] style A fill:#fff4e1 style D fill:#ffcccc style E fill:#ccffcc

效果:

  • 短暂的编译、备份不会告警
  • 只有真正持续繁忙才告警
  • 误报率从 80% 降到 < 10%

2.3 正确示例 2:区分 CPU 模式

整机 CPU 告诉你"忙不忙",但不知道"为什么忙"。

promql 复制代码
# 用户态 CPU 过高 → 应用代码效率问题
avg(rate(node_cpu_seconds_total{mode="user"}[5m])) by (instance) > 0.8
for: 10m

# 系统态 CPU 过高 → 系统调用过多(网络/磁盘 IO)
avg(rate(node_cpu_seconds_total{mode="system"}[5m])) by (instance) > 0.3
for: 10m

# iowait 过高 → 磁盘 IO 瓶颈
avg(rate(node_cpu_seconds_total{mode="iowait"}[5m])) by (instance) > 0.2
for: 5m

价值:不只知道"CPU 高",还知道"为什么高"。

erlang 复制代码
场景 1:整机 CPU 90%,同时 user 高
→ 运维:应用代码有问题,去排查业务

场景 2:整机 CPU 90%,同时 iowait 高
→ 运维:磁盘 IO 慢,去排查磁盘

场景 3:整机 CPU 90%,同时 system 高
→ 运维:系统调用多,可能是网络问题

这就是区分模式的价值:同样是 CPU 90%,但问题的根因完全不同,处理方式也不同。


3. 内存告警:Linux 的内存不是你想的那样

3.1 错误示例

promql 复制代码
# 错误示例:用 MemFree 计算使用率
(1 - node_memory_MemFree_bytes / node_memory_MemTotal_bytes) * 100 > 90

会发生什么?

makefile 复制代码
16G 内存的服务器:
MemTotal: 16G
MemFree:  1G   (真正空闲)
Cached:   8G   (文件缓存,可释放)

错误计算:使用率 = (16 - 1) / 16 = 93.75%
→ 告警:"内存不足!"
→ 运维登录查看
→ 发现 8G 是 cache,随时可以释放
→ 误报!

问题:不理解 Linux 内存管理机制。

Linux 会把空闲内存用来做 cache,提升文件读取性能。这些 cache 在应用需要内存时会自动释放,不应该算作"已使用"。

3.2 正确示例 1:用 MemAvailable

promql 复制代码
# 正确示例:用 MemAvailable 计算
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
for: 5m

MemAvailable 是什么?

ini 复制代码
MemAvailable = MemFree + 可回收的 Cache + 可回收的 Buffer

这才是真正可用的内存。

注意:node_memory_MemAvailable_bytes 在 node_exporter v0.16+ 版本才可用。如果你的版本较旧,需要先升级。

对比:

ini 复制代码
场景:16G 内存服务器

MemFree:      1G
Cached:       8G (可回收)
MemAvailable: 9G

用 MemFree:
使用率 = (16 - 1) / 16 = 93.75%  → 误报

用 MemAvailable:
使用率 = (16 - 9) / 16 = 43.75%  → 正确

3.3 正确示例 2:预测未来耗尽

当前可用内存充足,但如果有内存泄漏,迟早会耗尽。

promql 复制代码
# 预测 4 小时后可用内存是否耗尽
predict_linear(node_memory_MemAvailable_bytes[1h], 4*3600) < 0
for: 10m

predict_linear 的原理:

flowchart LR A[过去 1 小时数据] --> B[线性回归] B --> C[预测 4 小时后] C --> D{< 0?} D -->|是| E[告警] style A fill:#e1f5ff style E fill:#ffcccc

案例:

ini 复制代码
10:00 - MemAvailable = 10G
10:30 - MemAvailable = 8G
11:00 - MemAvailable = 6G

每小时下降 4G
→ 预测 4 小时后:6G - 16G = -10G < 0
→ 触发告警:"预计 4 小时后内存耗尽"

价值:提前 4 小时发现风险,而不是等到真的耗尽。

这就是 predict_linear() 的威力:从"被动应对"变成"主动预防"。


4. 磁盘告警:当前值和未来趋势

4.1 错误示例

promql 复制代码
# 错误示例:磁盘使用率 > 85% 就告警
(1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 > 85

会发生什么?

erlang 复制代码
场景 1:磁盘使用率 86%,但日志增长很快
→ 触发告警
→ 运维去清理日志
→ 但 2 小时后磁盘满了
→ 服务挂了

为什么?因为 85% 的阈值太晚了!

问题:

  • 不知道磁盘增长速度
  • 等到 85% 再告警,可能来不及处理
  • 没有考虑"未来会怎样"

4.2 正确示例 1:预测式告警

promql 复制代码
# 基于过去 6 小时数据,预测 24 小时后磁盘是否满
predict_linear(node_filesystem_avail_bytes[6h], 24*3600) < 0
for: 1h

价值:

erlang 复制代码
场景 1:磁盘使用率 82%,但增长很快
→ 规则 1 告警:"预测 24h 后满"
→ 运维:优先处理,明天就会满

场景 2:磁盘使用率 82%,但增长很慢
→ 规则 1 不告警:预测 7 天后才满
→ 运维:不紧急,排期清理即可

同样是 82%,但处理优先级完全不同。

4.3 正确示例 2:容量和 IO 分开

磁盘有两类问题:

  • 容量问题:快满了
  • 性能问题:IO 慢了
promql 复制代码
# 容量告警:使用率 > 90%
# 注意:过滤掉临时目录和虚拟文件系统
(1 - node_filesystem_avail_bytes{
    fstype=~"ext4|xfs",
    mountpoint!~"/tmp|/var/lib/docker|/boot"
  } / node_filesystem_size_bytes) * 100 > 90
for: 10m

# 性能告警:IO 使用率 > 80%
rate(node_disk_io_time_seconds_total{device=~"sd.*|vd.*"}[5m]) > 0.8
for: 10m

# 性能告警:IO 队列长度 > 10
rate(node_disk_io_time_weighted_seconds_total{device=~"sd.*|vd.*"}[5m]) > 10
for: 5m

为什么要过滤路径?

diff 复制代码
不需要告警的挂载点:
- /tmp:临时目录,满了也无所谓
- /var/lib/docker:容器存储,由 Docker 管理
- /boot:引导分区,通常很小且不会变化

只关注业务数据盘:
- /:根分区
- /data:数据分区
- /home:用户目录

为什么要分开?

yaml 复制代码
场景 1:磁盘使用率 50%,但 IO 队列很长
→ 容量充足,但性能差
→ 需要优化 IO(换 SSD、减少小文件操作)

场景 2:磁盘使用率 95%,但 IO 正常
→ 容量不足,但性能正常
→ 需要清理磁盘或扩容

两类问题,两种处理方式。

**运维口诀**:容量看趋势,性能看队列。

---

## 5. 网络告警:流量和错误

### 5.1 错误示例

```promql
# 错误示例:网络流量 > 100MB/s 就告警
rate(node_network_receive_bytes_total[5m]) / 1024 / 1024 > 100

问题:

  • 100MB/s 对千兆网卡是正常的(125MB/s 满载)
  • 没有区分"流量大"和"异常流量"
  • 没有考虑错误率

5.2 正确示例:流量 + 错误率

promql 复制代码
# 网络流量超过带宽的 80%
# 注意:排除回环接口和虚拟接口
rate(node_network_receive_bytes_total{
    device!~"lo|docker.*|veth.*|br-.*"
  }[5m]) / (node_network_speed_bytes * 0.8) > 1
for: 5m

# 网络错误率 > 0.1%
rate(node_network_receive_errs_total{
    device!~"lo|docker.*|veth.*|br-.*"
  }[5m]) 
/ 
rate(node_network_receive_packets_total{
    device!~"lo|docker.*|veth.*|br-.*"
  }[5m]) > 0.001
for: 5m

为什么要过滤接口?

markdown 复制代码
不需要监控的网络接口:
- lo:回环接口,本地通信
- docker0, br-*:Docker 网桥
- veth*:容器虚拟网卡

只监控物理网卡:
- eth0, eth1:传统命名
- ens*, enp*:systemd 命名
- bond*:网卡绑定

改进点:

  • 相对阈值(带宽的 80%)而不是绝对值
  • 关注错误率,不只是流量
  • 错误率 0.1% 很小,但可能是硬件问题的信号

场景:

复制代码
场景 1:流量高,错误率正常
→ 业务正常繁忙,无需告警

场景 2:流量正常,错误率高
→ 网卡/交换机有问题,需要排查硬件

场景 3:流量高,错误率也高
→ 可能是 DDoS 攻击或网络故障

6. 告警规则设计方法论

基于上面的案例,可以总结出告警规则设计的三个维度:

flowchart TB A[告警规则设计] --> B[时间维度] A --> C[严重程度] A --> D[业务上下文] B --> E[评估窗口: 5m/15m
平滑抖动] B --> F[持续时长: for 10m
过滤毛刺] C --> G[P0: 服务不可用
立即处理] C --> H[P1: 性能下降
1小时内] C --> I[P2: 资源紧张
当天处理] D --> J[区分场景
生产/测试] D --> K[关联指标
多维度判断] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffcccc style D fill:#ccffcc

6.1 时间维度

评估窗口 [5m]

  • 作用:平滑 15 秒抓取间隔的抖动
  • 选择:CPU/内存用 5m,磁盘用 6h(变化慢)

持续时长 for: 10m

  • 作用:过滤短暂峰值
  • 选择:CPU 用 10m,磁盘用 1h(不需要那么敏感)

6.2 严重程度

三个级别:

级别 含义 响应时间 典型场景
P0 服务不可用 立即 磁盘满、OOM
P1 性能下降 1 小时内 CPU 95%、内存 95%
P2 资源紧张 当天 CPU 85%、磁盘 80%

设计原则:

  • P0 的 for 更短(5m),不能等太久
  • P2 的 for 更长(30m),避免误报

6.3 业务上下文

区分场景:

promql 复制代码
# 生产环境 90% 告警
cpu_usage{env="prod"} > 0.9

# 测试环境 95% 告警(更宽松)
cpu_usage{env="test"} > 0.95

关联指标:

promql 复制代码
# CPU 高 + iowait 高 → 可能是磁盘慢
cpu_usage > 0.9 and cpu_iowait > 0.2

# CPU 高 + 网络流量高 → 可能是网络处理
cpu_usage > 0.9 and network_traffic > 100MB/s

这就是"因地制宜"的价值:不同场景用不同规则,不能一刀切。


7. 好的告警系统应该解决什么问题

有了规则,还需要一个引擎来执行。好的告警系统应该解决这些问题:

7.1 Fingerprint:告警实例的唯一标识

同一个规则可能命中多个实例:

ini 复制代码
规则: CPU 使用率 > 90%

命中:
- {instance="server-01", env="prod"} → Fingerprint = "abc123"
- {instance="server-02", env="prod"} → Fingerprint = "def456"

Fingerprint = hash(rule_id + labels)

每个 Fingerprint 是一个独立的告警,需要单独追踪:

  • 什么时候开始的?
  • 触发了几次?
  • 现在状态如何?

7.2 防抖动:避免告警风暴

两层防抖:

flowchart LR A[首次命中] --> B[pending
观察期] B --> C{持续 >= for?} C -->|是| D[firing
确认告警] C -->|否| A D --> E{5分钟内重复?} E -->|是| F[跳过处理] E -->|否| G[写库/通知] style B fill:#fff4e1 style D fill:#ffcccc style F fill:#cccccc style G fill:#ccffcc

第一层:for_duration(规则层)

  • 持续 10 分钟才从 pending 转为 firing
  • 过滤短暂毛刺

第二层:时间窗口去重(消费层)

  • 5 分钟内同一告警只处理一次
  • 避免频繁写库和通知

7.3 可扩展:插件化数据源

flowchart LR A[告警引擎] --> B[Prometheus
指标] A --> C[日志扫描
关键字] A --> D[SQL 查询
数据库] A --> E[黑盒探测
HTTP/TCP] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffe1f5 style D fill:#ffcccc style E fill:#ccffcc

不只是 Prometheus 指标,还可以扩展:

  • 日志关键字(如"OutOfMemoryError")
  • SQL 规则(如"超时订单 > 100")
  • 黑盒探测(如"API 5xx 错误")

7.4 静默管理:计划性维护

场景:

erlang 复制代码
晚上 22:00-23:00 发布新版本
→ 服务会重启
→ CPU 会短暂 100%
→ 不应该告警

解决方案:时间窗口静默

yaml 复制代码
静默规则:
  name: "发布窗口"
  start: "22:00"
  end: "23:00"
  match:
    env: "prod"
    service: "api"

在发布窗口内,匹配的告警自动静默,不发通知。

7.5 告警历史:可追溯和分析

sql 复制代码
-- 历史告警表
CREATE TABLE history_alerts (
  fingerprint VARCHAR(64),
  rule_name VARCHAR(255),
  start_at DATETIME,
  end_at DATETIME,
  duration INT,
  trigger_count INT
);

价值:

  • 查看某台服务器过去一周的告警次数
  • 分析哪些规则误报率高
  • 计算平均恢复时间(MTTR)

告警历史不只是记录,更是持续改进的基础。没有数据就没有优化,好的系统要能自我进化。


8. 总结

8.1 从指标到告警的核心思想

不是简单设阈值,而是:

  1. 理解指标的本质

    • CPU 是累计时间,要用 rate() 转成使用率
    • 内存要用 MemAvailable,不是 MemFree
    • 磁盘要看趋势,不只看当前值
  2. 加上时间维度

    • 评估窗口 [5m] 平滑抖动
    • 持续时长 for: 10m 过滤毛刺
  3. 考虑业务上下文

    • 区分生产和测试环境
    • 关联多个指标(CPU + iowait)
    • 预测未来(predict_linear)

8.2 好的告警规则长什么样

维度 坏规则 好规则
阈值 CPU > 80% CPU > 90%,for: 10m
指标 只看一个指标 关联多个指标
时间 只看当前值 看趋势(predict_linear)
上下文 一刀切 区分场景(生产/测试)

8.3 好的告警系统解决什么问题

五个核心能力:

  1. Fingerprint ------ 每个告警实例有唯一标识
  2. 防抖动 ------ 两层过滤(for + 时间窗口去重)
  3. 可扩展 ------ 不只是 Prometheus,还能接其他数据源
  4. 静默管理 ------ 计划性维护不告警
  5. 历史追溯 ------ 可分析、可改进

8.4 三个核心洞察

看完这篇文章,记住这三点就够了:

1. 好的告警要能定位问题

不只说"CPU 高",还要说"是 user 高还是 iowait 高"。不只告诉你有问题,还要告诉你什么问题。

2. 好的告警要能预测未来

predict_linear() 提前 4 小时、24 小时发现风险,而不是等到真的出问题才知道。从"被动应对"变成"主动预防"。

3. 好的系统要能自我进化

记录告警历史,分析误报率,持续优化规则。没有数据就没有改进,没有历史就没有进化。


附录:告警规则自查清单

在上线告警规则前,用这个清单检查一遍:

CPU 告警

  • 是否使用了 rate() 而不是直接用 Counter 值?
  • 阈值是否 >= 90%(而不是 80%)?
  • 是否设置了 for: 10m 持续时长?
  • 是否区分了 user/system/iowait 模式?

内存告警

  • 是否使用了 MemAvailable 而不是 MemFree
  • 版本是否 >= node_exporter v0.16?
  • 是否使用了 predict_linear() 预测未来?
  • P0 告警阈值是否 >= 95%?

磁盘告警

  • 容量告警是否使用了 predict_linear()
  • 是否过滤了 /tmp/boot 等临时目录?
  • 是否只监控 ext4xfs 文件系统?
  • 是否分别设置了容量告警和 IO 告警?
  • IO 告警是否过滤了设备类型(如 device=~"sd.*|vd.*")?

网络告警

  • 是否过滤了 lodocker0veth* 等虚拟接口?
  • 流量告警是否使用了相对阈值(带宽的 80%)?
  • 是否同时监控了流量和错误率?
  • 错误率阈值是否足够敏感(如 0.1%)?

通用检查

  • 所有规则是否都设置了 for 持续时长?
  • P0/P1/P2 的严重级别是否合理?
  • 是否区分了生产和测试环境?
  • 规则名称是否清晰表达了告警内容?
  • 是否添加了 annotations 描述?

快速参考:核心规则速查

监控项 推荐规则 阈值 for 说明
CPU 整机 (1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m]))) > 0.9 90% 10m 基础告警
CPU iowait avg(rate(node_cpu_seconds_total{mode="iowait"}[5m])) > 0.2 20% 5m 磁盘瓶颈
内存可用 (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 0.9 90% 5m 基础告警
内存预测 predict_linear(node_memory_MemAvailable_bytes[1h], 4*3600) < 0 - 10m 提前预警
磁盘容量 (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) > 0.9 90% 10m 基础告警
磁盘预测 predict_linear(node_filesystem_avail_bytes[6h], 24*3600) < 0 - 1h 提前预警
磁盘 IO rate(node_disk_io_time_seconds_total[5m]) > 0.8 80% 10m 性能问题
网络流量 rate(node_network_receive_bytes_total[5m]) / node_network_speed_bytes > 0.8 80% 5m 带宽瓶颈
网络错误 rate(node_network_receive_errs_total[5m]) / rate(node_network_receive_packets_total[5m]) > 0.001 0.1% 5m 硬件问题

从 node_exporter 的指标,到生产级的告警规则,核心是一套设计思想:

  • 理解本质(指标的含义)
  • 加上时间(评估窗口 + 持续时长)
  • 结合上下文(业务场景 + 关联指标)
  • 持续迭代(追踪历史 + 质量改进)

这不是一篇操作手册,而是一套方法论。掌握了这套思想,你就能设计出适合自己业务的告警规则。

相关推荐
源码获取_wx:Fegn08951 天前
基于springboot + vue酒店预约系统
java·vue.js·spring boot·后端·spring
我想问问天1 天前
【从0到1大模型应用开发实战】03|写一个可解释的RAG规则检索器
后端·aigc
北邮刘老师1 天前
【智能体互联协议解析】智能体点对点交互模式的三种实现方式和应用场景
数据库·人工智能·架构·智能体·智能体互联网
一直在追1 天前
实战硬核!手把手教你用 Python 打造企业级 LLM 网关 (FastAPI + Asyncio 架构篇)
架构
豆浆Whisky1 天前
6小时从0到1:我用AI造了个分片SQL生成器
后端·sql·ai编程
马达加斯加D1 天前
微服务治理 --- 核心维度及常用技术栈组件
微服务·云原生·架构
在西安放羊的牛油果1 天前
Monorepo 各包间正确的通信方式
前端·架构·代码规范
风的归宿551 天前
解构内存迷宫:串联虚拟地址、页表与内存使用(一)
后端
武子康1 天前
大数据-202 sklearn 决策树实战:criterion、Graphviz 可视化与剪枝防过拟合
大数据·后端·机器学习