标签: On-Call

  • 深入 SRE 告警治理:告别资源阈值风暴,基于多窗口 SLO 燃烧率与 Alertmanager 抑制实战

    生产环境绝大多数告警风暴源于粗放的“资源阈值”触发器。要真正给 On-Call 工程师减负,必须抛弃 CPU/内存使用率等原因导向告警,转向基于用户体验的 SLO(服务级别目标)现象导向告警。本文直接给出基于 Prometheus 的多窗口多燃烧率(Multi-Window Burn Rate)实现方案,结合 Alertmanager 路由抑制,彻底过滤瞬态抖动噪音。

    现场还原:被“阈值告警”淹没的真正故障

    近期排查过一个典型案例:某个核心交易链路出现 504 Gateway Timeout 雪崩。但在故障发生时的前 5 分钟内,On-Call 工程师的 Slack 和邮箱瞬间涌入 400 多条告警。

    其中 95% 的告警长这样:

    [FIRING] K8sNodeCpuHigh
    Severity: warning
    Summary: Node 10.x.x.x CPU usage is > 85%
    Description: CPU usage is at 92% for more than 3m.
    

    工程师的注意力完全被 Kubernetes 节点的 CPU 和 Pod 的重启告警吸引,试图去扩容 Node。但底层根因其实是:DB 连接池因慢查询耗尽,导致上游网关堆积请求,线程阻塞打满 CPU。高 CPU 只是结果,而非原因。 真正有价值的告警——“支付接口 P99 延迟突破 2s”被淹没在无穷无尽的资源告警噪音中。

    这种传统的告警配置策略(如 CPU > 80% 告警),在现代微服务和云原生架构中,除了消耗 SRE 的精力,毫无价值。

    为什么我们必须彻底抛弃静态资源利用率告警?

    传统的监控思路是自底向上的(Bottom-Up):监控机器 -> 监控 OS -> 监控 DB -> 监控应用。但在 K8S 集群中,Pod 随时在漂移,HPA(Horizontal Pod Autoscaler)会根据负载自动扩缩容。一个节点 CPU 跑到 90% 完全是资源利用率高的健康表现,只要服务的 RT(响应时间)和错误率达标,用户根本不关心你的 CPU 是 10% 还是 99%。

    防御性运维的核心思想是面向症状告警(Symptom-based Alerting)。 我们需要围绕 SLI(服务级别指示器)来构建监控体系,通常只关注四个黄金信号:延迟、流量、错误、饱和度。当且仅当错误预算(Error Budget)被快速消耗时,才触发 P1 级别 On-Call 呼叫。

    SLO 燃烧率告警核心架构与 PromQL 落地实战

    基于 Google SRE 实践,我们采用多时间窗口多燃烧率(Multi-Window, Multi-Burn-Rate)模型。

    假设我们的 SLO 是:API 过去 30 天的可用性达到 99.9%。 这意味着 30 天(730 小时)内的错误预算(Error Budget)为 0.1%。

    如果我们在 1 小时内消耗了整个月 2% 的错误预算,燃烧率(Burn Rate)计算如下: (2% / 100%) / (1h / 730h) ≈ 14.6(通常工程上取 14.4)。

    为了防止低频抖动触发告警(Flapping),我们引入双窗口:长窗口(1h)用于触发,短窗口(5m)用于确认当前故障仍在持续。只有当两个窗口的燃烧率同时超标时,才发出告警。

    1. 预计算 Recording Rules (Prometheus 2.45+)

    直接在告警规则中跑高基数(High Cardinality)的原始指标聚合会导致 Prometheus 评估超时。必须先使用 Recording Rules 将 SLI 降维。

    groups:
      - name: slo_sli_recordings
        interval: 1m
        rules:
          # 计算过去 5 分钟的错误率 SLI
          - record: job:request_error_rate5m
            expr: |
              sum by (job) (rate(http_requests_total{status=~"5.."}[5m]))
              /
              sum by (job) (rate(http_requests_total[5m]))
    
          # 计算过去 1 小时的错误率 SLI
          - record: job:request_error_rate1h
            expr: |
              sum by (job) (rate(http_requests_total{status=~"5.."}[1h]))
              /
              sum by (job) (rate(http_requests_total[1h]))
    

    2. 多窗口燃烧率告警规则

    在上述预计算指标的基础上,配置 14.4 燃烧率告警(严重告警,即刻 Page On-Call):

    groups:
      - name: slo_burn_rate_alerts
        rules:
          - alert: API_HighErrorBurnRate_Page
            # 条件:1小时的燃烧率 > 14.4 且 5分钟的燃烧率 > 14.4
            # SLO=99.9%, Budget=0.1% (0.001)
            # 14.4 * 0.001 = 0.0144 (即 1.44% 的绝对错误率阈值)
            expr: |
              (
                job:request_error_rate1h > 0.0144
                and
                job:request_error_rate5m > 0.0144
              )
            labels:
              severity: critical
              pager: "true"
            annotations:
              summary: "API 错误预算极速消耗 (Burn Rate > 14.4)"
              description: "服务 {{ $labels.job }} 在过去1小时内消耗了 2% 的月度错误预算,请立即介入排查。"
    

    通过这种多窗口机制,若只是 1 分钟的网络抖动,5m 窗口会很快回落,告警自动解除,On-Call 工程师根本不会被打扰;而如果是持续的底层熔断,1h 窗口和 5m 窗口同时达标,立刻触发电话告警。

    Alertmanager 高级减噪机制:Inhibit 与 Grouping

    即使有了 SLO 告警,在机房级网络割接或交换机故障时,仍会产生“服务级 SLO 全部崩塌”的并发告警。此时必须利用 Alertmanager (v0.26+) 的 group_byinhibit_rules 机制。

    1. 分组折叠 (Grouping)

    不要让每个容器的报错发一条消息,按服务或集群聚合:

    route:
      receiver: 'slack-oncall'
      group_by: ['job', 'cluster']
      group_wait: 30s      # 等待30秒收集同类告警
      group_interval: 5m   # 每5分钟发送一批新告警
      repeat_interval: 4h  # 未解决告警4小时后才重发
    

    2. 拓扑抑制 (Inhibition)

    底层基础组件宕机时,静默其上层所有应用的告警。例如:所在宿主机 NodeDown,则直接抑制该宿主机上所有 Pod 触发的 SLO 告警。

    inhibit_rules:
      - source_matchers:
          - alertname = "NodeDown"
          - severity = "critical"
        target_matchers:
          - severity =~ "warning|critical|info"
        # 只要 target 告警的 instance/node 标签和 source 匹配,就将其丢弃
        equal: ['node', 'cluster']
    

    通过抑制链设计:DatacenterDown -> 抑制 ClusterDown -> 抑制 NodeDown -> 抑制 AppSLOAlert,在灾难性故障现场,On-Call 工程师只会收到唯一一条最顶层的根因告警。

    常见问题

    Q:既然抛弃了静态资源告警,数据库磁盘满了或者证书过期这类问题怎么监控? A:不要陷入极端。基于症状的 SLO 告警针对的是用户请求链路。对于确定性的、必然导致宕机且有充足时间提前干预的“饱和度/容量指标”(如磁盘使用率 > 85%、TLS 证书 7 天后过期),依然需要配置静态阈值告警,但这部分告警级别通常设为 Warning,走工单或 IM 推送,白天处理即可,绝不能 Page 深夜的 On-Call。

    Q:对于流量极低的服务(比如每分钟只有几个请求),SLO 燃烧率计算会剧烈抖动,如何解决? A:低频服务的指标在计算 rate() 时极易出现“分母为0”或“1个错误=100%错误率”的噪音。解决方案是在 PromQL 中加入绝对流量过滤条件,例如 and sum by (job) (rate(http_requests_total[5m])) > 10,确保样本量具备统计学意义时才评估错误率。

    Q:如何定义异步消息队列(如 Kafka/RocketMQ 消费端)的 SLI? A:异步服务的核心用户体验不是“同步响应时间”,而是“消息堆积延迟”。SLI 可以定义为:过去 5 分钟内,99% 的消息从发送到被消费的端到端延迟(End-to-End Latency)小于 5 秒,或者更直白地以 Consumer Group 的 Lag 积压绝对值作为 SLI 指标,结合消费速率评估剩余处理时间(Time-to-critical)。