监控故障的特点是"监控本身坏了最难发现"——监控系统不监控自己,挂了就静悄悄。Prometheus 故障的核心域:① 采集(target/scrape) ② 查询(PromQL/Grafana) ③ 告警(规则/Alertmanager) ④ 存储(TSDB/磁盘)。本卷按这四个域拆解高频故障。
排障前先想清楚问题在哪一段。指标缺失看 1-2,告警不响看 3-4,面板空白看 4-5。
# 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 refused | exporter 没启动 | 启动 exporter |
| connection timed out | 防火墙拦截 / 网络不通 | 开放端口 / 检查路由 |
| no such host | DNS 解析失败 | 检查 hosts/DNS |
| context deadline exceeded | 抓取超时(见 Case 03) | 调 scrape_timeout |
| 401 Unauthorized | 需要认证 | 配置 basic_auth/bearer_token |
| x509 cert | HTTPS 证书问题 | 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__
# 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]))
last_over_time(metric[5m]) 可以跳过 NaN。
# 看 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"}))
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
从数据源到查询到面板,每一层都要确认:
# 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 个点,稳定
-- 慢查询特征:大时间范围 + 高基数 + 复杂聚合 -- 错误:先 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 - 1h | 15s | 实时排障 |
| 1h - 6h | 1m | 近期趋势 |
| 6h - 24h | 5m | 日内回顾 |
| 1d - 7d | 30m | 周报 |
| > 7d | 1h+ | 长期分析 |
# 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 config routes test --config.file=alertmanager.yml severity=critical team=ops 模拟一条告警走哪个路由,不用真触发告警。
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>
# 看数据目录 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)。生产建议: