PROMETHEUS · GRAFANA · ALERTMANAGER

监控指标栈
故障排查手册

THE OPS FIELD MANUAL
Metrics · Targets · PromQL · Alerts
/-/healthy · /metrics
target down scrape timeout missing data PromQL alert storage

监控故障的特点是"监控本身坏了最难发现"——监控系统不监控自己,挂了就静悄悄。Prometheus 故障的核心域:① 采集(target/scrape) ② 查询(PromQL/Grafana) ③ 告警(规则/Alertmanager) ④ 存储(TSDB/磁盘)。本卷按这四个域拆解高频故障。

监控数据流路径

排障前先想清楚问题在哪一段。指标缺失看 1-2,告警不响看 3-4,面板空白看 4-5。

1
EXPORTER
暴露 /metrics
2
SCRAPE
Prometheus 抓取
3
TSDB
本地时序存储
4
PROMQL
查询/告警计算
5
GRAFANA / AM
展示/发告警
01

Target Down — 抓不到目标

SCRAPE TARGET UNREACHABLE
高频 入门
  • Prometheus UI → Status → Targets,看到某个 target 是 DOWN
  • 该 target 的指标在 Grafana 上断流
  • up{job="xxx"} == 0
# 1. Web UI 看 target 状态
# http://prometheus:9090/targets
# 看 Last Error 列的具体错误

# 2. 命令行查 up 指标
curl 'http://prometheus:9090/api/v1/query?query=up' | jq

# 3. Prometheus 所在机器直连 target 测试
curl -v http://<target-ip>:9100/metrics
# 看是否能访问到 exporter 的 /metrics 接口

# 4. 在 target 机器看 exporter 进程
ss -lntp | grep 9100
systemctl status node_exporter
Last Error原因解决
connection refusedexporter 没启动启动 exporter
connection timed out防火墙拦截 / 网络不通开放端口 / 检查路由
no such hostDNS 解析失败检查 hosts/DNS
context deadline exceeded抓取超时(见 Case 03)调 scrape_timeout
401 Unauthorized需要认证配置 basic_auth/bearer_token
x509 certHTTPS 证书问题tls_config: insecure_skip_verify: true
# K8s service discovery,target 列表是动态的
# target 突然消失可能是 service/pod 被删了,正常

scrape_configs:
  - job_name: 'kubernetes-pods'
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # 只抓有 annotation 的 pod
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      # 使用 annotation 指定端口
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        target_label: __address__
02

指标缺失 / 断断续续

METRIC GAPS AND MISSING DATA
高频 进阶
  • Grafana 图表有空缺、断点
  • 某些 label 组合的指标突然消失
  • up == 1(target 正常),但具体指标没了
  • label 变化:Pod 重启后 instance label 变了,旧 series 停止更新
  • stale marker:Prometheus 在 target down 5 分钟后标记为 stale
  • 采集间隔大:scrape_interval 15s,但面板间隔 5s,看起来稀疏
# 1. 看指标的所有 label
count by(__name__, job, instance)({job="api"})

# 2. 看 series 数量变化(突然下降说明有 series 消失)
count({__name__=~".+"})

# 3. 显示 label 不连续的位置
absent_over_time(http_requests_total{job="api"}[5m])

# 4. 检查 staleness
last_over_time(up[1h])
# 用稳定的 label 替代 instance(常变)
relabel_configs:
  # 用 pod 名作为 instance(也会变,但比 IP 稳)
  - source_labels: [__meta_kubernetes_pod_name]
    target_label: pod

  # 用 deployment 名作为更稳定的维度
  - source_labels: [__meta_kubernetes_pod_label_app]
    target_label: app

# 查询时用稳定的维度聚合
# 不好:rate(http_requests_total{instance="10.0.0.1:8080"}[5m])
# 好:  sum by(app)(rate(http_requests_total[5m]))
stale marker 机制 · target 不再返回某个 series,Prometheus 在 5 分钟后给该 series 写入 NaN 标记。这是正常行为,避免老数据"假装还在更新"。查询时用 last_over_time(metric[5m]) 可以跳过 NaN。
03

scrape 超时 / 抓取慢

SCRAPE TIMEOUT AND HIGH CARDINALITY
中频 进阶
  • Last Error: context deadline exceeded
  • target 时好时坏(scrape duration 接近 timeout)
  • Prometheus 本身负载高
# 看 scrape 耗时
curl 'http://prometheus:9090/api/v1/query?query=scrape_duration_seconds' | jq

# 找出耗时长的 target
curl 'http://prometheus:9090/api/v1/query?query=topk(10,scrape_duration_seconds)'
# 全局设置
global:
  scrape_interval: 30s       # 默认 1m,频繁监控调小
  scrape_timeout: 10s        # 必须小于 interval
  evaluation_interval: 30s   # 告警规则计算间隔

# 单个 job 覆盖
scrape_configs:
  - job_name: 'slow-exporter'
    scrape_interval: 60s     # 这个慢,单独调长
    scrape_timeout: 30s
    static_configs:
      - targets: ['slow:9999']
# 看哪个指标的 series 最多(高基数元凶)
topk(10, count by(__name__)({__name__=~".+"}))

# 看哪个 label 取值最多
count(count by(<label_name>)({__name__="problematic_metric"}))
高基数杀手 · 把用户 ID、请求 URL 完整路径、UUID作为 label,会产生百万级 series,直接撑爆 Prometheus。规则:label 取值数应是有限的、稳定的(状态码、方法、服务名 ✓;用户 ID、订单号 ✗)。
scrape_configs:
  - job_name: 'api'
    static_configs:
      - targets: ['api:8080']
    metric_relabel_configs:
      # 删除某个高基数指标
      - source_labels: [__name__]
        regex: 'http_request_duration_per_user_seconds'
        action: drop

      # 删除某个 label(降低基数)
      - regex: 'user_id'
        action: labeldrop
04

Grafana 面板空白 — No data

DASHBOARD SHOWS NO DATA
高频 入门

从数据源到查询到面板,每一层都要确认:

  • 数据源层:Grafana 能连到 Prometheus?
  • 查询层:PromQL 在 Prometheus 自己能查出来吗?
  • 展示层:面板的变量、时间范围、单位有没有问题?
# 1. Grafana → Configuration → Data Sources → Test 数据源
# 失败 → 看 Grafana 容器/进程能否访问 Prometheus URL

# 2. 直接在 Prometheus UI 跑查询
# http://prometheus:9090/graph
# 把 Grafana 面板的 PromQL 复制过去,看有没有结果

# 3. 看面板的 Query Inspector
# Grafana 面板右上角 → Inspect → Query
# 看实际发出的查询(变量替换后)和返回
# 错误:变量值有空格/特殊字符
rate(http_requests_total{instance="$instance"}[5m])
# 如果 $instance 包含多个值,需要用正则匹配

# 正确:用 =~ 接受变量的多选
rate(http_requests_total{instance=~"$instance"}[5m])

# Grafana 变量定义时:
# Include All Option ✓
# All values: .*       (用正则匹配所有)
# 错误:面板用 rate(...[5m]),但时间选了 5 分钟
# rate 至少需要 2 个数据点,在窗口刚好的边界可能没数据

# 正确:rate 窗口建议是 scrape_interval 的 4 倍以上
rate(http_requests_total[5m])
# 如果 scrape_interval=15s,5m 窗口有 20 个点,稳定
Grafana 没数据的 90% 原因 · ① 时间范围不对 ② 变量没值或被过滤光了 ③ PromQL 在 Prometheus 里也查不到。先把 PromQL 复制到 Prometheus 验证,再回 Grafana 看变量。
05

Grafana 查询慢 / 面板卡顿

SLOW PROMQL OPTIMIZATION
中频 进阶
-- 慢查询特征:大时间范围 + 高基数 + 复杂聚合

-- 错误:先 rate 再聚合(对每个 series 都算 rate,慢)
sum(rate(http_requests_total[5m]))

-- 一样意思,但写法更清晰
sum(rate(http_requests_total{}[5m]))

-- 错误:用 increase 而不是 rate 做时间序列(慢且不直观)
increase(http_requests_total[5m]) / 300

-- 正确:rate 更高效
rate(http_requests_total[5m])

-- 优化:过滤越早越好
sum by(job)(rate(http_requests_total{job="api",status=~"5.."}[5m]))
# 复杂指标提前算好存储,Grafana 直接读结果

# /etc/prometheus/rules/recording.yml
groups:
  - name: http_recording
    interval: 30s
    rules:
      - record: job:http_requests:rate5m
        expr: sum by(job)(rate(http_requests_total[5m]))

      - record: job:http_errors:rate5m
        expr: sum by(job)(rate(http_requests_total{status=~"5.."}[5m]))

      - record: job:http_error_ratio:rate5m
        expr: job:http_errors:rate5m / job:http_recording:rate5m

# Grafana 直接查 job:http_error_ratio:rate5m,极快
时间范围step 建议面板适用
5m - 1h15s实时排障
1h - 6h1m近期趋势
6h - 24h5m日内回顾
1d - 7d30m周报
> 7d1h+长期分析
面板太多杀手 · 一个 Dashboard 加载 50 个图表,每个图表 1-2 个查询,刷新一次发 100+ 请求到 Prometheus。建议:① 用 row 折叠 ② 关键指标拆独立 Dashboard ③ 大范围分析关闭自动刷新。
06

Alertmanager 不发告警

ALERT NOT FIRING / NOT DELIVERED
高频 致命
  • 规则没生效:rules 文件没加载,或 PromQL 语法错
  • 规则触发了但没到 Alertmanager:Prometheus 没配 alerting
  • Alertmanager 收到了但被静音/抑制
  • 路由/receiver 配置错:走到默认 receiver 但默认没配通知
# Step 1. Prometheus → Status → Rules 看规则是否加载

# Step 2. Prometheus → Alerts 看规则当前状态
# Inactive  规则正常,未触发
# Pending   触发了,但还没到 for 持续时间
# Firing    已发送到 Alertmanager

# Step 3. Alertmanager → Alerts 看是否收到
curl http://alertmanager:9093/api/v1/alerts

# Step 4. Alertmanager → Status 看配置
curl http://alertmanager:9093/api/v1/status
groups:
  - name: server_health
    rules:
      - alert: InstanceDown
        expr: up == 0
        for: 5m              # 持续 5 分钟才告警(避免抖动)
        labels:
          severity: critical
          team: ops
        annotations:
          summary: "Instance {{ $labels.instance }} down"
          description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for > 5m"

      - alert: HighCpuUsage
        expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 10m
        labels:
          severity: warning
alerting:
  alertmanagers:
    - static_configs:
        - targets: ['alertmanager:9093']

rule_files:
  - 'rules/*.yml'

# 改完必须 reload
# curl -X POST http://prometheus:9090/-/reload
route:
  group_by: ['alertname', 'cluster']
  group_wait: 30s              # 同组等待
  group_interval: 5m           # 组内新告警等待
  repeat_interval: 4h          # 重复发送间隔
  receiver: 'default'

  routes:
    - match:
        severity: critical
      receiver: 'sre-pager'
      continue: true           # 继续匹配后续路由

    - match:
        team: ops
      receiver: 'ops-wechat'

receivers:
  - name: 'default'
    webhook_configs:
      - url: 'http://webhook:9000/default'

  - name: 'ops-wechat'
    wechat_configs:
      - api_url: 'https://qyapi.weixin.qq.com/...'
        agent_id: '1000002'
        corp_id: 'xxx'
        to_user: '@all'
amtool 调试神器 · amtool config routes test --config.file=alertmanager.yml severity=critical team=ops 模拟一条告警走哪个路由,不用真触发告警。
07

告警风暴 — 一秒收 100 条

ALERT FLOOD CONTROL
高频 进阶
  • 一次故障,微信/邮件被告警刷屏
  • 不同 instance 的同类问题分别发,几百条
  • 真正的根因告警被淹没
route:
  # 把同样 alertname 和 cluster 的告警合并成一条
  group_by: ['alertname', 'cluster']

  # 关键参数
  group_wait: 30s         # 第一条等 30 秒,收集同组其他告警
  group_interval: 5m      # 同组新告警来,等 5 分钟再发
  repeat_interval: 4h     # 已发过的告警,4 小时后才重发
inhibit_rules:
  # 节点 down 时,抑制该节点上所有服务的告警
  - source_match:
      alertname: 'InstanceDown'
    target_match_re:
      alertname: '.*'
    equal: ['instance']

  # critical 告警时,抑制同 service 的 warning
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['service']
# 临时维护期间静默(命令行)
amtool silence add \
  alertname="InstanceDown" \
  instance=~"10\\.0\\.0\\..+" \
  --duration=2h \
  --comment="机房 A 维护" \
  --author="lan"

# 列出当前静默
amtool silence query

# 过期/取消静默
amtool silence expire <silence-id>
规则设计原则 · 别为每个 instance 写一条规则,要写聚合规则。例如不写 100 条"node-01 CPU 高、node-02 CPU 高...",写 1 条"任意节点 CPU 高",label 自动带 instance 信息,但告警合并到一组。
08

Prometheus 磁盘满 / OOM

STORAGE AND MEMORY MANAGEMENT
中频 进阶
# 看数据目录
du -sh /prometheus/data

# 关键指标(在 Prometheus 自己上查)
prometheus_tsdb_storage_blocks_bytes        # 磁盘占用
prometheus_tsdb_head_series                 # 内存中 series 数
prometheus_tsdb_head_samples_appended_total # 写入速率
process_resident_memory_bytes               # 进程内存
# 启动参数
--storage.tsdb.path=/prometheus/data
--storage.tsdb.retention.time=30d        # 保留 30 天
--storage.tsdb.retention.size=500GB      # 上限 500GB,优先级高于 time

# 内存优化
--storage.tsdb.head-chunks-write-queue-size=0
--query.max-samples=50000000
# 1. 临时调短保留时间(立即生效)
curl -XPOST "http://prometheus:9090/-/reload"

# 2. 手动删除老数据(谨慎)
curl -XPOST -g 'http://prometheus:9090/api/v1/admin/tsdb/delete_series?match[]={__name__=~".+"}&start=2025-01-01T00:00:00Z&end=2025-06-01T00:00:00Z'
# 需要 --web.enable-admin-api 启动参数

# 3. 然后清理标记的数据
curl -XPOST "http://prometheus:9090/api/v1/admin/tsdb/clean_tombstones"

# 4. 看 head series 数,过高就找元凶(高基数指标)
topk(10, count by(__name__)({__name__=~".+"}))

Prometheus 本地存储不适合长期保留(months/years)。生产建议:

  • Thanos:对象存储 + 多集群查询
  • VictoriaMetrics:更省资源,兼容 PromQL
  • Mimir:Grafana Labs 出品,大规模 SaaS 友好
资源容量规划 · 经验值:每百万 series 约占用 5GB 内存 + 6GB/天磁盘(15s 间隔)。1000 个 target × 平均 1000 series = 100 万 series,需要 5-8GB 内存,30 天数据 ~180GB 磁盘。
CONTINUE TO
ELK 日志栈故障排查手册 →