现代监控告警体系从零到生产:Prometheus + Grafana + Alertmanager 全栈实战 🚀
手把手搭建可观测性三件套,用数据驱动运维决策,让故障在用户发现之前就被消灭。从单机监控到多集群联邦,本文覆盖生产级监控的全部关键知识点。
目录
- 背景:为什么监控是运维的生命线
- 可观测性三大支柱:Metrics、Logs、Traces
- Prometheus 核心概念深度解析
- 实战:从零搭建 Prometheus 监控体系
- Grafana 可视化:让数据开口说话
- Alertmanager 告警管理:从噪音到精准通知
- 进阶:大规模场景下的监控架构演进
- 常见问题 FAQ
- 总结与展望
一、背景:为什么监控是运维的生命线
想象一下这个场景:凌晨三点,你被手机铃声惊醒,客户说网站打不开了。你睡眼惺忪地打开电脑,手动检查服务器状态、数据库连接、应用日志...半小时后才发现只是某个微服务的健康检查超时,重启就恢复正常了。
这就是没有监控体系下的运维日常 —— 被动、低效、痛苦。
现代 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 监控文化:工具的尽头是文化
监控不是买一套工具装上就完事。真正的监控文化需要整个团队从认知到行动的统一:
- 从设计阶段就考虑可观测性:新服务上线前必须有健康检查端点
/health、metrics 暴露端口/metrics、以及对应的告警规则。这不是运维的要求,是架构设计的一部分 - 告警必须可执行:每一条告警通知都应该告诉接收者"做什么",而不是仅仅告知"发生了什么"。带上 runbook 链接是最低要求
- 减少告警疲劳:宁少勿多。如果一条告警连续触发一周都没人处理,直接删掉或者降级。告警质量比数量重要得多
- 事后复盘不是追责:故障后的"无责事后复盘"文化比任何监控工具都重要。复盘的目标是找出系统层面的改进点,而非找个人背锅
- 监控即代码:告警规则、仪表盘 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 使用。一个典型的故障排查流程是:
- Grafana 面板显示 5xx 错误率飙升(Metrics 发现问题)
- 点击面板上的链接跳转到 Loki,自动带上时间范围和
job标签 - Loki 显示该时间段内的错误日志,看到
NullPointerException at UserService.java:42 - 复制日志中的
trace_id,在 Jaeger 中查看完整的分布式追踪 - 发现是下游数据库查询超时导致的雪崩
日志最佳实践:
- 结构化日志(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.statement、http.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 模式在云原生环境下的六大优势:
- 自动服务发现:结合 Kubernetes SD,新 Pod 上线后自动被 Prometheus 发现并拉取,无需手动注册
- 双重故障检测:如果拉取失败,说明目标本身就有问题(网络不通、进程挂了、端口未监听)—— 既是监控也是健康检查
- 统一控制面:采集频率、超时时间、重试策略由 Prometheus 服务端统一管理,不需要去每个被监控节点调整配置
- 安全边界清晰:不需要向每个被监控对象开放入站端口(Prometheus 主动出站连接)
- 压力可控:Prometheus 决定何时拉取,避免了所有 Agent 同时推送造成的服务端压力尖峰
- 调试友好:任何目标都可以直接 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 架构概览
整体架构分为四层。从下到上依次是:
- 数据采集层:Exporter 暴露指标(Node Exporter 监控机器,应用内嵌 metrics 端点监控服务)
- 存储与计算层:Prometheus Server 拉取、存储、计算,Alertmanager 处理告警
- 可视化层:Grafana 展示仪表盘,提供统一的查看入口
- 告警层: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 日志、线程池、慢查询 |
五条可视化设计规范:
- 从上到下、从左到右按优先级排列:最重要的面板(如整体健康状态)永远在左上角
- 颜色传达语义:绿色=正常、黄色=接近阈值、红色=异常。不把颜色用作装饰
- 阈值线要显眼:用红色虚线标注告警阈值,让人一眼看到是否越界
- 单值面板展示 SLA:错误率、P99 延迟、可用率用 SingleStat 面板,配上阈值颜色
- 工具提示有上下文:鼠标悬停时显示的详细信息足以让人初步判断问题
5.4 变量和模板:让仪表盘活起来
Grafana 变量是实现"一个仪表盘适用全部服务"的关键。
# 定义数据源变量(多集群切换)
$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 高"
- 阈值合理:在测试环境验证过,不会因正常波动误报
- 抑制完备:相关告警之间有抑制规则,避免雪崩
- 负责人明确:每条告警有
team或owner标签 - 静默可用:维护窗口期间可以一键静默
- 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 联邦模式
当业务需要按团队或数据中心拆分监控时,联邦是首选方案。
# 全局 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:告警太多怎么办?如何治理?
告警治理不是技术问题,是管理问题。三步法:
- 审计:用 PromQL 统计过去 30 天每条告警的触发次数
# 统计各告警的触发频率 ALERTS{alertstate="firing"} - 修正:找出 Top 10 噪音源,逐条判断是调阈值、加
for等待时间、还是直接删除 - 分级:确保只有 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 核心要点回顾
这篇文章我们从理论到实践,完整覆盖了现代监控告警体系的搭建全流程:
- 可观测性三支柱:Metrics 告诉你"有没有问题"、Logs 告诉你"问题在哪里"、Traces 告诉你"问题怎么发生的"
- Prometheus 生态:Pull 模型的设计哲学、标签数据模型的力量、PromQL 的查询能力、丰富的 Exporter 生态
- 告警体系:从规则定义到 Alertmanager 的分组→抑制→路由→静默的完整链路
- 可视化最佳实践:Grafana 三层仪表盘设计(L1 概览 / L2 服务 / L3 调试)
- 大规模演进路径:垂直扩展 → 联邦 → Thanos → VictoriaMetrics 四种选择,按需选择
- 先进方法论: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,方向:监控告警