周五晚上十一点,手机连着震动三次,值班群里有人发了张截图:支付服务核心业务的CPU使用率拉到顶,但打开Grafana Dashboard才发现只有一条孤零零的曲线,没有版本对比、没有上下游调用关系、连是哪个节点都分不清,翻了五分钟面板才定位到告警来源。事后复盘,监控这套东西其实早就装了——Prometheus和Grafana都在跑,数据也一直在采集,但“能跑”和“好用”之间,隔着服务发现、指标口径、告警收敛、面板设计这些几乎没人一次性讲透的东西。
这篇文章就围绕云原生监控这条主线,从Prometheus为什么能成为事实标准开始,把采集端、查询端、可视化端、告警端一整套链路里真正影响落地效果的细节拆开讲。适合正在搭监控体系的运维工程师、刚接手Kubernetes集群的开发者,以及想把自己的服务从“裸奔”状态拉起来的团队参考。
1. 为什么云原生监控的默认答案总是 Prometheus
先聊一个反直觉的事实:Prometheus 不是性能最强的监控系统,也不是功能最全的,但它在云原生环境里就是有压倒性优势,背后是架构设计取向的问题。
1.1 拉模型:监控系统主动出击带来的连锁优势
传统监控系统(比如Zabbix)走的是推模型,Agent把数据上报到Server,服务端负责接收和存储。Prometheus反过来,它是拉模型:Prometheus Server按配置的时间间隔去目标端点的 /metrics 接口抓取数据。
这个“拉”的设计在Kubernetes里带来了几个实打实的好处。第一,目标发现不再靠人工维护IP列表,而是直接对接Kubernetes API,Pod创建、销毁、漂移,Prometheus都能通过服务发现自动感知。第二,拉模型天然适合隔离故障域——监控系统挂了不影响被监控的业务,业务挂了监控系统也能第一时间发现它在列表里消失。第三,数据抓取行为本身可以被监控,比如 up == 0 这个经典表达式,就是靠拉模型才有的。
推模型没有存在的价值吗?也不是。Pushgateway这种组件就是给短生命周期任务准备的,比如批量计算任务,跑完就退出了,来不及被拉取。但我个人的经验是,业务指标尽量别依赖Pushgateway,因为它是单点,而且指标过期清理不及时很容易造成数据混乱。能用拉的就别用推的。
1.2 指标、标签与时间序列:理解数据结构才能写出高效查询
很多人在PromQL里写不出高效查询,根因是没理解它的底层数据结构。Prometheus存储的是一条条时间序列(Time Series),每条序列由两部分组成:
- 指标名(Metric Name),比如
http_requests_total - 一组标签(Labels),比如
{method="GET", endpoint="/api/user", status="200"}
换句话说,http_requests_total{method="GET", endpoint="/api/user", status="200"} 是一条独立的时间序列,http_requests_total{method="POST", ...} 是另一条。
标签才是监控系统里的核心抽象,它决定了你能从哪些维度切数据。这就像电商系统里的SKU和SPU——指标名是SPU,标签组合才是SKU。理解这一点之后,很多问题就通了:
- 为什么两个表达式查出来的数值对不上?因为时间序列的匹配逻辑是标签完全一致,差一个标签就是两条不同的线。
- 为什么某些查询特别慢?因为标签基数太大,扫的时间序列太多。
- 为什么面板上出现断断续续的曲线?因为标签组合变化,导致序列中断,典型例子是容器重建后
instance标签变了。
1.3 四种指标类型:Counter、Gauge、Histogram、Summary
指标类型是理解PromQL语义的基石,很多新手写查询出错就是因为类型没搞懂。
| 类型 | 含义 | 典型场景 | 核心注意点 |
|---|---|---|---|
| Counter | 单调递增计数器 | 请求数、错误数、CPU时间累计 | 只在重启时清零,查询要用 rate() 或 increase() |
| Gauge | 可升可降的瞬时值 | CPU使用率、内存使用量、当前连接数 | 直接使用即可,不要套 rate() |
| Histogram | 直方图,客户端分桶统计 | 请求延迟、响应大小 | 配合 histogram_quantile() 计算分位数,需要预留 _bucket 序列 |
| Summary | 摘要,客户端预计算分位数 | 同上 | 和Histogram类似,但分位数由客户端算好,无法聚合 |
Counter 是最容易出问题的。直接用 http_requests_total 画图,得到的是不断上涨的曲线,没有任何业务含义。必须配合 rate(http_requests_total[5m]) 才能表示每秒请求数。而Gauge类型如果套了 rate(),常常会出现负数或剧烈波动,因为Gauge本来就能上下变化。
Histogram 是另一个重灾区。要计算P99延迟,正确写法是:
promql复制histogram_quantile(0.99, sum(rate(my_request_duration_seconds_bucket[5m])) by (le))
很多人忘了 le 这个标签,导致没有维度可以聚合。这些都建立在对数据结构的理解之上,所以我把这一节放在最前面——所有后续的操作、排错、优化,最后都会回到这里。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零部署:选对安装姿势比执行命令更重要
部署Prometheus看起来简单,一个二进制文件就能跑起来,但在Kubernetes环境里,安装姿势直接决定了后续的维护成本和功能边界。
2.1 三种部署方式怎么选
我见过团队为了简单,直接在K8s集群里用Deployment裸跑一个Prometheus容器,配置靠挂载ConfigMap,发现目标靠手工写 static_configs。短期能跑,长期会非常痛苦,因为采集目标在云原生环境里是动态的,手工维护静态列表等于放弃了Kubernetes最核心的价值。
主流部署方式有三种,我做了个对比:
| 部署方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 二进制/单容器部署 | 简单直接,排查方便 | 没有服务发现,需要手工管理配置 | 测试环境、单机监控 |
| Helm Chart部署(prometheus-community/kube-prometheus-stack) | 全家桶,自带node-exporter、kube-state-metrics、Grafana、Alertmanager | 依赖Helm版本,升级需谨慎 | 大多数生产环境首选 |
| Prometheus Operator / 自定义CRD | 声明式管理采集配置,支持ServiceMonitor | 学习曲线陡峭,概念多 | 大规模多团队共享监控平台 |
生产环境我推荐Helm Chart方案,它底层就是Operator,但帮你把全家桶都初始化好了。如果你已经熟悉Operator的CRD模型,直接上Operator当然更灵活。
2.2 用 Helm 在 Kubernetes 里部署全家桶的完整步骤
以 prometheus-community/kube-prometheus-stack 为例,核心步骤就四步:
bash复制# 添加仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 创建命名空间
kubectl create ns monitoring
# 安装全家桶
helm install prometheus prometheus-community/kube-prometheus-stack \
--namespace monitoring \
-f values.yaml
真正的工作量在 values.yaml。我通常会在里面覆盖这几个关键配置:
yaml复制# 关键配置片段
prometheus:
prometheusSpec:
# 存储保留时间,默认太久,建议根据磁盘规划
retention: 15d
# 资源限制必须配上,否则监控系统自己把节点打爆
resources:
requests:
memory: 4Gi
cpu: 2
limits:
memory: 8Gi
cpu: 4
# 如果用serviceMonitor方式发现目标,需要放开选择器
serviceMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelector: {}
serviceMonitorNamespaceSelector: {}
grafana:
adminPassword: "你的强密码"
persistence:
enabled: true
size: 5Gi
这几个配置每个都有讲究。retention: 15d 是本地磁盘和查询性能之间的折中,太长了磁盘扛不住,太短了出了问题没法回溯。资源限制更关键——我见过不止一次,Prometheus内存飙到十几个G,把节点吃撑,结果业务比监控先挂了。Operator默认给的资源请求很小(200Mi之类),在节点数多、指标量大的集群里会疯狂OOM。
2.3 采集配置里最容易写错的四个点
等部署完,你会发现自己需要手动加采集任务。在Helm部署下有两种方式:改 values.yaml 里的 additionalScrapeConfigs,或者用ServiceMonitor对象。无论哪种方式,采集配置本身有几个高频坑。
第一个坑:honor_labels: false导致标签冲突。 如果被采集端自己暴露了和Prometheus服务发现相同的标签名(比如 instance),默认Prometheus会用服务发现标签覆盖端点标签。这在某些exporter下会导致数据对不上。
第二个坑:metrics_path没设置。 很多exporter暴露指标的路径不是 /metrics,比如cAdvisor是 /metrics/cadvisor,blackbox_exporter是 /probe。如果没在配置里指定 metrics_path,只能抓到一个404。
第三个坑:scheme和tls_config没配套。 服务启了HTTPS,但采集配置还在用HTTP,抓取结果全是 context deadline exceeded。在云原生环境里,服务间走了mTLS之后,这个坑会更加明显。
第四个坑:relabel规则里的 __meta_ 前缀没理解。 服务发现生成的元标签(比如 __meta_kubernetes_pod_name)只在relabel阶段可用,一旦进入存储就成了普通标签。很多人以为加个 __address__ 就完事了,实际上只改了抓取地址,目标的所有原始标签都会被丢弃。
举个典型的ServiceMonitor例子,把服务发现和标签处理一起看:
yaml复制apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app-monitor
namespace: monitoring
spec:
selector:
matchLabels:
app: my-app
namespaceSelector:
matchNames:
- production
endpoints:
- port: metrics
interval: 30s
# 这里的relabeling是核心,保留namespace和pod标签用于后续查询
# 不加下面这一段,指标上的namespace标签不会生效
2.4 服务发现:让采集目标跟着 Pod 生命周期自动增减
服务发现是云原生监控和传统监控最本质的区别。Prometheus默认集成了 kubernetes_sd_configs,通过角色(role)来区分发现类型:
role: pod:发现命名空间下的Pod,适合直接暴露/metrics的Podrole: service:发现Service,通过Service的端口采集role: endpoints:发现Endpoints,最常用,配合ServiceMonitor使用role: node:发现集群节点,配合node-exporter使用
在Helm全家桶里,node-exporter的采集任务是自动配好的,你不需要手动写节点发现。但如果你要采集自研服务的指标,建议用ServiceMonitor,理由很简单:它把采集目标声明成Kubernetes对象,团队之间可以用GitOps管理,不需要登录服务器改配置文件。
我用ServiceMonitor采集自研服务时有个习惯——在 spec.endpoints 里设置 interval: 30s 而不是默认的15s。云原生环境下Pod数量多,统一15s抓取会让Prometheus的QPS压力陡增,尤其是大集群。按需调整抓取频率,比盲目提高抓取频率更健康。
3. 指标采集层:node-exporter、kube-state-metrics 与自定义业务指标
很多人以为监控就是装好Prometheus再加个Grafana就能看图了,真正开始搭的时候才发现要回答一个问题:数据从哪来?这一层往往是工作量最大的,也是坑最多的地方。
3.1 node-exporter 与 kube-state-metrics 的分工差异
这两个组件经常被混淆,因为它们都跟Kubernetes有关,但关注的东西完全不同。
node-exporter 采集的是节点级别的指标,比如CPU使用率、内存使用量、磁盘读写、网络流量。它暴露的是Linux系统的 /proc、/sys 信息,和Kubernetes没有直接关系,即使不装K8s,node-exporter照样能监控一台普通服务器。
kube-state-metrics 关注的是Kubernetes对象的状态——Deployment的期望副本数和实际副本数、Pod的状态分布、Service的端点数量、PVC的容量等。这些指标不是从系统里读出来的,而是通过监听Kubernetes API计算出来的。
配套指标速查表如下:
| 组件 | 典型指标 | 用途 |
|---|---|---|
| node-exporter | node_cpu_seconds_total |
节点CPU使用率 |
| node-exporter | node_memory_MemAvailable_bytes |
节点可用内存 |
| node-exporter | node_filesystem_avail_bytes |
磁盘剩余空间 |
| kube-state-metrics | kube_deployment_status_replicas_available |
Deployment可用副本数 |
| kube-state-metrics | kube_pod_container_status_restarts_total |
容器重启次数 |
| kube-state-metrics | kube_node_status_condition |
节点状态 |
用 kube_pod_container_status_restarts_total 可以写一个非常实用的监控:统计最近5分钟内重启次数大于0的容器:
promql复制sum by (namespace, pod, container) (
increase(kube_pod_container_status_restarts_total[5m])
) > 0
这个查询在排查线上CrashLoop时极其好用,比在控制台一个一个看Pod强多了。
3.2 暴露业务指标:从埋点到 http_requests_total
除了基础设施指标,业务指标才是真正能反映“用户感受”的数据。比如订单系统到底成不成功、支付链路有没有变慢,这类指标必须由业务代码主动暴露。
以Go服务为例,用 prometheus/client_golang 暴露指标的最小代码:
go复制package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
httpRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests.",
},
[]string{"method", "endpoint", "status"},
)
)
func init() {
prometheus.MustRegister(httpRequests)
}
func main() {
http.Handle("/metrics", promhttp.Handler())
// 业务逻辑里调用 httpRequests.WithLabelValues("GET", "/api/user", "200").Inc()
http.ListenAndServe(":8080", nil)
}
埋点时有几个原则,是我踩过坑之后总结出来的:
- 标签维度宁少勿多。每个标签值的组合都会产生一条新时间序列,标签值组合爆炸会直接把Prometheus内存打爆。典型的反面教材是把
user_id这种高基数的值作为标签,每条请求都产生新序列,第二天磁盘就满了。 - 统一指标命名规范。团队里如果每个人各起各的名字,最后Grafana面板根本没法复用。规范通常是
项目名_模块_指标名_单位,比如order_api_request_duration_seconds。 - 延迟指标用Histogram而不是Summary。Summary的分位数无法跨实例聚合,想算整个集群的P99,Summary做不到,Histogram可以。
关于Histogram的bucket设计也给个建议:不要用默认的 {0.1, 0.25, 0.5, 1, 2.5, 5, 10} 这一套,结合业务实际设置,比如支付接口的SLO是200ms,那bucket就应该在200ms附近加密采样:{0.05, 0.1, 0.15, 0.2, 0.3, 0.5, 1, 2}。bucket边界越接近真实值,分位数计算越准。
3.3 常用采集端组件速查表
除了Node和K8s层面的指标,很多组件也有现成的exporter,我的建议是不要重复造轮子:
| 组件 | Exporter | 关键点 |
|---|---|---|
| MySQL | mysqld_exporter | 注意 collect.info_schema.processlist 的开关,连接数指标需要 |
| Redis | redis_exporter | 支持Redis Cluster,配置 --check-keys 前注意性能 |
| PostgreSQL | postgres_exporter | 配置 pg_stat_database 采集,注意实例多时的标签设计 |
| Nginx | nginx-exporter / nginx-vts-module | 推荐用官方nginx-prometheus-exporter,需要在Nginx配置里开放stub_status |
| 黑盒探测 | blackbox_exporter | 支持HTTP、TCP、ICMP,配合探活告警很好用 |
| 进程监控 | process-exporter | 可以监控指定进程是否存在,替代部分脚本需求 |
用exporter也有个原则:每个exporter尽量只暴露自己负责的指标,不要图省事把所有exporter塞进一个容器里跑,不然后期定位问题会非常痛苦——你分不清数据是哪个exporter采集不了,还是Prometheus配置问题。
4. Grafana 接入与面板修炼:从第一块面板到可复用模板
Prometheus把数据存下来了,接下来要用Grafana让数据真正“看得见”。这一章我重点讲那些不亲手折腾过很难发现的细节。
4.1 数据源配置的正确姿势
Grafana添加Prometheus数据源时,URL这一段最容易翻车。如果Grafana部署在Kubernetes里,而Prometheus也部署在同一个集群,URL不能填 https://prometheus-prometheus.monitoring:9090 这种外网地址吗?能填,但容易被网络策略挡住。
我建议统一使用服务发现地址:
- 同命名空间:
http://prometheus-operated:9090 - 跨命名空间:
http://prometheus-operated.monitoring.svc:9090
另外,数据源里有一个容易被忽略的选项——Scrape interval。这个值必须和Prometheus实际的抓取间隔一致。如果Prometheus是30s抓一次,Grafana里填了15s,时间范围较短的图表上会出现锯齿状的数据空洞。我习惯在数据源里统一设置 Scrape interval: 30s 和 Query timeout: 60s。
对于新版Grafana,还要注意数据源的访问权限。云原生环境下推荐用 Server 访问模式而非 Browser,这样查询请求由Grafana服务器发出,跨域问题会少很多。
4.2 第一块面板:手把手写一条 PromQL
我见过很多新手在Grafana里点Add Panel,然后面对空白的Query窗口不知所措。先从最简单的开始:监控一台节点的CPU使用率。
完整的PromQL如下:
promql复制100 - (
avg by (instance) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100
)
这个表达式的逻辑分三层:
rate(node_cpu_seconds_total{mode="idle"}[5m]):计算过去5分钟内CPU处于空闲状态的比例。avg by (instance):每个实例聚合所有CPU核,取平均值。100 - ...:用100减去空闲率,得到使用率。
如果你有多个节点,这个查询会返回多条曲线,每条曲线对应一个 instance 标签值。
磁盘使用率的查询也经常用到,但要注意排除tmpfs、overlay这类虚拟文件系统,否则会统计出一堆无效数据:
promql复制(
node_filesystem_avail_bytes{
fstype!~"tmpfs|overlay|squashfs"
} / node_filesystem_size_bytes{
fstype!~"tmpfs|overlay|squashfs"
}
) * 100
在面板设计上,有个经验建议:尽量让面板的Y轴单位匹配指标含义。CPU使用率设为 percent,磁盘空间设为 bytes,请求延迟设为 s(秒)。这样鼠标悬停时直接显示人类可读的单位,排查问题时不用自己心算换算。
4.3 变量模板:让一套面板适配所有环境
刚开始做监控面板,最常见的动作是:在查询里写死 namespace="production",等测试环境要看,再复制一个面板改标签。这样维护成本极高,改一个查询所有面板都要跟着改。
Grafana的变量功能就是为了解决这个问题。在Dashboard Settings里添加一个变量:
- 变量类型:Query
- Query:
label_values(node_boot_time_seconds, instance) - 变量名:
instance
然后在所有查询里把写死的值替换成 $instance:
promql复制100 - (
avg by (instance) (
rate(node_cpu_seconds_total{mode="idle", instance="$instance"}[5m])
) * 100
)
这样面板顶部会出现一个下拉列表,你可以自由选择要看哪台机器。
更实用的做法是把 namespace 也做成变量:
promql复制label_values(kube_namespace_created, namespace)
如果变量之间还有联动关系,比如选完namespace之后,instance下拉只显示该namespace下的实例,可以在变量查询里用正则过滤:label_values(kube_pod_info{namespace=~"$namespace"}, instance)。这个联动功能能大幅减少误操作,尤其是集群规模大了之后,几十台机器全列在下拉框里根本没法选。
4.4 面板性能优化:为什么图多了会更卡
我经历过Grafana面板加载时间变慢,从1秒变成30秒的尴尬。最核心的因素是:面板数量 × 每张图的PromQL复杂度 × 时间范围。
几个立竿见影的优化手段:
- 使用
$__rate_interval而不是5m。$__rate_interval会根据当前面板的时间范围和抓取间隔自动计算合适的rate窗口,能显著减少Prometheus的计算压力。这是Grafana官方推荐的最佳实践。 - 减少
sum聚合的维度。查询结果里如果大量使用sum by (job, instance, pod, namespace, container),会产生非常多的中间序列。先做rate()再做sum能减少内存占用。 - 避免在Grafana里计算跨指标的值。比如需要
A/B这种除法,尽量在Prometheus里用record规则生成一个新指标,Grafana直接查询结果。这样复杂的计算由Prometheus定时完成,不用每次打开面板都重新算一遍。 - 限制默认时间范围。Dashboard的时间范围默认设为
Last 6 hours比Last 7 days合适,用户真正要长时间回溯的时候自己会改时间范围,而不是一打开就压垮存储。
5. 告警体系配置:从Prometheus规则到Alertmanager收敛
监控数据的最终目的是发现问题,如果发现不了问题,那图和面板也只是摆设。告警链路里最容易被忽视的是规则写法、收敛策略这些细节。
5.1 告警规则的写法与防误报
在Prometheus里配置告警规则,很多人以为只要写一个 expr 就行了,但实际生产环境里,规则写不好就两件事:要么报警报疯了,要么该报警的时候没报。
一个基础但完整的规则配置如下:
yaml复制groups:
- name: node_alerts
rules:
- alert: NodeDown
expr: up{job="node-exporter"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "Node instance {{ $labels.instance }} has been down for more than 2 minutes."
这里的 for: 2m 很有讲究——在云原生环境里,Pod重启、网络抖动都可能导致采集目标短暂失联,如果立即报警,大半夜会收到一堆根本不用处理的噪音。我建议 for 至少设置1到2分钟,让Prometheus确认问题持续存在后才触发。
磁盘空间告警是另一个高频规则,但很容易漏掉一个坑:node-exporter会暴露根分区、数据分区、还有各种伪文件系统。如果不排除tmpfs/overlay,一个正常的集群可能每分钟都在报警。建议规则这样写:
yaml复制- alert: DiskUsageHigh
expr: |
(
node_filesystem_avail_bytes{
fstype!~"tmpfs|overlay|squashfs",
mountpoint!~"/var/lib/kubelet/pods/.*"
} / node_filesystem_size_bytes{
fstype!~"tmpfs|overlay|squashfs",
mountpoint!~"/var/lib/kubelet/pods/.*"
}
) * 100 < 15
for: 10m
labels:
severity: warning
annotations:
summary: "Disk usage on {{ $labels.mountpoint }} is above 85%"
for: 10m 用于过滤掉节点重建期间的瞬时高指标。文件系统告警属于慢性问题,晚十分钟知道和晚一小时知道差别不大,但能显著减少误报。
写规则时有一个建议:每个规则都要先问自己“这个异常持续多久才算真正有问题”。比如CPU使用率高,持续5分钟可能是业务高峰期正常情况,持续30分钟才值得关注。告警的 for 时间要对应这个判断。
5.2 Alertmanager 分组、抑制、静默
Prometheus负责产生告警,但真正把告警发到钉钉、企业微信、邮件里的,是Alertmanager。它是告警链路里的“邮局”——负责决定消息怎么打包、发给谁、哪些暂时不发。
分组(Grouping):告警会按标签聚合成一组。比如一台机器挂了,可能同时触发多个磁盘、CPU相关的告警,如果不分组,值班人员会收到几十条重复消息。按 instance 分组之后,一条通知就能包含这台机器的所有问题。
yaml复制route:
group_by: ['alertname', 'instance']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default'
group_wait: 30s:同一组告警等待30秒后一起发出,避免瞬时抖动造成轰炸。group_interval: 5m:同一组告警的发送间隔。repeat_interval: 4h:告警未恢复时,隔多久重复提醒一次,默认4小时比较合适。
抑制(Inhibition):当某类告警已经处于活跃状态时,抑制某些低优先级告警。典型场景是节点宕机:一旦 NodeDown 触发,该节点上的所有Pod级告警就不需要再发了,因为根因已经明确。
yaml复制inhibit_rules:
- source_matchers:
- severity="critical"
target_matchers:
- severity="warning"
equal: ['instance']
这个规则的意思是:当同一台机器上出现critical级别的告警时,所有warning级别的告警被抑制。避免了对同一个根因重复轰炸。
静默(Silence):在某些已确认的维护窗口内,可以手动添加一条静默规则,在Alertmanager UI里操作即可,不需要改配置重启。
5.3 Grafana统一告警(v9+)与Alertmanager的取舍
Grafana从v8开始引入了统一告警(Unified Alerting),到了v9已经比较成熟。它可以直接从Grafana面板创建告警规则,也能把Prometheus、Loki、CloudWatch等数据源都接入同一套告警系统。于是大家经常会问:到底用Prometheus + Alertmanager,还是直接用Grafana的告警?
我的建议是区分场景:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 指标告警规则较多,且告警接收方是钉钉/企业微信 | Prometheus + Alertmanager | 规则由Prometheus定时计算,不依赖Grafana运行 |
| 需要跨多个数据源做告警聚合(如指标+日志) | Grafana统一告警 | 它天然支持多数据源,同一个规则里可以引用不同数据源 |
| 团队已经用Grafana做唯一可视化入口 | Grafana统一告警 | 学习成本低,不用单独维护Alertmanager配置 |
从生产稳定性角度,我更倾向于Prometheus + Alertmanager做核心指标告警,因为Grafana本身的可用性不能作为监控系统的前提——如果Grafana挂了,告警应该还能正常工作。而且Alertmanager的分组、抑制、静默这套机制是经过多年生产检验的。
Grafana统一告警的优势在于多数据源联动。比如可以用一条规则检查“CPU持续高 + 错误日志数量增加”这种复合条件。但这类场景相对少,基础设施级别的告警绝大多数还是单指标,所以Prometheus规则依然是主力。
6. 排错与性能调优:监控系统自己别先倒下
监控系统的本质是保障业务稳定,但如果它自己先挂了,那一切都白谈。这一章我分享一些实际排查中总结出来的经验。
6.1 查不到数据的排查链路
遇到Grafana面板没有数据,第一反应不是怀疑Prometheus,而是先理清排查链路。
我的排查顺序是:
- 先看Grafana的Query Inspector。它能直接看到实际发给Prometheus的请求和返回结果,能判断是查询语句的问题还是数据源的问题。
- 再查Prometheus的Targets页面。如果目标状态是DOWN,说明采集有问题;如果是UP但没数据,说明exporter没暴露对应指标。
- 直接在Prometheus的Graph页面执行查询。如果这里有数据但Grafana没有,说明Grafana查询写法有问题;如果这里也没有,说明采集端或PromQL有问题。
- 确认指标名和标签名完全一致。复制粘贴最容易出错的点就是大小写和拼写,比如
node_cpu_seconds_total少写一个s。
常见的PromQL问题里,时区也值得一提。时间范围显示不对、数据延迟15分钟出现,通常不是Prometheus问题,而是容器时区设置不对。建议在Grafana数据源配置里把 Timezone 设为 utc 或者用户本地区域,避免默认时区错位导致排错时对不上时间线。
6.2 指标高基数问题:监控系统性能的隐形杀手
“高基数(High Cardinality)”是Prometheus运维里最需要重视的概念。基数指某个标签所有可选值的数量。method 标签大概就几个值,基数很低;但 user_id 可能有几十万个值,基数极高。
每一条带不同标签值的指标都是一条独立的时间序列,Prometheus需要为每条序列维护内存索引。当序列数量从10万涨到100万,内存占用可能是非线性上涨。我见过一个团队把用户ID当标签后,Prometheus内存从4G涨到30G,最后直接把节点打爆。
避免高基数的核心原则:
- 标签值只能是低基数的枚举值,如
method、status、namespace。 - 高基数数据(如用户ID、订单ID)请存日志系统,而不是监控系统。
- 在relabel阶段用
metric_relabel_configs主动丢弃不需要的高基数标签。
metric_relabel_configs 示例:
yaml复制metric_relabel_configs:
- source_labels: [user_id]
regex: '.+'
action: drop
6.3 磁盘空间与长期存储方案
Prometheus默认把数据存在本地磁盘,本地存储简单高效,但有上限——单节点磁盘再大也是有限的,而且一旦节点故障,历史数据就没了。
在集群规模变大后,你需要直接面对两个问题:数据保留多久,数据放哪里。
短期方案是设置合理的 retention:
yaml复制prometheus:
prometheusSpec:
retention: 15d
retentionSize: "50GB"
retentionSize 比 retention 更实用——它保证本地存储不超过50G,超过后自动淘汰旧数据。建议两者都设。
如果要长期保留或做多集群统一查询,就需要上Thanos。Thanos通过对象存储(比如S3、MinIO)保存历史数据,同时提供全局查询视图。在生产环境里,我通常的做法是:热数据保留在Prometheus本地15天,Thanos把超过15天的数据存到对象存储,随时可以回溯查询。
配置Thanos需要改Prometheus的启动参数,加上 --storage.tsdb.min-block-duration=2h 等参数,然后Thanos sidecar会上传TSDB块到对象存储。这一层改造相对复杂,但如果业务有合规审计、长期容量趋势分析的需求,这一步迟早要推。
配置Grafana时还有一个小技巧:如果用了Thanos,数据源URL指向Thanos Query的地址,所有历史数据都能通过同一个Grafana面板查询,不需要切换数据源。
6.4 用 promtool 验证规则:告警上线前最后的防线
告警规则写错了,最坏的结果是Pod一直报错但没人知道。我有一个习惯:所有告警规则上线前先用 promtool 验证一遍。
bash复制promtool check rules /path/to/rules.yaml
此外还有 promtool test rules,可以写单元测试来验证规则是否按预期触发。虽然这个功能很多人没用过,但它真的能防止“自认为规则没问题,结果生产环境压根不报警”的尴尬。
一个简单的规则测试文件:
yaml复制rule_files:
- rules.yaml
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: 'up{job="node-exporter", instance="10.0.0.1:9100"}'
values: '0 0 0 0 0'
alert_rule_test:
- eval_time: 4m
alertname: NodeDown
exp_alerts:
- exp_labels:
severity: critical
exp_annotations:
summary: "Instance 10.0.0.1:9100 down"
这个测试模拟了up指标连续4分钟为0的情况,确认NodeDown告警会在2分钟后如期触发。有了这个测试,以后改规则时也敢放心提交了。
从监控系统搭建到真正能在业务出问题时第一时间给出答案,中间的距离就是这些细节。我自己的体会是,Prometheus和Grafana的学习曲线并不在工具本身,而在于你怎么理解数据模型、怎么设计标签、怎么写查询、怎么收敛告警。工具每天都在更新,但底层这套思路和坑,在云原生监控这个领域里是通用的,会长期有效。
