WEBKT

从Zabbix/CloudWatch迁移到Prometheus:为什么你的告警规则成了技术债?

6 0 0 0

迁移不是"配置翻译",而是"观测范式重构"

去年这个时候,我刚把公司最后一台Zabbix Server关机。看着 Grafana 上漂亮的 Prometheus 仪表盘,本以为功德圆满,结果接下来两周,我们团队被告警风暴折腾得几乎神经衰弱——不是因为Prometheus不好,而是我们把Zabbix的思维硬生生塞进了Prometheus的壳子里

如果你正在计划或执行类似的迁移,请停下来问自己一个问题:你只是在把旧系统的阈值"翻译"成PromQL,还是在重新思考"什么才是真正需要告警的"?

这是两个完全不同的维度。前者是配置搬运工,后者是SRE工程师。

陷阱一:把"固定阈值"当成真理

在Zabbix或CloudWatch时代,我们习惯了这样的逻辑:

# Zabbix风格的思维
if CPU使用率 > 85% for 5 minutes then 告警
if 内存使用率 > 90% then 告警

这种基于绝对数值的阈值在Prometheus里当然能写:

# 灾难性的直接翻译
100 - (avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85

问题在哪? 这种规则忽略了业务上下文。一个批处理服务器的85% CPU可能是正常的,而一个API网关的50% CPU伴随延迟飙升才是灾难。

正确的重构思路是基于SLO(服务等级目标)的SLI(服务等级指标)设计:

# 错误率SLI(基于业务影响)
(sum(rate(http_requests_total{status=~"5.."}[5m])) 
 / sum(rate(http_requests_total[5m]))) > 0.001  # 0.1%错误预算

# 延迟SLI(关注长尾,而非平均值)
histogram_quantile(0.99, 
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
) > 0.5  # P99延迟超过500ms

关键区别: 旧系统监控的是"资源健康",新系统应该监控的是"用户体验"。旧规则清单只是你的需求参考源,告诉你"以前大家担心什么",而不是配置模板告诉你"应该抄什么"。

陷阱二:忽视指标类型的语义差异

CloudWatch里的CPUUtilization和Prometheus的node_cpu_seconds_total不仅仅是格式不同,它们代表了完全不同的观测哲学

  • Zabbix/CloudWatch:定期采样点值,关注的是"此刻是多少"
  • Prometheus:Counter/Gauge/Histogram/Summary,关注的是"趋势和分布"

最危险的迁移垃圾:把Histogram当Gauge用

很多人把应用埋点的http_request_duration_seconds直接取avg()就拿来告警,这完全浪费了Histogram的价值。正确的做法是使用分位数和速率:

# 垃圾写法(丢失了分布信息)
avg(http_request_duration_seconds) > 0.3

# 正确写法(关注99分位线的趋势)
histogram_quantile(0.99, 
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, job)
) > 0.3

特别注意rate()increase()的使用: 旧系统的"当前连接数"可能直接对应node_netstat_Tcp_CurrEstab,但如果你想告警"连接数突增",必须用:

deriv(node_netstat_Tcp_CurrEstab[10m]) > 100  # 变化率告警,而非绝对值

陷阱三:Alertmanager路由的"复制粘贴"灾难

这是最容易产生"告警噪音"的环节。Zabbix的Action配置和Alertmanager的route机制有着本质差异:

Zabbix思维(扁平化):

  • 触发器 → Action → 发送给某个用户组

Prometheus思维(分层路由):

  • 告警标签 → 路由树 → 分组(group) → 抑制(inhibition) → 接收器

典型的反模式配置:

# 错误的:为每个旧Zabbix规则创建独立路由
routes:
  - match: {alertname: "HighCPU"}
    receiver: "ops_team"
  - match: {alertname: "HighMemory"}
    receiver: "ops_team"
  # ... 100个类似的路由

正确的映射策略应该是基于 severity 和 service 的矩阵:

route:
  receiver: 'default'
  group_by: ['alertname', 'cluster', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h
  routes:
    # 关键路径:立即通知,不分组
    - match:
        severity: critical
        team: backend
      receiver: 'pagerduty-backend'
      group_by: ['alertname', 'instance']  # 关键告警需要快速定位到具体实例
      continue: false
      
    # 非关键:聚合通知,减少噪音
    - match:
        severity: warning
      receiver: 'slack-monitoring'
      group_by: ['cluster', 'service']  # 按服务聚合,避免单实例抖动轰炸
      group_interval: 30m  # 降低通知频率

# 抑制规则:关键告警已触发时,抑制低级别告警
inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['cluster', 'service', 'alertname']

核心原则:

  • 分组(group_by):解决"单故障多点告警"问题,比如数据库主库挂了,相关的连接池告警应该被聚合
  • 抑制(inhibit_rules):解决"级联告警"问题,网络不可达时,抑制所有依赖该网络的下游服务告警
  • 路由(routes):解决"谁应该在什么时间收到什么"的问题,而非简单的人员映射

实战迁移检查清单

如果你正在准备迁移,按这个顺序执行,能避免90%的坑:

第一阶段:语义梳理(不要碰代码)

  1. 审计旧告警:列出Zabbix/CloudWatch所有触发器,标记哪些是"资源监控",哪些是"业务监控"
  2. 定义SLO:为每个服务定义错误率、延迟、吞吐量的SLO(如99.9%可用性,P99<200ms)
  3. 分级标签:为每条旧规则打上 severity: critical|warning|infoteam: backend|dba|sre 标签

第二阶段:指标映射(关键转换)

旧系统概念 Prometheus对应 注意点
CPU使用率 100 - rate(node_cpu_seconds_total{mode="idle"}[5m]) * 100 rate而非原始值
磁盘使用率 node_filesystem_avail_bytes / node_filesystem_size_bytes 关注可用空间比例,而非使用率
网络流量 rate(node_network_receive_bytes_total[5m]) 必须取rate,原始Counter无意义
应用延迟 histogram_quantile(0.99, rate(...)) 不要用avg(),必须用分位数
黑盒探测 probe_success 比ping更可靠的端点健康检查

第三阶段:灰度验证

  1. 双跑期:Prometheus和Zabbix并行运行2周,但不要直接对比告警数量,而是对比"漏报"和"误报"事件
  2. 降噪调优:观察Alertmanager的group_intervalrepeat_interval,确保非关键告警不会在夜间打电话
  3. 标签规范化:建立clusterserviceteam的标签字典,这是路由的基础

结语:从"监控工具迁移"到"可观测性升级"

迁移到Prometheus不是终点,而是**从"监控"(Monitoring)转向"可观测性"(Observability)**的起点。当你不再纠结于"如何把Zabbix的CPU阈值改写成PromQL",而是思考"这个指标是否真正反映用户体验"时,你才真正完成了这次迁移。

那些直接复制过来的旧规则,最好的归宿是git commit -m "legacy rules for reference"然后永远不要启用。毕竟,技术债最好的偿还方式,有时候是承认它存在,然后勇敢地重写。

运维老张 Prometheus监控告警SRE

评论点评