linux

用 Prometheus Recording Rules 把告警噪声砍掉 70%(一)

发布时间:18天前热度: 93 ℃评论数:

一、概述

1.1 背景介绍

在大规模微服务架构下,Prometheus 告警系统往往会陷入一个尴尬的境地:告警太多,运维团队开始选择性忽略;告警太少,真正的故障又可能漏掉。我在某电商平台负责监控体系建设时,团队每天要处理超过 2000 条告警,其中 70% 以上是重复的、关联的或者短暂抖动产生的噪声。

Recording Rules 是 Prometheus 提供的预计算机制,可以将复杂的查询表达式预先计算并存储为新的时间序列。通过合理设计 Recording Rules,我们不仅能显著降低 PromQL 查询压力,更重要的是可以实现告警聚合、去重、平滑,从根本上减少告警噪声。

1.2 技术特点

  • • 预计算优化:将复杂的聚合查询预先计算,避免告警规则重复计算相同的表达式,降低 Prometheus 负载
  • • 时间窗口平滑:通过 avg_over_timemax_over_time 等函数消除瞬时抖动,避免单点毛刺触发告警
  • • 多维度聚合:支持按 namespace、service、pod 等维度聚合指标,实现告警收敛
  • • 层级化告警:基于 Recording Rules 构建指标层级,实现从基础指标到业务指标的逐层抽象

1.3 适用场景

  • • 场景一:大规模 Kubernetes 集群监控,Pod 数量超过 1000,原生指标产生的告警风暴需要收敛
  • • 场景二:微服务架构下,服务间依赖复杂,需要将底层告警聚合为服务级别的健康状态
  • • 场景三:业务高峰期流量抖动频繁,需要平滑处理避免误告警
  • • 场景四:多租户环境下,需要按租户/业务线聚合告警,避免一个故障触发上百条告警

1.4 环境要求

组件
版本要求
说明
Prometheus
2.28+
支持 Recording Rules 分组和 limit 特性
Alertmanager
0.23+
配合使用告警抑制和分组功能
Kubernetes
1.21+
如果使用 kube-prometheus-stack
内存配置
建议 8GB+
Recording Rules 会增加时序存储量

二、详细步骤

2.1 准备工作

◆ 2.1.1 现状分析

在动手之前,先要搞清楚当前告警噪声的分布。我习惯用下面这个查询来分析过去一周的告警触发情况:

# 查看 Prometheus 配置
kubectl get configmap prometheus-server -n monitoring -o yaml

# 分析告警触发频次(在 Prometheus UI 执行)
# 统计过去7天各告警规则触发次数
count_over_time(ALERTS{alertstate="firing"}[7d])

# 查看当前活跃告警数量
count(ALERTS{alertstate="firing"})

◆ 2.1.2 识别噪声来源

根据我的经验,告警噪声主要来自以下几类:

# 检查高频告警(每分钟触发超过10次的规则)
# 在 Prometheus 执行
topk(20, count by (alertname) (count_over_time(ALERTS{alertstate="firing"}[1h])))

# 检查短暂告警(持续时间小于5分钟的)
# 这类告警往往是抖动产生的噪声

通常会发现以下几类高频噪声:

  • • CPU/内存使用率瞬时超阈值
  • • Pod 重启计数器误报
  • • 网络延迟抖动
  • • 磁盘 IO 突增

2.2 核心配置

◆ 2.2.1 Recording Rules 基础结构

# 文件路径:/etc/prometheus/rules/recording_rules.yml
groups:
-name:node_recording_rules
interval:30s# 计算间隔,根据需求调整
rules:
# 规则定义
-record:job:node_cpu_utilization:avg5m
expr:|
          1 - avg by (job, instance) (
            rate(node_cpu_seconds_total{mode="idle"}[5m])
          )

说明interval 参数控制 Recording Rules 的计算频率。设置过小会增加计算压力,过大则可能错过关键变化。一般建议设置为告警评估间隔的一半。

◆ 2.2.2 CPU 使用率平滑处理

这是最常见的噪声来源。原始告警规则可能是这样的:

# 原始告警规则(噪声大)
-alert:HighCPUUsage
expr:100-(avgby(instance)(rate(node_cpu_seconds_total{mode="idle"}[1m]))*100)>80
for:1m

使用 Recording Rules 优化后:

# recording_rules.yml
groups:
-name:cpu_smoothing
interval:30s
rules:
# 第一层:5分钟平均 CPU 使用率
-record:instance:node_cpu_utilization:avg5m
expr:|
          1 - avg by (instance) (
            rate(node_cpu_seconds_total{mode="idle"}[5m])
          )

# 第二层:基于5分钟均值的15分钟最大值
# 这样可以捕获持续性高负载,过滤掉短暂峰值
-record:instance:node_cpu_utilization:max15m_avg5m
expr:|
          max_over_time(instance:node_cpu_utilization:avg5m[15m])

# 第三层:按集群聚合的 CPU 使用率
-record:cluster:node_cpu_utilization:avg
expr:|
          avg(instance:node_cpu_utilization:avg5m)

参数说明

  • • avg5m:使用 5 分钟窗口计算平均值,消除秒级抖动
  • • max15m_avg5m:在 5 分钟均值基础上取 15 分钟最大值,只有持续性高负载才会触发
  • • cluster: 前缀:表示集群级别聚合,便于识别层级

◆ 2.2.3 内存使用率分层聚合

groups:
-name:memory_recording_rules
interval:30s
rules:
# 节点级别内存使用率
-record:instance:node_memory_utilization:ratio
expr:|
          1 - (
            node_memory_MemAvailable_bytes
            / node_memory_MemTotal_bytes
          )

# 应用级别内存使用(Kubernetes 环境)
-record:namespace_pod:container_memory_usage:sum
expr:|
          sum by (namespace, pod) (
            container_memory_working_set_bytes{container!="", container!="POD"}
          )

# 服务级别内存使用率
-record:namespace_service:memory_utilization:avg5m
expr:|
          avg by (namespace, service) (
            avg_over_time(
              container_memory_working_set_bytes{container!=""}[5m]
            )
          ) / avg by (namespace, service) (
            kube_pod_container_resource_limits{resource="memory"}
          )

◆ 2.2.4 请求错误率聚合

对于微服务场景,HTTP 错误率告警是另一个噪声重灾区:

groups:
-name:http_error_rate_rules
interval:15s
rules:
# 服务级别错误率(5分钟窗口)
-record:service:http_requests_total:rate5m
expr:|
          sum by (namespace, service) (
            rate(http_requests_total[5m])
          )

-record:service:http_requests_errors:rate5m
expr:|
          sum by (namespace, service) (
            rate(http_requests_total{status=~"5.."}[5m])
          )

# 错误率计算
-record:service:http_error_rate:ratio5m
expr:|
          service:http_requests_errors:rate5m
          / service:http_requests_total:rate5m

# 加权错误率:流量越大权重越高
# 避免低流量服务的单个错误触发高错误率告警
-record:service:http_error_rate:weighted5m
expr:|
          (service:http_requests_errors:rate5m + 1)
          / (service:http_requests_total:rate5m + 10)

2.3 启动和验证

◆ 2.3.1 配置热加载

# 检查配置语法
promtool check rules /etc/prometheus/rules/recording_rules.yml

# 热加载 Prometheus 配置
curl -X POST http://localhost:9090/-/reload

# 或者发送 SIGHUP 信号
kill -HUP $(pgrep prometheus)

◆ 2.3.2 验证 Recording Rules 生效

# 在 Prometheus UI 查询新生成的指标
# 应该能看到数据
instance:node_cpu_utilization:avg5m

# 检查 Recording Rules 状态
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[] | select(.name=="cpu_smoothing")'

# 预期输出应显示规则状态为 "health": "ok"

三、示例代码和配置

3.1 完整配置示例

◆ 3.1.1 完整的 Recording Rules 配置

# 文件路径:/etc/prometheus/rules/recording_rules.yml
groups:
# ============ 基础设施层 ============
-name:infrastructure_recording_rules
interval:30s
rules:
# CPU
-record:instance:node_cpu_utilization:avg5m
expr:1-avgby(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))

-record:instance:node_cpu_utilization:max_avg5m_over_15m
expr:max_over_time(instance:node_cpu_utilization:avg5m[15m])

# Memory
-record:instance:node_memory_utilization:ratio
expr:1-(node_memory_MemAvailable_bytes/node_memory_MemTotal_bytes)

-record:instance:node_memory_utilization:avg5m
expr:avg_over_time(instance:node_memory_utilization:ratio[5m])

# Disk
-record:instance:node_disk_utilization:ratio
expr:|
          1 - (
            node_filesystem_avail_bytes{fstype=~"ext4|xfs"}
            / node_filesystem_size_bytes{fstype=~"ext4|xfs"}
          )

# Network
-record:instance:node_network_receive:rate5m
expr:sumby(instance)(rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*|br.*"}[5m]))

-record:instance:node_network_transmit:rate5m
expr:sumby(instance)(rate(node_network_transmit_bytes_total{device!~"lo|veth.*|docker.*|br.*"}[5m]))

# ============ Kubernetes 层 ============
-name:kubernetes_recording_rules
interval:30s
rules:
# Pod 资源使用聚合到 namespace
-record:namespace:container_cpu_usage:sum
expr:|
          sum by (namespace) (
            rate(container_cpu_usage_seconds_total{container!="", container!="POD"}[5m])
          )

-record:namespace:container_memory_usage:sum
expr:|
          sum by (namespace) (
            container_memory_working_set_bytes{container!="", container!="POD"}
          )

# Pod 重启率(1小时窗口,避免单次重启告警)
-record:namespace_pod:container_restarts:increase1h
expr:|
          increase(kube_pod_container_status_restarts_total[1h])

# Deployment 可用性
-record:namespace_deployment:replicas_unavailable:ratio
expr:|
          kube_deployment_status_replicas_unavailable
          / kube_deployment_spec_replicas

# ============ 应用层 ============
-name:application_recording_rules
interval:15s
rules:
# HTTP 请求速率
-record:service:http_requests:rate5m
expr:sumby(namespace,service,method)(rate(http_requests_total[5m]))

# HTTP 错误率
-record:service:http_errors:rate5m
expr:sumby(namespace,service)(rate(http_requests_total{status=~"5.."}[5m]))

# HTTP P99 延迟
-record:service:http_latency_p99:5m
expr:|
          histogram_quantile(0.99,
            sum by (namespace, service, le) (
              rate(http_request_duration_seconds_bucket[5m])
            )
          )

# 服务健康评分(综合指标)
-record:service:health_score:5m
expr:|
          (
            1 - clamp_max(service:http_errors:rate5m / service:http_requests:rate5m, 1)
          ) * 0.4
          +
          (
            1 - clamp_max(service:http_latency_p99:5m / 2, 1)
          ) * 0.3
          +
          (
            clamp_max(service:http_requests:rate5m / 100, 1)
          ) * 0.3

◆ 3.1.2 基于 Recording Rules 的告警规则

# 文件路径:/etc/prometheus/rules/alert_rules.yml
groups:
-name:infrastructure_alerts
rules:
# 使用预计算指标,更稳定
-alert:NodeHighCPU
expr:instance:node_cpu_utilization:max_avg5m_over_15m>0.85
for:5m
labels:
severity:warning
annotations:
summary:"节点 {{ $labels.instance }} CPU 持续高负载"
description:"15分钟内 CPU 使用率峰值超过 85%,当前值 {{ $value | humanizePercentage }}"

-alert:NodeHighMemory
expr:instance:node_memory_utilization:avg5m>0.9
for:10m
labels:
severity:warning
annotations:
summary:"节点 {{ $labels.instance }} 内存使用率过高"
description:"内存使用率持续超过 90%"

-name:application_alerts
rules:
# 基于聚合指标的服务健康告警
-alert:ServiceUnhealthy
expr:service:health_score:5m<0.6
for:5m
labels:
severity:critical
annotations:
summary:"服务 {{ $labels.namespace }}/{{ $labels.service }} 健康状态异常"
description:"服务健康评分低于 0.6,当前值 {{ $value }}"

# Pod 频繁重启(使用1小时窗口)
-alert:PodFrequentRestart
expr:namespace_pod:container_restarts:increase1h>3
for:0m
labels:
severity:warning
annotations:
summary:"Pod {{ $labels.namespace }}/{{ $labels.pod }} 频繁重启"
description:"过去1小时重启 {{ $value }} 次"

3.2 实际应用案例

◆ 案例一:电商大促告警收敛

场景描述:双11大促期间,订单服务 QPS 从日常 1000 飙升到 50000,原有的错误率告警(错误数/总请求数 > 1%)频繁触发,因为即使错误率只有 0.5%,绝对错误数也达到了 250/秒。

实现代码

# Recording Rules
groups:
-name:ecommerce_rules
rules:
# 动态基线:使用过去1小时的错误率作为基线
-record:service:http_error_rate:baseline1h
expr:|
          avg_over_time(
            (
              sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
              / sum by (service) (rate(http_requests_total[5m]))
            )[1h:]
          )

# 当前错误率与基线的比值
-record:service:http_error_rate:ratio_to_baseline
expr:|
          (
            sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
            / sum by (service) (rate(http_requests_total[5m]))
          )
          / service:http_error_rate:baseline1h

# 告警规则
-alert:ServiceErrorRateSpike
expr:service:http_error_rate:ratio_to_baseline>3
for:5m
annotations:
summary:"服务 {{ $labels.service }} 错误率异常飙升"
description:"当前错误率是基线的 {{ $value | humanize }} 倍"

运行结果

大促期间告警数量对比:
- 优化前:每小时平均 150+ 条告警
- 优化后:每小时平均 5-10 条告警
- 误告警率:从 85% 降低到 15%

◆ 案例二:多租户环境告警聚合

场景描述:SaaS 平台有 200+ 租户,每个租户有独立的命名空间。当某个底层节点故障时,原有配置会触发 200+ 条 Pod 不可用告警。

实现步骤

  1. 1. 创建租户级别的聚合指标
  2. 2. 实现告警收敛,只告警受影响的节点,附带影响的租户列表
  3. 3. 配合 Alertmanager 的 group_by 实现进一步收敛
# Recording Rules
groups:
-name:tenant_aggregation
rules:
# 按租户聚合不可用 Pod 数量
-record:tenant:pods_unavailable:count
expr:|
          count by (tenant) (
            kube_pod_status_phase{phase!="Running", phase!="Succeeded"}
          )

# 租户服务可用性
-record:tenant:service_availability:ratio
expr:|
          sum by (tenant) (kube_deployment_status_replicas_available)
          / sum by (tenant) (kube_deployment_spec_replicas)

# 故障影响范围
-record:node:affected_tenants:count
expr:|
          count by (node) (
            count by (node, tenant) (
              kube_pod_info * on(pod, namespace) group_left(tenant)
              kube_namespace_labels
            )
          )

四、最佳实践和注意事项

4.1 最佳实践

◆ 4.1.1 Recording Rules 命名规范

遵循一致的命名规范对于维护大规模 Recording Rules 至关重要:

# 命名格式:level:metric_name:aggregation_window
# level: 聚合级别,如 instance, namespace, cluster
# metric_name: 指标名称
# aggregation_window: 时间窗口和聚合方式

# 好的命名示例
instance:node_cpu_utilization:avg5m
namespace:container_memory:sum
cluster:http_requests:rate15m

# 避免的命名方式
cpu_high                    # 缺少层级和时间窗口信息
recording_rule_1            # 无意义的名称

◆ 4.1.2 分层设计

# 三层架构设计
# 第一层:原始指标清洗和标准化
-record:instance:node_cpu_seconds:rate5m
expr:rate(node_cpu_seconds_total[5m])

# 第二层:指标聚合
-record:instance:node_cpu_utilization:avg5m
expr:1-avgby(instance)(instance:node_cpu_seconds:rate5m{mode="idle"})

# 第三层:业务指标
-record:service:availability:ratio
expr:|
    (
      sum(instance:node_cpu_utilization:avg5m < 0.9)
      / count(instance:node_cpu_utilization:avg5m)
    )

◆ 4.1.3 性能优化

  • • 控制 Recording Rules 数量:每增加一条规则,就会产生新的时间序列
    # 检查 Recording Rules 产生的时序数量
    prometheus_tsdb_head_series

    # 建议:单个 Prometheus 实例的 Recording Rules 产生的时序不超过总时序的 20%
  • • 合理设置 interval:根据指标变化频率设置
    • • 基础设施指标:30s
    • • 应用指标:15s
    • • 业务指标:60s
  • • 使用 limit 防止基数爆炸
    -record:topk_services:http_requests:rate5m
    expr:topk(100,sumby(service)(rate(http_requests_total[5m])))

◆ 4.1.4 高可用配置

  • • 多副本一致性:Recording Rules 在多个 Prometheus 副本上执行,确保配置完全相同
  • • 告警去重:配合 Alertmanager 的 group_by 和 inhibit_rules 进一步去重
  • • 联邦集群:在联邦架构中,Recording Rules 应该在被采集端执行,减少联邦查询压力
# Alertmanager 配置配合
route:
group_by: ['alertname''cluster''service']
group_wait:30s
group_interval:5m
repeat_interval:4h

inhibit_rules:
# 节点故障时抑制该节点上的所有 Pod 告警
-source_match:
alertname:NodeDown
target_match_re:
alertname:Pod.*
equal: ['node']

4.2 注意事项

◆ 4.2.1 配置注意事项

警告:Recording Rules 配置错误可能导致 Prometheus 无法启动或产生错误数据

  • • 注意事项一:Recording Rules 的 record 字段必须是合法的指标名称,不能包含特殊字符
  • • 注意事项二:避免 Recording Rules 之间的循环依赖,虽然 Prometheus 会检测,但可能导致计算延迟
  • • 注意事项三:修改已有 Recording Rules 的表达式时,注意下游依赖的告警规则可能需要同步调整阈值

◆ 4.2.2 常见错误

错误现象
原因分析
解决方案
Recording Rule 显示 health: unknown
表达式语法错误或引用的指标不存在
使用 promtool check rules 验证语法,确认源指标存在
新指标无数据
interval 设置过长或表达式结果为空
检查 interval 配置,在 Prometheus UI 测试表达式
Prometheus OOM
Recording Rules 产生的时序过多
添加 limit、优化标签基数、增加内存
告警延迟增加
Recording Rules 计算压力过大
减少规则数量、增大 interval、升级硬件

◆ 4.2.3 兼容性问题

  • • 版本兼容limit 功能需要 Prometheus 2.28+,低版本需要在表达式中使用 topk 替代
  • • 平台兼容:使用 Prometheus Operator 时,Recording Rules 需要通过 PrometheusRule CRD 管理
  • • 组件依赖:Recording Rules 依赖的源指标(如 node_exporter、kube-state-metrics)版本变更可能导致表达式失效


手机扫码访问