现代监控告警体系从零到生产:Prometheus + Grafana + Alertmanager 全栈实战 🚀

O&M 0 次阅读
现代监控告警体系从零到生产:Prometheus + Grafana + Alertmanager 全栈实战 🚀

手把手搭建可观测性三件套,用数据驱动运维决策,让故障在用户发现之前就被消灭。从单机监控到多集群联邦,本文覆盖生产级监控的全部关键知识点。

目录

  1. 背景:为什么监控是运维的生命线
  2. 可观测性三大支柱:Metrics、Logs、Traces
  3. Prometheus 核心概念深度解析
  4. 实战:从零搭建 Prometheus 监控体系
  5. Grafana 可视化:让数据开口说话
  6. Alertmanager 告警管理:从噪音到精准通知
  7. 进阶:大规模场景下的监控架构演进
  8. 常见问题 FAQ
  9. 总结与展望

一、背景:为什么监控是运维的生命线

想象一下这个场景:凌晨三点,你被手机铃声惊醒,客户说网站打不开了。你睡眼惺忪地打开电脑,手动检查服务器状态、数据库连接、应用日志...半小时后才发现只是某个微服务的健康检查超时,重启就恢复正常了。

这就是没有监控体系下的运维日常 —— 被动、低效、痛苦

现代 IT 基础设施已经从过去的"几台物理机"演变为"数百个容器、微服务、Serverless、云资源"的复杂混合体。在这种环境下,监控告警不是可有可无的附加品,而是运维的生命线

1.1 监控系统要回答的四个问题

一个好的监控系统应该能回答运维团队的四个核心问题。每个问题对应不同的监控手段和工具选型:

问题层级 对应手段 典型工具 时间要求
系统是否在正常运转? 黑盒监控(端口、HTTP 探活) Blackbox Exporter、Uptime Kuma 秒级
哪里出了故障? 白盒监控(指标、日志) Prometheus、ELK、Loki 分钟级
为什么出故障? 分布式追踪 Jaeger、Tempo、Zipkin 交互式
如何预防下一次故障? 趋势分析、容量规划 Grafana Dashboard、Thanos 小时/天级

这四个问题形成了一个完整的故障处理闭环。最理想的状态是:问题在第一个层次就被发现并解决,后续三层只作为根因分析的参考

1.2 从 Nagios 到云原生:监控工具的进化简史

如果你是一个老运维人,你可能会记得 Nagios 时代。那些年我们手写插件脚本,配置上千行的配置文件,告警通知靠短信猫。时代变了,监控工具也经历了翻天覆地的变化:

  • 2000 年代:Nagios/Zabbix 称霸。SNMP 协议、Agent 推送、静态配置文件。优点是稳定可靠,缺点是配置繁琐,扩展困难
  • 2012 年:Google 发表 Borgmon 论文,提出"时间序列 + 标签"的数据模型和 rate() 等查询函数,奠定了现代监控理论基础
  • 2015 年:Prometheus 加入 CNCF,成为继 Kubernetes 之后第二个毕业项目。SoundCloud 开源的这个小项目迅速成为云原生标配
  • 2018 年:OpenTelemetry 成立,CNCF 将 OpenTracing 和 OpenCensus 合并,统一了可观测性数据采集标准
  • 2022 年:Prometheus 成为事实上的监控标准,几乎所有云原生项目(K8s、Etcd、CoreDNS、Istio)都原生暴露 Prometheus metrics
  • 2024-2025 年:eBPF + Prometheus 结合实现 零侵入监控 成为新趋势。Cilium、Pixie 等工具可以在不修改应用代码的情况下捕获网络和系统调用指标

1.3 监控文化:工具的尽头是文化

监控不是买一套工具装上就完事。真正的监控文化需要整个团队从认知到行动的统一:

  1. 从设计阶段就考虑可观测性:新服务上线前必须有健康检查端点 /health、metrics 暴露端口 /metrics、以及对应的告警规则。这不是运维的要求,是架构设计的一部分
  2. 告警必须可执行:每一条告警通知都应该告诉接收者"做什么",而不是仅仅告知"发生了什么"。带上 runbook 链接是最低要求
  3. 减少告警疲劳:宁少勿多。如果一条告警连续触发一周都没人处理,直接删掉或者降级。告警质量比数量重要得多
  4. 事后复盘不是追责:故障后的"无责事后复盘"文化比任何监控工具都重要。复盘的目标是找出系统层面的改进点,而非找个人背锅
  5. 监控即代码:告警规则、仪表盘 JSON、Prometheus 配置文件全部纳入 Git 管理,走 CI/CD 流程部署

好了,概念铺垫够了。下面我们进入正题,从零开始搭建一个现代化的监控告警体系。


二、可观测性三大支柱:Metrics、Logs、Traces

在深入具体工具之前,我们需要理解"可观测性"的三大数据支柱。它们就像盲人摸象 —— 单独看每一类数据都有局限,合在一起才能还原系统全貌。

2.1 Metrics:数字告诉你"怎么了"

Metrics(指标)是聚合的数字时间序列,回答"系统是否正常"的问题。

特点剖析

Metrics 是三者中存储成本最低、查询最快的数据类型。一个时间序列本质上是 (timestamp, value) 的数组,经过压缩后非常紧凑。Prometheus 的 TSDB 能在 1 秒内扫描数百万个样本点。

但 Metrics 的代价也很明显:信息丢失。当你在 Prometheus 里看到 http_errors_total=1000,你只知道有 1000 个错误,但不知道是哪个用户、什么请求体造成的。这就需要日志和追踪来补充。

四种指标类型详解

类型 行为 可用的 PromQL 函数 典型场景
Counter 只增不减(重启归零) rate(), increase(), irate() HTTP 请求数、错误数、任务完成数
Gauge 可增可减 直接使用, delta(), deriv(), predict_linear() CPU 使用率、内存、连接数、温度
Histogram 自动分桶计数 histogram_quantile() 请求延迟分布、响应大小分布
Summary 客户端计算分位数 直接读取 _sum / _count 客户端 P99(不推荐,详见下文)

Histogram vs Summary —— 一个重要选择

这是 Prometheus 新手最容易踩的坑。两者都能统计延迟分布,但行为截然不同:

# Histogram:服务端计算分位数,灵活但需要预定义桶
# 可以在查询时计算任意分位数
histogram_quantile(0.99, rate(http_duration_seconds_bucket[5m]))
histogram_quantile(0.999, rate(http_duration_seconds_bucket[5m]))

# Summary:客户端计算分位数,查询快但不可聚合
# 两个不同实例的 P99 无法数学合并!
http_duration_seconds{quantile="0.99"}

结论:99% 的场景用 Histogram。Summary 只在单实例且不需要跨实例聚合时使用。

PromQL 深度实践

# Counter 的魔法:rate() 将"计数"转化为"速率"
# 注意:[5m] 窗口选择:太短噪声大,太长反应慢
rate(http_requests_total{job="api"}[5m])

# Histogram + Apdex 得分计算
# Apdex = (满意数 + 容忍数/2) / 总数
(
  sum(rate(http_duration_seconds_bucket{le="0.1"}[5m]))  # T < 0.1s(满意)
  +
  sum(rate(http_duration_seconds_bucket{le="0.5"}[5m]))   # T < 0.5s(包括容忍)
  - sum(rate(http_duration_seconds_bucket{le="0.1"}[5m])) # 减去满意数得到容忍数
  / 2
) / sum(rate(http_duration_seconds_count[5m]))

# Gauge 趋势预测:提前发现磁盘告急
predict_linear(node_filesystem_free_bytes{mountpoint="/"}[1h], 4 * 3600) < 0

# 同环比分析:当前 QPS vs 昨天同一时刻
rate(http_requests_total[5m]) 
/ 
rate(http_requests_total[5m] offset 1d)

2.2 Logs:文字告诉你"为什么"

日志是带时间戳的文本事件,回答"为什么出问题"。

Prometheus 本身不处理日志,但在实际运维中,通常配合 Loki 或 ELK 使用。一个典型的故障排查流程是:

  1. Grafana 面板显示 5xx 错误率飙升(Metrics 发现问题)
  2. 点击面板上的链接跳转到 Loki,自动带上时间范围和 job 标签
  3. Loki 显示该时间段内的错误日志,看到 NullPointerException at UserService.java:42
  4. 复制日志中的 trace_id,在 Jaeger 中查看完整的分布式追踪
  5. 发现是下游数据库查询超时导致的雪崩

日志最佳实践

  • 结构化日志(JSON 格式):{"level":"ERROR","msg":"db timeout","latency_ms":3200,"trace_id":"abc123"} 比纯文本 [ERROR] db timeout after 3200ms 好用 100 倍
  • 每个请求带上 trace_id:这是关联 metrics + logs + traces 的黄金钥匙
  • 日志级别分五档:DEBUG(开发调试)< INFO(关键流程节点)< WARN(潜在问题)< ERROR(需要人工介入)< FATAL(服务即将崩溃)
  • 敏感信息脱敏:密码、Token、身份证号、手机号不能出现在日志里。可以用正则替换:re.sub(r'(password=)[^&\s]+', r'\1***', log_line)

Loki 特色功能

# 按 job 统计错误率(类似 Prometheus,但基于日志)
sum(rate({job="api"} |= "ERROR" [5m])) by (job)

# 提取日志中的延迟数值并计算 P99
quantile_over_time(0.99,
  {job="api"} | json | unwrap latency_ms [5m]
) by (endpoint)

2.3 Traces:追踪告诉你"哪里慢了"

分布式追踪记录一个请求在多个服务间的完整路径,回答"哪里是瓶颈"。

核心概念剖析

  • Trace:一个用户请求的完整调用链。比如"用户下单"这个操作经过了 API Gateway → Order Service → Inventory Service → Payment Service → Database,总共 5 个 Span
  • Span:Trace 中的一个操作单元。每个 Span 包含操作名、开始时间、持续时间、标签(如 db.statementhttp.status_code)和父子关系
  • Context Propagation:通过 HTTP Header 在服务间传递 traceparent: 00-{trace_id}-{span_id}-01,确保下游服务知道自己是哪个 Trace 的一部分

Jaeger 典型查询场景

# 查询延迟超过 1s 的 trace(找慢请求)
service="api-gateway" AND duration > 1s

# 查询包含错误的 trace(找故障)
service="order-service" AND error=true

# 查询特定用户的请求(找具体问题)
service="api-gateway" AND tags.user_id="12345"

在代码中添加追踪(OpenTelemetry 示例)

from opentelemetry import trace
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

# 自动插桩:零代码改动的魅力
FlaskInstrumentor().instrument_app(app)

# 手动创建 Span
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("process_order") as span:
    span.set_attribute("order.id", order_id)
    span.set_attribute("order.amount", amount)
    # ... 业务逻辑 ...
    # Span 结束时自动记录耗时

2.4 三位一体:USE 方法和 RED 方法

光有数据还不够,你需要监控方法论来指导"监控什么"。这是运维工程师从"会用工具"到"会用脑子"的跨越。

USE 方法(面向资源)

由 Brendan Gregg 提出。对于每个硬件资源(CPU、内存、网络接口、磁盘),回答三个问题:

实践模板 —— 完整的 USE 检查清单

# ========== CPU ==========
# Utilization(利用率):每个 CPU 核心的忙碌百分比
100 - (avg by(instance, cpu)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Saturation(饱和度):等待 CPU 的进程数
rate(node_context_switches_total[5m])
# 或使用 node_load1 / node_load5 判断

# Errors:硬件错误(极少,但很关键)
rate(node_cpu_package_throttles_total[5m]) > 0  # CPU 降频

# ========== Memory ==========
# Utilization:已用内存占比
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

# Saturation:Swap 使用率(内存不足时触发交换)
(node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes) 
  / node_memory_SwapTotal_bytes > 0.5

# Errors:OOM Kill 次数
increase(node_vmstat_oom_kill[1h])

# ========== Network ==========
# Utilization:网络带宽使用率
rate(node_network_receive_bytes_total[5m]) * 8 / 1e9  # Gbps

# Saturation:网卡丢包(比带宽满更可怕)
rate(node_network_receive_drop_total[5m]) > 0

# Errors:网卡错误包
rate(node_network_receive_errs_total[5m]) > 0

# ========== Disk ==========
# Utilization:磁盘 IO 使用率
rate(node_disk_io_time_seconds_total[5m]) * 100

# Saturation:IO 等待队列长度
rate(node_disk_io_time_weighted_seconds_total[5m])

# Errors:已用空间
(node_filesystem_size_bytes - node_filesystem_free_bytes) 
  / node_filesystem_size_bytes > 0.9

RED 方法(面向服务)

由 Tom Wilkie 提出。对于每个微服务,监控三件事:

# Rate(请求速率):每秒请求数
sum(rate(http_requests_total{job="api-gateway"}[5m]))

# Errors(错误率):失败请求占比
sum(rate(http_requests_total{job="api-gateway",status=~"5.."}[5m])) 
  / sum(rate(http_requests_total{job="api-gateway"}[5m]))

# Duration(延迟):P95 和 P99 延迟
histogram_quantile(0.95, 
  sum(rate(http_request_duration_seconds_bucket{job="api-gateway"}[5m])) by (le)
)
histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket{job="api-gateway"}[5m])) by (le)
)

USE + RED 的分工

  • 基础设施团队关注 USE(CPU、内存、网络、磁盘)
  • 应用/SRE 团队关注 RED(请求速率、错误率、延迟)
  • 两者在 Grafana 同一个 Dashboard 中并排展示,上 USE 下 RED,一目了然

2.5 Google 四大黄金信号

Google SRE 书提出了比 RED 更全面的监控框架,增加了一个 Saturation 维度:

信号 含义 PromQL 示例
Latency 请求耗时 histogram_quantile(0.99, rate(duration_bucket[5m]))
Traffic 请求量 rate(http_requests_total[5m])
Errors 失败率 rate(http_requests_total{status=~"5.."}[5m])
Saturation 资源饱和 predict_linear(node_filesystem_free[1h], 4*3600) < 0

这四个信号合在一起,足以覆盖绝大多数服务。新服务上线时先把这四个定义好,再考虑定制指标。


三、Prometheus 核心概念深度解析

Prometheus 是整个云原生监控生态的核心。理解它的设计哲学,能让你少走很多弯路。

3.1 架构设计:Pull 模型为王

Prometheus 采用**主动拉取(Pull)**的数据采集模式,而不是像 Zabbix 那样等 Agent 推数据。这个设计选择深刻影响了整个生态。

为什么 Pull 更好?深入分析

       Pull 模式                           Push 模式
  ┌──────────────┐                  ┌──────────────┐
  │  Prometheus   │──GET /metrics──▶│    App        │
  │    Server     │                 │  Instance     │
  │              │                  │              │
  │  控制一切     │                  │  负责推送     │
  └──────────────┘                  └──────┬───────┘
                                           │
                                    ┌──────▼───────┐
                                    │   监控中心    │
                                    │  (Zabbix 等) │
                                    └──────────────┘

Pull 模式在云原生环境下的六大优势:

  1. 自动服务发现:结合 Kubernetes SD,新 Pod 上线后自动被 Prometheus 发现并拉取,无需手动注册
  2. 双重故障检测:如果拉取失败,说明目标本身就有问题(网络不通、进程挂了、端口未监听)—— 既是监控也是健康检查
  3. 统一控制面:采集频率、超时时间、重试策略由 Prometheus 服务端统一管理,不需要去每个被监控节点调整配置
  4. 安全边界清晰:不需要向每个被监控对象开放入站端口(Prometheus 主动出站连接)
  5. 压力可控:Prometheus 决定何时拉取,避免了所有 Agent 同时推送造成的服务端压力尖峰
  6. 调试友好:任何目标都可以直接 curl /metrics 查看原始数据,排查问题一目了然

Pushgateway 的正确使用场景

Pull 模式有一个天然缺陷:短生命周期任务(CronJob、批处理)在 Prometheus 下次拉取之前可能已经结束了。这时才需要用 Pushgateway:

# CronJob 执行完后把结果推送到 Pushgateway
curl -X POST http://pushgateway:9091/metrics/job/batch_job/instance/daily_report \
  --data-binary @- << EOF
batch_job_duration_seconds{status="success"} 342
batch_job_processed_count{status="success"} 15000
EOF

⚠️ 注意:Pushgateway 是临时中转站而非持久存储。不要用它替代 Prometheus 原生的 Pull 采集。

3.2 数据模型:时间序列的标签魔法

Prometheus 的数据模型看似简单,实则威力巨大。每个指标是一个时间序列,由指标名和一组标签(key-value)唯一标识:

http_requests_total{method="GET", handler="/api/users", status="200", job="api"} 12345 @ 1716940800

上面的例子表示:在时间戳 1716940800,api 这个 Job 中的 /api/users 接口收到了第 12345 个返回 200 的 GET 请求。

标签设计的黄金法则

✅ 应该用标签 ❌ 不应该用标签 原因
method、status_code、endpoint user_id、session_id、email 高基数:百万级唯一值会撑爆内存
instance、job、environment request_body、response_body 数据体积:标签值是索引的一部分
datacenter、region、version timestamp 语义错误:时间是维度不是标签
le(Histogram 桶边界) random_request_id 无聚合意义

高基数陷阱的数学解释

假设你有 1000 个 endpoint × 5 个 method × 10 个 status = 50,000 个时间序列。每个序列每秒 1 个样本,每个样本约 3 字节,一天的数据量 ≈ 50,000 × 86400 × 3 ≈ 13GB。如果再加上 user_id 标签(10万用户),序列数直接爆炸到 50 亿,这是任何单机都承受不了的。

最佳实践

# ❌ 危险:user_id 作为标签,百万级唯一值
http_requests_total{user_id="12345", endpoint="/api/order"}

# ✅ 正确:用 Histogram 按延迟分布统计,不暴露 user_id
http_request_duration_seconds_bucket{endpoint="/api/order", le="0.1"}

# ✅ 正确:如果确实需要按用户分析,在 Grafana 中按时间过滤而非按 user_id

3.3 PromQL:监控领域的 SQL

PromQL 是 Prometheus 的查询语言,也是监控工程师的核心技能。下面我们从基础到高级系统学习。

数据类型:PromQL 有四种表达式类型:

类型 说明 示例
Instant Vector 同一时刻的一组时间序列 http_requests_total
Range Vector 一段时间窗口内的样本 http_requests_total[5m]
Scalar 单个浮点数 3.14
String 字符串(很少用) "hello"

核心函数速查表

# ==== Counter 专用函数 ====
rate(http_requests_total[5m])           # 每秒增长率(最常用)
irate(http_requests_total[5m])          # 瞬时增长率(适合长窗口查询)
increase(http_requests_total[1h])       # 1小时内增量(适合做 SLA 报表)
resets(http_requests_total[1h])         # Counter 重置次数(判断是否重启)

# ==== Gauge 专用函数 ====
delta(cpu_temperature_celsius[5m])      # 差值
deriv(node_filesystem_free_bytes[1h])   # 线性回归斜率(趋势)
predict_linear(node_free[1h], 3600)     # 预测值(容量规划必备)
idelta(cpu_temperature[5m])             # 最后两个样本的差值

# ==== 聚合函数 ====
sum(rate(requests[5m])) by (job)                         # 按 job 求和
avg(node_cpu_seconds_total) by (instance)                # 按实例平均
topk(5, rate(http_requests_total[5m]))                   # Top 5
bottomk(3, node_memory_MemAvailable_bytes)               # Bottom 3
count_values("version", app_version)                      # 统计版本分布
stddev(rate(latency[5m]))                                # 标准差(衡量稳定性)
quantile(0.95, rate(latency[5m]))                        # 聚合分位数

# ==== 向量匹配 ====
# 一对一匹配:除法运算中自动按标签匹配
rate(errors[5m]) / rate(requests[5m])

# 多对一匹配:把 sum 结果广播到每个实例
rate(errors{job="api"}[5m])
  / on(job) group_left
sum(rate(requests{job="api"}[5m])) by (job)

# ==== 时间操作 ====
rate(requests[5m] offset 1d)   # 昨天同时段的速率(同环比)
rate(requests[5m] offset -1h)  # 一小时前的速率

Recording Rules:查询加速的秘密武器

如果你有一个很复杂的 PromQL 经常被用到(如在 Grafana 仪表盘中),可以用 Recording Rule 把结果预先计算好:

# rules/recording_rules.yml
groups:
  - name: node_recording_rules
    interval: 30s
    rules:
      # 预计算 CPU 利用率,后续查询直接使用 instance:node_cpu:rate5m
      - record: instance:node_cpu_utilization:rate5m
        expr: |
          100 
          - (avg by(instance, cpu)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
      
      # 预计算错误率
      - record: job:http_errors:rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
          / 
          sum(rate(http_requests_total[5m])) by (job)
      
      # 预计算 P99 延迟(这是最值的,P99 计算很贵)
      - record: job:http_request_duration:p99_5m
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le)
          )

有了 Recording Rules,联邦抓取时只拉预聚合指标,性能提升数十倍。

3.4 Exporter 生态:万物皆可监控

Prometheus 自己不采集数据,它通过 Exporter 把各种系统的内部指标暴露为标准 /metrics 端点。

常用 Exporter 全景表

Exporter 监控对象 关键指标 默认端口
Node Exporter Linux 服务器 CPU、内存、磁盘 IO、网络、系统负载 9100
cAdvisor Docker 容器 每个容器的 CPU/Memory/IO 用量 8080
Blackbox Exporter 外部 URL 探活 HTTP 状态码、TLS 到期、DNS 解析 9115
MySQL Exporter MySQL 数据库 连接数、慢查询、复制延迟、InnoDB 缓冲池 9104
Redis Exporter Redis 内存使用、命中率、连接数、key 过期 9121
PostgreSQL Exporter PostgreSQL 连接数、查询延迟、死锁、表大小 9187
NGINX Exporter Nginx 请求数、活跃连接、响应时间 9113
kube-state-metrics K8s 对象状态 Pod 状态、Deployment 副本数、Node 状况 8080
JMX Exporter Java 应用 JVM 堆内存、GC 暂停、线程数 随应用
Elasticsearch Exporter ES 集群 集群健康、索引速率、JVM 内存 9114
Process Exporter 进程级指标 每个进程的 CPU/内存/IO 9256

如何编写自定义 Exporter

Python prometheus_client 库让这件事简单到不可思议:

from prometheus_client import start_http_server, Counter, Histogram, Gauge
import time, random

# 定义指标——注意变量名的「语义化命名」
REQUEST_COUNT = Counter(
    'app_requests_total', 
    'Total HTTP requests',
    ['method', 'endpoint', 'status']
)
REQUEST_LATENCY = Histogram(
    'app_request_duration_seconds',
    'HTTP request latency in seconds',
    ['method', 'endpoint'],
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
)
ACTIVE_CONNECTIONS = Gauge(
    'app_active_connections',
    'Current number of active connections'
)
DB_QUERY_LATENCY = Histogram(
    'app_db_query_duration_seconds',
    'Database query latency',
    ['query_type']  # select / insert / update / delete
)

@REQUEST_LATENCY.labels(method='GET', endpoint='/api/data').time()
def handle_api_request():
    ACTIVE_CONNECTIONS.inc()
    REQUEST_COUNT.labels(method='GET', endpoint='/api/data', status='200').inc()
    with DB_QUERY_LATENCY.labels(query_type='select').time():
        time.sleep(random.uniform(0.01, 0.05))
    ACTIVE_CONNECTIONS.dec()

if __name__ == '__main__':
    start_http_server(8000)  # 一行启动 /metrics 端点
    while True:
        handle_api_request()

访问 http://localhost:8000/metrics 即可看到所有指标。


四、实战:从零搭建 Prometheus 监控体系

理论讲完了,现在让我们动手搭建一个完整的监控体系。实战场景:三台 Linux 服务器组成的微服务集群,需要监控基础设施状态、应用性能和数据库健康。

4.1 架构概览

图1:现代监控告警架构全景图

整体架构分为四层。从下到上依次是:

  1. 数据采集层:Exporter 暴露指标(Node Exporter 监控机器,应用内嵌 metrics 端点监控服务)
  2. 存储与计算层:Prometheus Server 拉取、存储、计算,Alertmanager 处理告警
  3. 可视化层:Grafana 展示仪表盘,提供统一的查看入口
  4. 告警层:Alertmanager 管理告警去重、分组、路由、静默

每一层都可以独立扩展和替换,这是云原生架构的基本思想。

4.2 安装 Prometheus

方式一:Docker Compose(推荐用于开发和中小规模)

# docker-compose.yml
version: '3.8'
services:
  prometheus:
    image: prom/prometheus:v2.53.0
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./rules:/etc/prometheus/rules:ro
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--storage.tsdb.retention.size=10GB'
      - '--web.enable-lifecycle'
      - '--web.enable-admin-api'
      - '--query.max-concurrency=20'
      - '--query.timeout=2m'
    ports:
      - '9090:9090'
    restart: unless-stopped
    networks:
      - monitoring

networks:
  monitoring:
    driver: bridge

volumes:
  prometheus_data:

关键参数说明

参数 作用 推荐值
--storage.tsdb.retention.time 数据保留时间 15d(生产)、30d(需要历史趋势)
--storage.tsdb.retention.size 数据保留大小上限 10GB-50GB
--web.enable-lifecycle 允许热加载配置文件 生产环境必开
--query.max-concurrency 最大并发查询数 CPU 核心数 × 2
--query.timeout 单次查询超时 2m
# prometheus.yml —— 完整的生产配置模板
global:
  scrape_interval: 15s
  scrape_timeout: 10s
  evaluation_interval: 30s
  external_labels:
    cluster: 'production'
    region: 'cn-east-1'
    environment: 'prod'

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - 'alertmanager:9093'
    - timeout: 10s
    - api_version: v2

rule_files:
  - 'rules/host_alerts.yml'
  - 'rules/app_alerts.yml'
  - 'rules/recording_rules.yml'

scrape_configs:
  # Prometheus 自监控
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
    metric_relabel_configs:
      # 丢弃高基数标签以节省内存
      - source_labels: [__name__]
        regex: 'prometheus_http_response_size_bytes_bucket'
        action: drop

  # 三台服务器的基础监控
  - job_name: 'node'
    scrape_interval: 30s  # 覆盖全局设置
    static_configs:
      - targets:
          - '192.168.1.10:9100'
          - '192.168.1.11:9100'
          - '192.168.1.12:9100'
        labels:
          env: 'production'
          team: 'infra'

  # 微服务应用监控
  - job_name: 'microservices'
    scrape_interval: 15s
    metrics_path: '/metrics'
    static_configs:
      - targets:
          - '192.168.1.10:8080'  # API Gateway
          - '192.168.1.11:8081'  # User Service
          - '192.168.1.12:8082'  # Order Service
    relabel_configs:
      - source_labels: [__address__]
        regex: '(.+):(.+)'
        target_label: instance
        replacement: '${1}'

4.3 安装 Node Exporter

在每台服务器上安装 Node Exporter。以 Ubuntu 22.04 为例:

# 步骤 1:创建专用系统用户
sudo useradd --no-create-home --shell /bin/false node_exporter

# 步骤 2:下载最新版 Node Exporter
wget https://github.com/prometheus/node_exporter/releases/download/v1.8.0/node_exporter-1.8.0.linux-amd64.tar.gz
tar xzf node_exporter-1.8.0.linux-amd64.tar.gz
sudo cp node_exporter-1.8.0.linux-amd64/node_exporter /usr/local/bin/
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter

# 步骤 3:创建 systemd 服务文件
sudo tee /etc/systemd/system/node_exporter.service << 'EOF'
[Unit]
Description=Prometheus Node Exporter
Documentation=https://prometheus.io/docs/guides/node-exporter/
After=network-online.target

[Service]
Type=simple
User=node_exporter
Group=node_exporter
ExecStart=/usr/local/bin/node_exporter \
  --collector.systemd \
  --collector.processes \
  --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|run)($|/) \
  --web.listen-address=:9100 \
  --web.telemetry-path=/metrics
Restart=always
RestartSec=5s
# 安全加固
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadOnlyPaths=/

[Install]
WantedBy=multi-user.target
EOF

# 步骤 4:启动并验证
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
sudo systemctl status node_exporter

# 步骤 5:测试 metrics 端点
curl -s localhost:9100/metrics | head -20

4.4 编写告警规则

告警规则是将 Prometheus 的查询能力转化为运维行动的核心环节。下面的规则覆盖了最常见的生产故障场景。

# rules/host_alerts.yml
groups:
  - name: host_alerts
    interval: 30s
    rules:
      # ========== CPU ==========
      - alert: HighCPUUsage
        expr: |
          100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
          category: cpu
        annotations:
          summary: "服务器 {{ $labels.instance }} CPU 使用率过高"
          description: "CPU 使用率已超过 80%,当前值: {{ $value | humanize }}%"
          runbook: "https://wiki.example.com/runbooks/high-cpu"

      # ========== Memory ==========
      - alert: LowMemory
        expr: |
          (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) < 0.10
        for: 5m
        labels:
          severity: critical
          category: memory
        annotations:
          summary: "服务器 {{ $labels.instance }} 内存不足"
          description: "可用内存仅剩 {{ $value | humanizePercentage }},请立即检查内存泄漏或扩容"

      - alert: OOMKillDetected
        expr: increase(node_vmstat_oom_kill[10m]) > 0
        labels:
          severity: critical
          category: memory
        annotations:
          summary: "服务器 {{ $labels.instance }} 检测到 OOM Kill 事件"
          description: "过去 10 分钟内发生 {{ $value }} 次 OOM Kill"

      # ========== Disk ==========
      - alert: DiskSpaceLow
        expr: |
          (node_filesystem_avail_bytes{mountpoint="/"} 
          / node_filesystem_size_bytes{mountpoint="/"}) < 0.10
        for: 0m
        labels:
          severity: critical
          category: disk
        annotations:
          summary: "磁盘 / 剩余空间不足 10%"

      - alert: DiskWillFillIn4Hours
        expr: |
          predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[1h], 4 * 3600) < 0
        for: 10m
        labels:
          severity: critical
          category: disk
        annotations:
          summary: "磁盘 / 预计 4 小时内填满"
          description: "基于过去 1 小时的使用速率,4 小时后磁盘将满"

      # ========== Network ==========
      - alert: NetworkReceiveErrors
        expr: rate(node_network_receive_errs_total[5m]) > 0
        for: 5m
        labels:
          severity: warning
          category: network
        annotations:
          summary: "网卡 {{ $labels.device }} 出现接收错误"

      # ========== Instance ==========
      - alert: InstanceDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
          category: availability
        annotations:
          summary: "实例 {{ $labels.instance }} 已宕机"
          description: "实例已离线超过 1 分钟,请立即检查"
# rules/app_alerts.yml
groups:
  - name: app_alerts
    interval: 15s
    rules:
      # RED - Rate:流量异常
      - alert: HighErrorRate
        expr: |
          (sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
          / sum(rate(http_requests_total[5m])) by (job)) > 0.05
        for: 5m
        labels:
          severity: critical
          category: application
        annotations:
          summary: "服务 {{ $labels.job }} 错误率超过 5%"
          description: "当前错误率: {{ $value | humanizePercentage }},请检查应用日志"

      # RED - Duration:延迟过高
      - alert: HighLatencyP99
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (job, le)
          ) > 1
        for: 10m
        labels:
          severity: warning
          category: application
        annotations:
          summary: "服务 {{ $labels.job }} P99 延迟超过 1 秒"
          description: "当前 P99: {{ $value }}s,请检查下游依赖或数据库"

      # RED - Traffic:流量突降(可能是服务挂了但 up==1)
      - alert: TrafficDrop
        expr: |
          (sum(rate(http_requests_total[5m])) by (job)
           / sum(rate(http_requests_total[5m] offset 15m)) by (job)) < 0.3
        for: 5m
        labels:
          severity: warning
          category: application
        annotations:
          summary: "服务 {{ $labels.job }} 流量异常下降"
          description: "当前流量仅为 15 分钟前的 {{ $value | humanizePercentage }}"

      # 数据库连接池耗尽
      - alert: DatabaseConnectionPoolExhausted
        expr: |
          db_connection_pool_active / db_connection_pool_max > 0.9
        for: 5m
        labels:
          severity: critical
          category: database
        annotations:
          summary: "数据库连接池使用率超过 90%"

4.5 验证环境

启动后,通过以下命令验证所有组件正常工作:

# 1. 检查 target 状态(所有应该是绿色 UP)
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {scrapeUrl, health}'

# 2. 检查告警规则是否加载正确
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[] | {name, rules: [.rules[].name]}'

# 3. 确认数据正在流入
curl -s 'http://localhost:9090/api/v1/query?query=up' | jq '.data.result[] | {instance: .metric.instance, value: .value[1]}'

# 4. 检查 Prometheus 自身健康
curl -s http://localhost:9090/-/healthy

五、Grafana 可视化:让数据开口说话

Prometheus 存储了海量指标数据,但裸 Prometheus 的图表功能非常简陋。Grafana 是监控领域的"最后一公里"—— 把冷冰冰的数字变成了可以"看"出来的故事。

5.1 安装 Grafana

# 在 docker-compose.yml 中追加
  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin
      - GF_USERS_ALLOW_SIGN_UP=false
      - GF_AUTH_ANONYMOUS_ENABLED=false
      - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-piechart-panel
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    ports:
      - '3000:3000'
    restart: unless-stopped
    networks:
      - monitoring

5.2 数据源自动配置

# grafana/datasources/prometheus.yml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    editable: false
    jsonData:
      timeInterval: "15s"
      queryTimeout: "60s"
      httpMethod: "POST"
      exemplarTraceIdDestinations:
        - name: trace_id
          datasourceUid: jaeger

5.3 仪表盘设计原则

一个好的仪表盘不是把所有指标堆在一起,而是按查看层次组织信息。这个分层设计来自 Google SRE 的最佳实践:

层级 用途 更新频率 目标用户 典型内容
L1:概览 系统整体健康 10-30s 值班人员、管理者 绿色/红色状态指示灯、SLA 达成率、Error Budget 剩余
L2:服务 单服务深入分析 30-60s SRE、开发团队 RED 面板、依赖健康、部署版本、日志面板嵌入
L3:调试 单实例详细诊断 5-15s 故障排查人员 按进程的 CPU/内存、GC 日志、线程池、慢查询

五条可视化设计规范

  1. 从上到下、从左到右按优先级排列:最重要的面板(如整体健康状态)永远在左上角
  2. 颜色传达语义:绿色=正常、黄色=接近阈值、红色=异常。不把颜色用作装饰
  3. 阈值线要显眼:用红色虚线标注告警阈值,让人一眼看到是否越界
  4. 单值面板展示 SLA:错误率、P99 延迟、可用率用 SingleStat 面板,配上阈值颜色
  5. 工具提示有上下文:鼠标悬停时显示的详细信息足以让人初步判断问题

5.4 变量和模板:让仪表盘活起来

Grafana 变量是实现"一个仪表盘适用全部服务"的关键。

图3:监控数据流与代码可视化

# 定义数据源变量(多集群切换)
$datasource

# 定义 Job 下拉框(选择不同服务)
label_values(http_requests_total, job)

# 定义 Instance 下拉框(选择具体实例)
label_values(up{job="$job"}, instance)

# 定义时间范围快捷键
$__range  # 当前仪表盘的时间范围

# 然后在所有面板中使用这些变量
rate(http_requests_total{job="$job", instance="$instance"}[$__rate_interval])

六、Alertmanager 告警管理:从噪音到精准通知

有了指标和告警规则,下一步是正确地通知正确的人。如果告警不加管理直接发送,一个故障可能触发数十条甚至上百条告警,这就是"告警疲劳"—— 比没有告警更危险的状态。

6.1 Alertmanager 的核心职责

Alertmanager 在 Prometheus 和通知渠道之间扮演"智能中间件"的角色,核心功能有四:

  • 分组:把同类告警合并成一条通知(如 10 个实例的 DiskFull 合并为 1 封邮件)
  • 抑制:高级别告警触发后,屏蔽低级别相关告警(如整个集群宕机时,不再发送单节点宕机告警)
  • 静默:计划维护期间暂停特定告警(如已知今晚要升级数据库,提前静默相关告警)
  • 路由:根据标签将不同告警发送到不同渠道和接收人

6.2 安装与完整配置

# alertmanager.yml —— 生产级配置
global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.example.com:587'
  smtp_from: 'alertmanager@example.com'
  smtp_auth_username: 'alertmanager@example.com'
  smtp_auth_password: 'your-app-password'
  smtp_require_tls: true
  # 微信/钉钉 webhook(通过 Alertmanager 的 webhook receiver)
  http_config:
    proxy_url: ''

# 模板文件
templates:
  - '/etc/alertmanager/templates/*.tmpl'

# 告警路由树 —— 基于标签的树状分发
route:
  receiver: 'default-receiver'
  group_by: ['alertname', 'cluster', 'severity']
  group_wait: 10s        # 首次等待(收集同类告警)
  group_interval: 5m     # 同组后续通知间隔
  repeat_interval: 4h    # 未恢复告警的重复通知
  
  routes:
    # 关键告警:多重通知
    - match:
        severity: critical
      receiver: 'oncall-critical'
      group_wait: 5s       # 缩短等待
      repeat_interval: 1h   # 每小时提醒
      continue: true        # 继续匹配下级规则

    # 数据库相关:发给 DBA 团队
    - match_re:
        job: '.*(mysql|postgres|redis|mongodb).*'
      receiver: 'dba-team'
      
    # 测试环境:只发 Slack,不骚扰邮箱
    - match:
        env: staging
      receiver: 'dev-slack'

    # 凌晨 2-6 点:非 critical 告警延迟发送
    - match:
        severity: warning
      receiver: 'delayed-email'
      active_time_intervals:
        - night-hours

# 时间间隔定义
time_intervals:
  - name: night-hours
    time_intervals:
      - times:
          - start_time: '02:00'
            end_time: '06:00'
        weekdays: ['monday:friday']

# 接收器定义
receivers:
  - name: 'default-receiver'
    email_configs:
      - to: 'ops-team@example.com'
        headers:
          Subject: '[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}'

  - name: 'oncall-critical'
    email_configs:
      - to: 'oncall@example.com'
    # PagerDuty 集成(电话通知)
    pagerduty_configs:
      - routing_key: 'your-pagerduty-integration-key'
        severity: '{{ if eq .CommonLabels.severity "critical" }}critical{{ else }}error{{ end }}'

  - name: 'dba-team'
    email_configs:
      - to: 'dba@example.com'

  - name: 'dev-slack'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00/B00/xxx'
        channel: '#dev-alerts'
        username: 'Alertmanager'
        icon_emoji: ':warning:'
        title: '{{ .GroupLabels.alertname }}'
        text: |
          *状态:* {{ .Status | toUpper }}
          *严重程度:* {{ .CommonLabels.severity }}
          {{ range .Alerts }}
          • {{ .Annotations.summary }}
          {{ end }}

  - name: 'delayed-email'
    email_configs:
      - to: 'ops-team@example.com'

# 抑制规则
inhibit_rules:
  # 如果整个集群宕机,抑制单节点宕机告警
  - source_match:
      alertname: 'ClusterDown'
      severity: 'critical'
    target_match_re:
      alertname: '(InstanceDown|NodeUnreachable|ServiceDown)'
    equal: ['cluster']
  
  # 如果磁盘快满了,抑制"磁盘空间预测"告警
  - source_match:
      alertname: 'DiskSpaceLow'
    target_match:
      alertname: 'DiskWillFillIn4Hours'
    equal: ['instance', 'mountpoint']

6.3 告警通知模板

用 Go 模板语言定制邮件内容,让通知更有用:

{{/* email.tmpl */}}
{{ define "email.default.subject" }}
[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }} ({{ .Alerts | len }} alerts)
{{ end }}

{{ define "email.default.html" }}
<style>
  table { border-collapse: collapse; width: 100%; }
  th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
  th { background: #f44336; color: white; }
  .critical { background: #ffebee; }
  .warning { background: #fff3e0; }
  .resolved { background: #e8f5e9; }
</style>

<h2>{{ .GroupLabels.alertname }}</h2>
<p><strong>Status:</strong> {{ .Status | toUpper }}</p>
<p><strong>Severity:</strong> {{ .CommonLabels.severity }}</p>
<p><strong>Time:</strong> {{ .Alerts.First.StartsAt }}</p>

<h3>Alerts ({{ .Alerts | len }}):</h3>
<table>
<tr><th>实例</th><th>描述</th><th>数值</th><th>开始时间</th></tr>
{{ range .Alerts }}
<tr class="{{ .Labels.severity }}">
  <td>{{ .Labels.instance }}</td>
  <td>{{ .Annotations.description }}</td>
  <td>{{ .Annotations.value }}</td>
  <td>{{ .StartsAt }}</td>
</tr>
{{ end }}
</table>

<h3>📋 处理步骤:</h3>
<p>{{ .CommonAnnotations.runbook }}</p>

<hr>
<p><small>Sent by Alertmanager at {{ .Alerts.First.StartsAt }}</small></p>
{{ end }}

6.4 告警质量检查清单

每一条告警规则在上线前必须通过以下检查。这是一份需要团队共同维护的活文档:

  • 可执行性:收件人看完通知就知道该做什么,有 runbook 链接
  • 症状导向:告警的是"用户登录超时",而不是"服务器 CPU 高"
  • 阈值合理:在测试环境验证过,不会因正常波动误报
  • 抑制完备:相关告警之间有抑制规则,避免雪崩
  • 负责人明确:每条告警有 teamowner 标签
  • 静默可用:维护窗口期间可以一键静默
  • SLO 对齐:critical 告警应该对应 Error Budget 的消耗

七、进阶:大规模场景下的监控架构演进

当你的系统从几十台服务器扩展到几千台、从单机房扩展到多地域部署时,单实例 Prometheus 就到了瓶颈。下面我们讨论三种成熟的扩展策略。

7.1 先垂直扩展,再水平扩展

很多团队一遇到性能问题就想着拆分。实际上,垂直扩展更简单且效果显著:

配置 支持的时间序列数 采集 Target 数 日存储量
2C4G ~20 万 ~200 ~2GB
4C16G ~100 万 ~500 ~10GB
8C32G + SSD ~300 万 ~1000 ~30GB
16C64G + NVMe ~800 万 ~2000 ~80GB

如果你的时间序列数在百万级别以内,加内存是最简单有效的优化。

7.2 联邦模式

当业务需要按团队或数据中心拆分监控时,联邦是首选方案。

图2:Prometheus 联邦架构流程图

# 全局 Prometheus(聚合层配置)
scrape_configs:
  - job_name: 'federate'
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        # 🔴 关键:只拉预聚合的 Recording Rules,不拉原始指标
        - '{__name__=~"job:.*"}'
        - '{__name__=~"instance:.*"}'
        - '{__name__=~"cluster:.*"}'
    static_configs:
      - targets:
          - 'prometheus-team-a:9090'
          - 'prometheus-team-b:9090'
          - 'prometheus-team-c:9090'
        labels:
          team: 'all'

联邦的核心原则:永远不要联邦原始指标。你是在"把问题从一台机器搬到另一台",而不是解决问题。

7.3 Thanos:长期存储 + 全局视图

Thanos 是目前 CNCF 生态中最成熟的 Prometheus 高可用方案。它提供了四个核心组件:

  • Sidecar:和 Prometheus 一起部署,将 TSDB 数据上传到对象存储
  • Store Gateway:从对象存储读取历史数据,对外提供查询接口
  • Querier:聚合多个 Prometheus/Store 的查询,提供全局视图(这是用户感知最明显的部分)
  • Compactor:压缩和降采样历史数据
# 启动 Thanos Sidecar(与 Prometheus 同 Pod / 同机器)
thanos sidecar \
  --tsdb.path=/prometheus \
  --prometheus.url=http://localhost:9090 \
  --objstore.config-file=/etc/thanos/s3.yaml \
  --grpc-address=0.0.0.0:10901 \
  --http-address=0.0.0.0:10902

# 启动 Thanos Querier(聚合多个集群)
thanos query \
  --http-address=0.0.0.0:9090 \
  --grpc-address=0.0.0.0:10901 \
  --query.replica-label=replica \
  --store=sidecar-cluster-east:10901 \
  --store=sidecar-cluster-west:10901 \
  --store=dnssrv+_grpc._tcp.thanos-store.default.svc.cluster.local

7.4 VictoriaMetrics:极简替代方案

如果 Thanos 的复杂度让你头疼,可以试试 VictoriaMetrics。它兼容 Prometheus API(直接替换),但有几个显著优势:

维度 Prometheus VictoriaMetrics 单机版
磁盘压缩 1x 约 7x(同等数据量)
内存使用 高(索引在内存) 较低(索引在磁盘)
部署复杂度 单二进制 单二进制
PromQL 兼容 原生 99% 兼容(部分高级函数不支持)
集群版 需要 Thanos/Cortex 内置集群模式
# 启动 VictoriaMetrics
./victoria-metrics-prod \
  -storageDataPath=/victoria-metrics-data \
  -retentionPeriod=12 \
  -httpListenAddr=:8428 \
  -search.maxUniqueTimeseries=5000000

# Prometheus 远程写入到 VM
remote_write:
  - url: "http://victoriametrics:8428/api/v1/write"
    queue_config:
      max_samples_per_send: 5000
      batch_send_deadline: 5s

7.5 SLO 驱动的告警:衡量用户体验

这是监控体系的"进阶课"。传统的"CPU 超过 80% 就告警"的问题是:CPU 高 ≠ 用户体验差。也许正在跑批处理任务,用户完全无感知。

SLO(Service Level Objective)框架

概念 定义 计算示例
SLI 服务级别指标 "99.9% 的请求在 300ms 内完成"
SLO 服务级别目标 SLI >= 99.9%(内部承诺)
SLA 服务级别协议 SLO + 赔偿条款(对外承诺)
Error Budget 允许的失败窗口 (1 - 0.999) × 30天 × 86400秒 = 43,200 秒/月

基于 Error Budget 消耗速率的告警

# 多窗口燃烧速率告警 —— Google SRE 的推荐做法
groups:
  - name: slo_alerts
    rules:
      # 短窗口:1 小时内消耗了 2% 的 Error Budget → 紧急告警
      - alert: SLOErrorBudgetBurnCritical
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
            / sum(rate(http_requests_total[1h]))
          ) > (0.02 * 14.4)
        labels:
          severity: critical
          
      # 长窗口:6 小时内消耗了 5% 的 Error Budget → 告警
      - alert: SLOErrorBudgetBurnWarning
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h]))
            / sum(rate(http_requests_total[6h]))
          ) > (0.05 * 14.4)
        for: 5m
        labels:
          severity: warning

SLO 方法论的价值

  • 告警直接关联用户体验,而非底层指标
  • 自动权衡稳定性与迭代速度:Error Budget 用完了就冻结新功能部署
  • 减少无意义告警:只要用户在正常使用,CPU 高、内存高都不用告警

八、常见问题 FAQ

Q1:Prometheus 数据丢了怎么办?有备份机制吗?

Prometheus 本地 TSDB 不像 MySQL 有主从复制。推荐方案:

# 方案1:手动创建快照
curl -XPOST http://localhost:9090/api/v1/admin/tsdb/snapshot
# 快照存入 data/snapshots/ 目录,可以直接 cp 走

# 方案2:Thanos Sidecar 自动上传到 S3/GCS/MinIO
# 方案3:Remote Write 到 VictoriaMetrics,利用其内置复制
# 方案4:部署两套 Prometheus 抓取同一批 target(HA Pair)

对于生产环境,强烈推荐 Thanos + 对象存储。对象存储自带 99.999999999% 的持久性,远超本地磁盘。

Q2:Grafana 显示 "No Data" 怎么排查?

系统化排查四步法:

# 第一步:检查数据源连接
# Grafana → Configuration → Data Sources → Test

# 第二步:先用最简单的 PromQL 验证数据存在
up  # 如果这也无数据,说明 Prometheus 根本没在抓取

# 第三步:检查标签值是否匹配
count by (job)(up)  # 看看有哪些 job 有数据

# 第四步:确认时间范围
# Grafana 右上角的时间选择器是否包含了数据的时间范围
# 一些指标需要 [5m] 窗口,前 5 分钟内无数据则面板空白

Q3:Prometheus 内存占用太高怎么办?

内存大户主要是 TSDB 的倒排索引。优化手段按优先级排列:

# 1. 查看内存占用构成
curl http://localhost:9090/api/v1/status/tsdb | jq

# 2. 降低 scrape_interval(如 15s → 30s)
# 3. 减少高基数标签(如删除 user_id 标签)
# 4. 缩短 retention 时间(如 30d → 15d)
# 5. 使用 Recording Rules 预聚合,减少长窗口原始查询
# 6. 考虑迁移到 VictoriaMetrics(内存占用低 3-5 倍)

Q4:如何监控 TLS 证书到期时间?

scrape_configs:
  - job_name: 'blackbox-tls'
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://example.com
          - https://api.example.com
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - target_label: __address__
        replacement: blackbox-exporter:9115

# 告警规则:证书 30 天内到期
- alert: TLSCertificateExpiring
  expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30
  for: 1h
  annotations:
    summary: "{{ $labels.target }} TLS 证书即将到期"

Q5:Kubernetes 环境有什么特殊的监控需求?

K8s 环境的监控需要关注五个层面:

层面 工具 关键指标
Node Node Exporter / kubelet CPU、内存、磁盘、网络
Pod cAdvisor 每个 Pod 的 CPU/Memory/Network
K8s 对象 kube-state-metrics Pod 状态、Deployment 副本数
控制面 kube-apiserver metrics API 延迟、etcd 健康
应用 自定义 Exporter RED 指标、业务指标
# 基于 Pod Annotation 的自动发现
- job_name: 'kubernetes-pods'
  kubernetes_sd_configs:
    - role: pod
  relabel_configs:
    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
      action: keep
      regex: true
    - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
      action: replace
      target_label: __metrics_path__
      regex: (.+)
    - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
      action: replace
      regex: ([^:]+)(?::\d+)?;(\d+)
      replacement: $1:$2
      target_label: __address__

Q6:告警太多怎么办?如何治理?

告警治理不是技术问题,是管理问题。三步法:

  1. 审计:用 PromQL 统计过去 30 天每条告警的触发次数
    # 统计各告警的触发频率
    ALERTS{alertstate="firing"}
    
  2. 修正:找出 Top 10 噪音源,逐条判断是调阈值、加 for 等待时间、还是直接删除
  3. 分级:确保只有 critical 级别的告警才会半夜把人叫醒。warning 级别发 Slack 即可

Q7:要不要监控 Prometheus 自身?

要!这是监控系统的"空中预警机"。关键指标:

# Prometheus 自身的资源消耗
process_resident_memory_bytes{job="prometheus"}
rate(process_cpu_seconds_total{job="prometheus"}[5m])

# 抓取健康状况
prometheus_target_interval_length_seconds{quantile="0.99"}
prometheus_sd_discovered_targets  # 服务发现的目标数

# 存储健康
prometheus_tsdb_head_series         # 当前活跃序列数——最关键的容量指标
prometheus_tsdb_storage_blocks_bytes  # 磁盘占用
prometheus_tsdb_compactions_failed_total  > 0  # 压缩失败

# 查询健康
prometheus_engine_query_duration_seconds{quantile="0.99"}

# 告警:Head Series 超过 80% 容量
- alert: PrometheusHeadSeriesHigh
  expr: |
    prometheus_tsdb_head_series 
    / prometheus_tsdb_head_series_limit > 0.8
  annotations:
    summary: "Prometheus 活跃序列超过 80% 上限"

Q8:如何实现告警自愈?

告警自愈是监控的终极目标 —— 从"告警通知人"到"告警通知自动化":

receivers:
  - name: 'auto-remediation'
    webhook_configs:
      - url: 'http://remediation-bot:8080/webhook'
        send_resolved: true

# remediation-bot 根据 alertname 执行预定义的操作:
# HighCPUUsage → 自动扩容
# DiskSpaceLow  → 自动清理日志
# ServiceDown   → 自动重启容器
# DatabaseSlow  → 自动杀死慢查询

⚠️ 自愈有风险:自动重启可能掩盖真正的 Bug。建议先在 warning 级别试点,逐步扩展到 critical。


九、总结与展望

9.1 核心要点回顾

这篇文章我们从理论到实践,完整覆盖了现代监控告警体系的搭建全流程:

  1. 可观测性三支柱:Metrics 告诉你"有没有问题"、Logs 告诉你"问题在哪里"、Traces 告诉你"问题怎么发生的"
  2. Prometheus 生态:Pull 模型的设计哲学、标签数据模型的力量、PromQL 的查询能力、丰富的 Exporter 生态
  3. 告警体系:从规则定义到 Alertmanager 的分组→抑制→路由→静默的完整链路
  4. 可视化最佳实践:Grafana 三层仪表盘设计(L1 概览 / L2 服务 / L3 调试)
  5. 大规模演进路径:垂直扩展 → 联邦 → Thanos → VictoriaMetrics 四种选择,按需选择
  6. 先进方法论:SLO 驱动的告警、Error Budget 概念、USE+RED+四大黄金信号

9.2 渐进式落地路线图

你不需要一步到位搭建所有组件。按这个路线渐进式引入,根据团队的规模和成熟度选择:

阶段一(基础 — 第 1-2 周)
  ├─ Prometheus + Node Exporter → 服务器 CPU/内存/磁盘指标
  ├─ Grafana → 导入 Node Exporter Full 仪表盘
  └─ 目标:能看到所有服务器的基本状态

阶段二(应用 — 第 3-4 周)
  ├─ 应用接入 Prometheus Client → 暴露 /metrics
  ├─ Grafana RED 仪表盘 → 请求速率、错误率、延迟
  ├─ Alertmanager + 邮件告警 → CPU/内存/磁盘告警
  └─ 目标:应用出问题 5 分钟内知道

阶段三(完善 — 第 5-8 周)
  ├─ Recording Rules → 预聚合查询加速
  ├─ Blackbox Exporter → URL 探活 + TLS 过期监控
  ├─ Loki + Promtail → 日志关联查询
  └─ 目标:20 分钟内定位到根因

阶段四(成熟 — 持续优化)
  ├─ Thanos / VictoriaMetrics → 长期存储 + 多集群聚合
  ├─ SLO Dashboard → 基于用户体验的监控
  ├─ OpenTelemetry Tracing → 分布式追踪
  ├─ 告警自愈试点 → 自动重启/扩容/清理
  └─ 目标:80% 的故障自动恢复,值班人员睡个好觉

9.3 推荐学习资源

资源 类型 适合人群
Prometheus 官方文档 (prometheus.io) 文档 所有级别,必读
Google SRE Book 监控章节 书籍 中高级,理解"为什么"
Robust Perception Blog 博客 中高级,PromQL 深度技巧
Grafana Play (play.grafana.org) 交互 新手,在线体验仪表盘
PromLabs 培训 (training.promlabs.com) 课程 新手到中级,系统学习
Awesome Prometheus (GitHub) 合集 所有级别,工具和资源索引

本文由 MarkShareX AI 自动创作,分类:O&M,方向:监控告警