PromQL 与告警规则实战

学习 PromQL与告警规则实战 相关知识

PromQL 与告警规则实战


1. PromQL 基础

数据类型

类型说明示例
Instant Vector某一时刻的一组时间序列node_cpu_seconds_total
Range Vector一段时间内的一组时间序列node_cpu_seconds_total[5m]
Scalar单个数字1024
String字符串较少使用

选择器

# 精确匹配
node_memory_MemTotal_bytes{instance="10.0.0.1:9100"}

# 正则匹配(=~ 匹配,!= 不匹配,!~ 正则不匹配)
node_cpu_seconds_total{job=~"node.*", mode!="idle"}

# 范围选择(取过去 5 分钟的数据)
node_cpu_seconds_total[5m]

# 偏移(取 1 小时前的数据)
node_cpu_seconds_total offset 1h

2. 常用 PromQL 查询模式

2.1 CPU 相关

# CPU 使用率(排除 idle)
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# 每核 CPU 使用率
100 - (avg by(instance, cpu) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# CPU 负载 vs 核心数
node_load1 / count by(instance) (node_cpu_seconds_total{mode="idle"})

2.2 内存相关

# 内存使用率
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

# 实际已用内存(不含缓存/buffer)
node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes

# 内存压力(Available 过低告警用)
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.1

2.3 磁盘相关

# 磁盘使用率
100 - ((node_filesystem_avail_bytes{mountpoint="/",fstype!=""} / node_filesystem_size_bytes{mountpoint="/",fstype!=""}) * 100)

# 磁盘 IO 利用率
rate(node_disk_io_time_seconds_total[5m])

# 预测磁盘满时间(7 天趋势)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[7d], 86400 * 7)

2.4 网络相关

# 网络入流量(bytes/s)
rate(node_network_receive_bytes_total[5m])

# 网络错误率
rate(node_network_receive_errors_total[5m]) / rate(node_network_receive_bytes_total[5m])

2.5 Kubernetes 相关

# Pod CPU 使用率
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, namespace)

# Pod 内存使用率
sum(container_memory_working_set_bytes{container!=""}) by (pod, namespace)

# 容器重启次数
changes(kube_pod_container_status_restarts_total[1h])

# Node 的 Pod 密度
count(kube_pod_info) by (node)

# HPA 当前/最大副本比
kube_horizontalpodautoscaler_status_current_replicas / kube_horizontalpodautoscaler_spec_max_replicas

3. PromQL 进阶技巧

3.1 聚合操作

# 常用聚合函数
sum by(instance) (...)         # 分组求和
avg by(namespace) (...)        # 分组平均
max without(cpu) (...)         # 排除维度求最大
topk(3, ...)                   # 取前 3
quantile(0.95, ...)            # P95 分位数
count(...)                     # 计数

# 实例:各 namespace 的 Pod 内存使用 TOP 3
topk(3, sum by(namespace) (container_memory_working_set_bytes{container!=""}))

3.2 数学与比较运算

# 比较(返回 bool 0 或 1)
node_load1 > 2                 # 返回实际值(满足条件时)
node_load1 > bool 2            # 返回 0 或 1

# 向量匹配(一对一)
# 要求两边 label 完全一致
node_disk_io_time_seconds_total / node_disk_reads_completed_total

# 向量匹配(多对一) using group_left
# 允许左边有多余的 label
rate(node_cpu_seconds_total[5m]) / on(job) group_left(instance) node_cpu_count

3.3 时间与预测

# 过去 1 小时 vs 现在
avg_over_time(node_load1[1h])            # 1 小时平均
max_over_time(node_load1[1h])            # 1 小时内最大值

# 变化率与预测
deriv(node_load1[30m])                   # 30 分钟线性斜率
predict_linear(node_filesystem_avail_bytes[6h], 3600 * 24)   # 预测 24h 后

3.4 子查询与直方图

# 子查询:计算过去 1h 内每 5 分钟的 P99 延迟
quantile_over_time(0.99, http_request_duration_seconds{job="api"}[5m])[1h:5m]

# Histogram 使用(适用于请求延迟等已分桶指标)
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# P99
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# P50

4. Recording Rules

为什么需要 Recording Rules

  • 复杂查询预计算,Grafana 面板秒级加载
  • 减少 Prometheus 查询压力
  • 告警规则复用预计算指标

配置结构(Prometheus rules YAML)

groups:
  - name: node_recording_rules
    interval: 30s           # 每 30s 评估一次
    rules:
      - record: node:cpu_usage_percent:avg_5m
        expr: |
          100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

      - record: node:memory_usage_percent
        expr: |
          (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

      - record: node:disk_usage_percent:root
        expr: |
          100 - ((node_filesystem_avail_bytes{mountpoint="/",fstype!=""} / node_filesystem_size_bytes{mountpoint="/",fstype!=""}) * 100)

      - record: node:disk_fill_hours:root
        expr: |
          predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 3600 * 24) <= 0
          or
          node_filesystem_avail_bytes{mountpoint="/"} / deriv(node_filesystem_avail_bytes{mountpoint="/"}[6h]) / 3600

      - record: k8s:container_restart_changes_1h
        expr: |
          changes(kube_pod_container_status_restarts_total[1h])

      - record: k8s:pod_cpu_usage:avg_5m
        expr: |
          sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, namespace)

      - record: k8s:pod_memory_usage:bytes
        expr: |
          sum(container_memory_working_set_bytes{container!=""}) by (pod, namespace)

命名规范建议

<source>:<metric_name>:<aggregation>_<time_range>
# node:cpu_usage_percent:avg_5m
# k8s:pod_memory_usage:bytes
# app:request_latency:p99

5. Alerting Rules

5.1 节点级告警

groups:
  - name: node_alerts
    interval: 30s
    rules:
      - alert: NodeDown
        expr: up{job="node-exporter"} == 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "节点 {{ $labels.instance }} 已离线"
          description: "{{ $labels.instance }} 已离线超过 5 分钟"

      - alert: NodeCPUUsageHigh
        expr: node:cpu_usage_percent:avg_5m > 90
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "节点 {{ $labels.instance }} CPU 使用率过高"
          description: "CPU 使用率: {{ $value | humanizePercentage }}"

      - alert: NodeMemoryPressure
        expr: node:memory_usage_percent > 90
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "节点 {{ $labels.instance }} 内存不足"
          description: "内存使用率: {{ $value | humanizePercentage }}"

      - alert: NodeDiskFillPrediction
        expr: node:disk_fill_hours:root < 48
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "节点 {{ $labels.instance }} 根分区将在 48h 内写满"
          description: "预计剩余: {{ $value | humanizeDuration }}"

      - alert: NodeDiskUsageHigh
        expr: node:disk_usage_percent:root > 85
        for: 5m
        labels:
          severity: info
        annotations:
          summary: "节点 {{ $labels.instance }} 磁盘使用率超过 85%"

5.2 Kubernetes 告警

groups:
  - name: kubernetes_alerts
    interval: 30s
    rules:
      - alert: PodCrashLoopBackOff
        expr: kube_pod_status_phase{phase="Waiting"} > 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Pod {{ $labels.pod }} 处于 CrashLoopBackOff"

      - alert: PodRestartFrequent
        expr: k8s:container_restart_changes_1h > 5
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "Pod {{ $labels.pod }} 在 1h 内重启 {{ $value }} 次"

      - alert: PodMemoryLimitNear
        expr: (container_memory_working_set_bytes{container!=""} / container_spec_memory_limit_bytes{container!=""}) > 0.9
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Pod {{ $labels.pod }} 内存接近 limit"

5.3 服务级告警

groups:
  - name: service_alerts
    interval: 30s
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "服务错误率超过 5%"
          description: "当前错误率: {{ $value | humanizePercentage }}"

      - alert: HighLatency
        expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P99 延迟超过 2s"

6. 与 Grafana Alerting 集成

如果使用的是 Grafana Alerting(而非 Prometheus Alertmanager),有两种方式:

方式 A:Prometheus 规则 → Grafana 读取

Prometheus 保留 alerting rules 文件,Grafana 通过 Prometheus 数据源读取告警状态。这种方式告警触发和路由都在 Prometheus 侧。

方式 B:Grafana 管理规则

在 Grafana UI 中创建告警(Alert → Alert rules),直接基于 PromQL 查询。适合不想维护 YAML 文件的团队。

生产建议

  • 中小规模(< 100 个告警规则):用 Grafana Alerting UI 管理,规则可视化、审批流程方便
  • 大规模(> 100 个规则):用 Prometheus rules YAML,代码化(git 管理)+ CI 审查
  • Recording Rules 必须在 Prometheus 侧定义,Grafana 不支持 recording rules

Grafana Alerting 关键配置

# 告警分组(避免告警风暴)
Group by: severity, namespace

# 抑制规则(高级别告警静默低级别)
If critical for node X fired → silence warnings for node X

# 静默(计划的维护窗口)
Matchers: instance =~ "10.0.0.*"
Duration: 2h

7. 常见错误与排查

问题原因解决
查询返回空label 拼写错误、目标未 up先查 up{job="xxx"} 确认数据存在
告警不触发for 时间不够、表达式逻辑错Grafana 中测试表达式,缩短 for 时间测试
recording rule 不生效rules 文件语法错误promtool check rules rules.yaml 校验
Grafana 面板加载慢查询未用 recording rule将复杂查询改为 record: 规则
No data 在面板时间范围不对、数据源切换检查面板时间范围匹配 Prom 数据保留期

8. 面试表达框架

  1. 先说明 PromQL 的三种数据类型:Instant/Range/Scalar,以及各自适用场景
  2. 再讲常用模式:CPU/内存/磁盘/延迟的典型查询写法
  3. 再讲性能优化:Recording Rules 预计算、避免 raterate、合理使用 sum by
  4. 再讲告警设计:SLO 驱动、Multi-window Multi-burn-rate、分组抑制
  5. 最后讲实战经验:遇到过的告警风暴、规则误报调优、Recording Rules 迁移