监控可视化这件事,现在基本绕不开两个名字:Prometheus 和 Grafana。无论是做服务器资源监控、应用性能观察,还是业务指标大盘,这套组合几乎成了标配。前面几篇我们讲完了部署、采集、告警这些前置环节,这篇就把“可视化”这一层彻底聊透——从最基础的 Dashboard 设计思路,到模板变量、告警联动、疑难杂症排查,争取一篇看完,你自己就能搭出一套看着舒服、用着顺手的监控大屏。
这篇内容适合谁?从刚接触监控的新手,到已经跑通采集、想优化展示效果的运维同学,都能从中找到用得上的东西。我会避免讲一堆云里雾里的概念,尽量用实际场景和踩坑经验来说话。
1. 整体设计:监控可视化方案为什么这么选
1.1 可视化要解决的不只是“画图”
很多人一提到监控可视化,第一反应就是把曲线画出来。真正做了几年监控后,我的理解是:可视化本质上是把“数据”转化成“决策信息”的过程。机器上有几千个指标,如果只是把 CPU 曲线堆在屏幕上,那只是数据搬运,远谈不上可视化。
一个合格的监控可视化系统,至少要解决三个层面的问题:
第一层是“发生了什么”。比如 CPU 突然飙到 95%,这属于实时状态的呈现,偏重对故障的感知。第二层是“为什么发生”。这时候需要把不同指标的曲线放在同一个时间轴里对比,像请求量、延迟、错误率、GC 次数,只有放到一起才能看出因果。第三层是“接下来会发生什么”。依赖趋势数据和预测算法,预估一下磁盘还能撑几天、连接数会不会在半小时后爆掉。
Prometheus 负责的是“数据层”,它把指标抓下来、存起来,并且提供了强大的 PromQL 查询能力;Grafana 负责的是“呈现层”,把 PromQL 查出来的结果渲染成各种图表,同时承担告警和权限管理。两者分工非常清晰,这也是这套组合能火这么多年的核心原因。
1.2 Prometheus 的数据模型为何适合做可视化
Prometheus 能成为监控领域的事实标准,不是因为它的引擎性能有多极致,而是它的数据模型恰好长在了可视化的需求上。
每一条时序数据都由“指标名 + 标签集合”唯一标识。比如 http_requests_total{method="GET", endpoint="/api/user"} 这组数据,既描述了“是什么指标”(request 总量),又带上了“从哪个维度切分”(method、endpoint)。这意味着在绘图时,你天然拥有了多维度的数据透视能力。
用生活化的例子理解:这就像一张流水账里,每一行除了金额,还记了“哪个销售员”“哪个地区”“哪条产品线”。想按销售员看汇总,筛选一下标签就行;想对比不同地区的趋势,直接做分组聚合。Prometheus 的标签体系天生就是这个结构,所以在 Grafana 里,你几乎可以随意地从各个维度切分、对比、钻取数据,而不需要预先设计固定的报表格式。
另外值得夸的是 Pull 模型。Prometheus 定期主动去抓取目标暴露的指标,这个设计和可视化结合后特别舒服:你可以随时知道采集目标的健康状况,抓取失败会在图上直接显示断点,而不像 Push 模型那样,数据没来你都分不清是业务停了还是采集器挂了。
那为什么可视化层选了 Grafana,而不是 Prometheus 自带的 UI 或者 Kibana?用一份简单对比来说明:
| 维度 | Grafana | Prometheus 自带 UI | Kibana |
|---|---|---|---|
| 图表丰富度 | 极高,几十种面板 | 极简,仅适合临时查询 | 一般,强在日志检索 |
| 多数据源支持 | 非常完善,可同时打通多套数据 | 仅限 Prometheus | 主要绑定 ES |
| 告警能力 | 完整,支持多种通知渠道 | 需配合 Alertmanager | 有但配置繁琐 |
| 上手成本 | 中等,文档丰富 | 极低 | 较高 |
| 可扩展性 | 插件生态非常丰富 | 有限 | 有限 |
这个表格背后有一个很实际的经验:把可视化集中到一个平台处理,对未来是非常划算的。因为你不可能只有 Prometheus 这一套数据源。走在前面的团队,通常还会接 Loki 日志、Tempo 链路追踪、MySQL 业务表甚至 Elasticsearch。Grafana 最大的隐性价值,是它把这些来源迥异的数据统一到了同一个交互界面里。排查问题的时候,一边看着指标曲线,一边切到日志面板,体验非常顺畅。
2. 核心细节拆解:把指标和图表“接上电”
2.1 搞懂指标类型,才知道图该画成什么样
在实际配置 Grafana 之前,有几个 Prometheus 的概念必须得先搞清楚。很多人卡在“不知道写什么 PromQL”,根因就是没理解指标类型。
Prometheus 的指标分为四种核心类型:
- Counter(计数器):只增不减,适合描述累计量,比如请求总数、CPU 总时长。画面上的关键操作通常是取速率,也就是 rate() 函数。
- Gauge(仪表盘):可增可减,适合描述当前瞬时状态,比如温度、内存占用、在线人数。直接绘图即可。
- Histogram(直方图):适合描述分布,比如请求响应时长的百分位数。一般会配合 histogram_quantile 函数,画出 P50、P95、P99 曲线。
- Summary(摘要):同样用于分布统计,但它是在客户端计算好后直接上报的,缺点是无法跨实例聚合。
我在实际项目里见过最典型的新手错误,是拿 Counter 直接绘图。Counter 类的指标只增不减,曲线永远在爬坡,这会导致你根本看不出请求量波动。正确的做法是先用 rate() 算每秒速率,再画图。类似地,想看延迟,别直接画 http_request_duration_seconds_sum / http_request_duration_seconds_count 这种裸的平均值,它会被极少数慢请求带偏,画出来的曲线参考价值有限。这时候用 Histogram 类型算 P99 才是合理的选择。
2.2 抓取配置里的几个关键抉择
Grafana 的画布只是皮囊,真正决定画质的是 Prometheus 抓下来的数据质量。我之前帮一个团队排查过一个问题:Grafana 上的曲线频繁出现“空心”缺口,业务方看着心慌,以为是采集挂了。后来定位发现是抓取超时——监控目标的应用接口偶尔响应慢,超过了配置的 scrape_timeout,该轮抓取就被丢弃了。
这里有个容易疏忽的比例关系:scrape_interval(抓取间隔)和 scrape_timeout(抓取超时)是有约束的,超时必须小于间隔,官方建议给抓取留出至少三分之一的时间余量。比如 10 秒的抓取间隔,超时最多配 8 秒,稳妥一点就配 5 秒,避免因为目标响应抖动导致整轮采集失败。
再看下面这份常见的配置片段:
global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: "node" static_configs: - targets: ["192.168.1.20:9100"] labels: env: "prod" relabel_configs: - source_labels: [__address__] regex: "(.*):9100" target_label: "instance" replacement: "${1}"先说 scrape_interval。15 秒是默认值,也是多数场景下的平衡点。有些团队为了追求曲线的“丝滑感”,改成 5 秒甚至 1 秒,其实大概率只需要把 rate 函数里的窗口调大一点就能达到视觉平滑的效果。抓得太频繁带来的副作用是:TSDB 的写入放大、存储空间加倍、查询性能下降。我们通常只在采集关键业务指标时把某些 Job 单独降到 10 秒,基础设施类的保持 15 到 30 秒就够。
再说 relabel_configs。这块很多新手直接跳过,但它是控制数据“长什么样”的关键阵地。像上面的配置,把 192.168.1.20:9100 里的端口信息去掉,instance 标签只保留 IP。这样处理后,Grafana 绘图时显示的图例就是干净的主机 IP,不会带着一排 :9100 的尾巴。类似地,你还可以通过 relabel 把不同环境的标签注入进去,这在多环境的大盘设计中非常有用。
2.3 PromQL:可视化的灵魂语言
在 Grafana 面板里写查询表达式,是“从数据到图表”的临门一脚。很多人看到长长的 PromQL 就头皮发麻,其实常用到的就那么几个模式。
核心常用的函数可以按场景归归类:
- 速率类:rate(),适合 Counter。注意推荐用 rate 而不是 irate,前者是区间平均速率,曲线平滑;后者是瞬时速率,曲线锯齿感强,适合看突发的场景。
- 聚合类:sum()、avg()、max()、min(),按标签维度做聚合。通常和 by (label) 配合。
- 趋势类:predict_linear(),用于预测未来值。磁盘告警经常用这个。
- 分组类:topk(),取 TopN,比如看哪个实例的 CPU 最高。
- 分布类:histogram_quantile(),算延时百分位。
举一个真实的例子。要画“各实例的 CPU 使用率”,新手会写一大串乱七八糟的数学公式,其实标准写法是这么几行:
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)这个查询先取 CPU 的空闲时间占用的速率,取平均后乘以 100 转成百分比,再用 100 减掉,得到的就是忙碌率。理解这个思路,很多系统指标都能照葫芦画瓢。另外,一个容易被忽略的细节:range selector 里的 [5m] 要和 Grafana 面板的时间范围区分开。前者是“计算窗口”,后者是“展示范围”。窗口太小,曲线容易抖动;窗口太大,趋势滞后。5 分钟是我们实践下来比较舒服的默认值。
3. 实操过程:从零搭建一套可视化大盘
3.1 五分钟跑起基础环境
实践出真知,先快速跑起一套最小可用的环境。假设你已经有了一个能访问的 Prometheus(二进制的和容器化的都行),下面用 docker compose 方式把 Grafana 和两个常用 exporter 一并拉起来。这套组合很适合当实验床,后续什么配置都可以在上面验证。
version: '3.7' services: prometheus: image: prom/prometheus:v2.47.0 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=30d' ports: - "9090:9090" grafana: image: grafana/grafana:10.1.0 environment: - GF_SECURITY_ADMIN_PASSWORD=admin123 ports: - "3000:3000" depends_on: - prometheus node-exporter: image: prom/node-exporter:v1.6.0 ports: - "9100:9100" mysqld-exporter: image: prom/mysqld-exporter:v0.15.0 command: - '--config.my-cnf=/etc/mysql/.my.cnf' volumes: - ./my.cnf:/etc/mysql/.my.cnf environment: - DATA_SOURCE_NAME=monitor:password@(mysql-host:3306)/ ports: - "9104:9104"启动完成后,先用浏览器确认 Prometheus 的 web 界面能访问,同时在 Status -> Targets 页面里看到所有 exporter 都是 UP 状态。这一步别跳过,后面 Grafana 画不出图,八成是这里没通。
3.2 接入 Grafana 数据源
打开 Grafana 后,进入 Connections -> Data Sources,点击“Add data source”,选 Prometheus。关键配置就这么几项:
- URL:填 Prometheus 的地址。如果是容器部署,填 http://prometheus:9090;如果是二进制部署在同一台机器,填 http://127.0.0.1:9090。
- Scrape interval:这里填一个和 Prometheus 抓取节奏匹配的值,通常 15s。它决定 Grafana 拉取数据的频率。
- 更高级的设置里,有个“Custom HTTP headers”,如果后面 Prometheus 加了反代认证,可以在这里配置。
保存后点击“Save & test”,看到绿色的 Success 提示就说明通了。这个环节几乎人人都能过,真正的坑在于——数据源连通了,不代表有数据。比如 mysqld-exporter 的很多指标默认是禁止收集的,需要在启动参数或配置文件里打开。某次我排查慢查询图表空白,查了两个小时才发现,原来是 exporter 没开启 performance_schema 相关的采集项。
3.3 手把手搭建一个主机监控面板
环境就绪,用 node_exporter 的数据来建第一个面板。
新建 Dashboard,添加一个 Panel。在查询编辑器里输入:
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)右侧设置里,Panel type 选择 Time series,Unit 选择 Percent(0-100)。一个 CPU 使用率的图画出来了。
按同样的套路,把另外几个基础指标配上:
- 内存使用率:
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100- 磁盘使用率:
100 - node_filesystem_avail_bytes / node_filesystem_size_bytes * 100这里要特别小心的一个坑:磁盘挂了多块盘、多个挂载点的时候,不加 by 聚合,Grafana 上就会出现一堆线条让人眼花缭乱。建议先用实例和挂载点做维度区分,再按需筛选:
100 - node_filesystem_avail_bytes{mountpoint!="/boot"} / node_filesystem_size_bytes{mountpoint!="/boot"} * 100写这个例子的重点是排除那些没必要盯着的挂载点,像 /boot 这种分区,因为容量小,稍微写点东西使用率就跳得很高,很容易触发无意义的告警。
3.4 模板变量的高级用法
一个面板建好后,另一个环境、另一组机器怎么复用?答案是模板变量。
在 Dashboard Settings -> Variables 里新增变量。这是让 Dashboard 从“一次性报表”进化为“自助分析工具”的关键。
我给一个最常见的数据库监控场景举例:
label_values(mysql_up, instance)这个表达式把所有送过数据的 MySQL 实例自动采集为一个下拉列表。然后在 Panel 的查询里,写成这样:
rate(mysql_global_status_queries{instance="$instance"}[5m])之后,打开这个 Dashboard 的人就可以直接在下拉菜单里切换要观察的实例,而不用去改查询。同样逻辑也可以套环境标签:
label_values(node_uname_info, env)这样就能把“生产环境”和“测试环境”的机器分开看。
模板变量这块,我记得自己第一次用的时候,花了不少力气去理解 Query 类型变量和 Interval 变量的区别。简单说,Query 类型变量是让用户做选择用的,值来自某个查询结果;Interval 变量则是定义时间粒度,比如按 1m、10m、30m 来动态聚合数据,用来控制图表的数据密度。两者配合使用,能让 Dashboard 在“全量明细”和“长期趋势”之间无缝切换。
3.5 图表展示的经验之谈
面板数量和布局,这件事看着自由,其实有讲究。太多人第一次搭 Dashboard,什么都想往里面塞,结果一面墙几十个图,看起来热闹,真出问题反而不知道往哪看。
我习惯用“分层”的思路组织 Dashboard:最上面一排是总览指标,只放最关键的 4 到 6 个,比如整体 CPU、内存、网络流量、在线人数;中间一排放细分维度,比如按实例分开的资源使用率;最下面一层放排查类指标,比如磁盘 IO、上下文切换、文件描述符等等。
配色上,我会人为约定:CPU 系列统一用暖色,内存用蓝色,网络用绿色。Grafana 有内置的按查询排序的配色方案,也可以把固定阈值加上。比如 CPU 超过 80% 自动变红,这在视觉上比数值更直观。
还有一个细节是“时间范围”。Dashboard 右上角的时间选择器,默认是 Last 6 hours。有的人做的监控图,别人一打开默认看 6 小时,其实完全不够。我一般把默认调成 Last 24 hours,配合模板变量里的 interval,让 6 小时以上的数据自动聚合到分钟级,既能看趋势,又不至于因为数据点太多导致出图很慢。
4. 告警联动:可视化不只是给人看,还要能“喊人”
4.1 在 Prometheus 侧配置告警规则
Grafana 本身有告警能力,如果要统一管理,更成熟的方案是走 Prometheus + Alertmanager。
告警规则文件一般长这样:
groups: - name: host_alerts rules: - alert: HighCPUUsage expr: 100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85 for: 5m labels: severity: warning annotations: summary: "{{ $labels.instance }} CPU 使用率过高" description: "当前值 {{ humanize $value }}%"这个规则里面,expr 就是刚才图表里的那条 PromQL,加上大于 85 的阈值判断。for: 5m 表示“持续 5 分钟才触发”。这一项非常关键,不加的话,一次瞬时抖动就会触发一堆告警。加了之后,系统会先观察 5 分钟,如果 CPU 降下来了就不发,确认持续高才发。
配置好规则文件后,在 prometheus.yml 里引用它:
rule_files: - "alert_rules/*.yml"然后检查 Rules 页面,确认规则加载成功。
4.2 Alertmanager 路由与抑制规则
Alertmanager 负责“把告警发给谁”,它支持路由树、分组、抑制和静默,远比 Grafana 内置的告警灵活。
下面是一份最简但可用的路由配置:
route: group_by: ['alertname', 'instance'] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: severity: warning receiver: 'ops-wechat' - match: severity: critical receiver: 'ops-phone' receivers: - name: 'ops-wechat' webhook_configs: - url: 'http://webhook.example.com/send' - name: 'ops-phone' webhook_configs: - url: 'http://webhook.example.com/call'写配置的时候,三个时间参数是决定“告警轰炸”还是“告警淹死”的核心:
- group_wait:同一组的第一条告警产生后,等多久再发出去。给其他告警一点时间一起扎堆进来,避免一两分钟内连续收到多条邮件。
- group_interval:同一组的告警之间没有新变化时,多久再发一次。
- repeat_interval:告警一直没恢复时,多久提醒一次。这个值我一般设 4 小时起步,太短容易让人产生“告警疲劳”,看到通知都懒得点开。
4.3 一条告警引发的“风暴”教训
说个我们实际经历过的场景:某天凌晨,一台机器的磁盘 inode 打满,node_exporter 的告警规则被触发,于是 Alertmanager 把邮件发给了整个运维组。巧的是,这个问题影响了一个应用集群,导致集群里每台机器状态异常,而每一台都产生了自己的告警。最终结果是半小时内收到了 100 多封相似内容的邮件。
事后排查,问题有两个:一是告警规则粒度不够,inode 使用率高通常只影响特定节点,不需要把所有节点都报警;二是没有利用好 group_by 的聚合能力。后来我们把 group_by 改成 ['alertname'],同一类型的告警全部聚成一条,只在内容里列出受影响实例的列表,紧急程度反而提高了。
4.4 告警阈值为什么不建议“一次调到位”
要告警可靠,阈值是要花时间调校的。刚上线就设得很激进,比如 CPU 超过 30% 就告警,结果生产环境高峰本来就能到 50%,于是告警一天到晚在响。这种情况比不设告警更糟糕——狼来了太多次,遇到真的故障,大家反而麻木了。
我们当时的做法是“以周为单位调整”:第一周把目标阈值都调成比较宽松的值,观察一周内的触发频率;第二周根据触发率逐步收紧;特别重要的指标,用 predict_linear 预测,务必在磁盘真正写满之前就发出预警。调整阈值时要打开 Grafana 告警规则里面的“预览”功能,可以看到这条规则在最近一段时间内的命中次数,避免盲目拍脑袋。
5. 常见问题与排查技巧实录
5.1 图表出现缺口:抓取丢失还是存储问题
图表断点是出现频率最高的现象。除了上一节提到的抓取超时,还有两个隐蔽原因:
一个是 Prometheus 存储本身的写入冲突。如果你把存储目录放在网络盘上,经常会出现写入延迟导致某些批次丢弃;另一个是时间线同步问题。监控目标的机器如果时钟偏了,抓取到的数据时间戳就会错位,导致在图上显示为“未来时刻”的数据,曲线看起来像平移了一样。
排查时,直接在 Prometheus 的查询页面执行相同表达式,如果查询结果也有断点,那问题出在采集侧;如果查询的数据是齐的,那问题出在 Grafana 的渲染或数据源设置。这个“自底向上”的排查思路,能让你少走不少弯路。
5.2 高基数指标的存储爆炸
Prometheus 最容易被诟病的点就是存储。不要怪它,因为它本质上是“用空间换灵活”的架构。一旦某个指标带上了高基数的标签,比如把用户 ID、订单 ID 作为标签,存储量就会呈指数级膨胀,甚至连 Prometheus 进程都会 OOM。
有次我排查一个 Prometheus 内存飙高的问题,最后发现是应用的某个 Counter 指标里带了 http 请求路径作为标签,而路径里又带有动态参数。每个用户请求都会生成一条新序列,两天不处理就把几十GB磁盘吃光了。
解决方案无外乎两个方向:一是应用侧改造,限制标签的取值范围;二是加 recording rule,把高基数指标降维成低基数指标,比如提前按接口聚合好再存储。可视化层遇到这类问题其实是好事,它比监控系统本身更早地暴露了数据质量问题。
5.3 查询太慢,图就是出不来
面板加载慢也是一个高频投诉。最常见的元凶是查询里用了过大的 range selector,比如 [1d],这会让 Prometheus 扫描大量原始数据。处理办法是用 recording rule 预先计算好长时间窗口的聚合值,查询的时候只扫聚合结果,速度能快一个数量级。
另一个技巧:尽量让 Grafana 的 Minimum interval 与数据精度对齐。比如重聚合后的数据是 5 分钟粒度,那面板的最小时间间隔也设成 5m,省得查询层多做很多无谓的运算。
5.4 告警没触发,到底为什么
告警没触发,一般先看三处:
- Alertmanager 是否加载了新配置,重载配置可以用
kill -HUP,或者通过它的 web 界面 reload。 - 告警规则是否有语法错误,Prometheus 的 Rules 页面会直接标红提示。
- 是不是被 inhibit_rules 抑制了。抑制规则会让“高优先级告警”自动静默“低优先级告警”,以避免告警风暴。如果你设了主机挂了就抑制该主机上的服务告警,那确实看不到服务告警——因为它被吞了。
有一次我排查“钉钉群收不到告警”,最后发现是 Alertmanager 的 webhook 地址少了一个斜杠,导致消息发出去 404。这类集成问题排查的时候,先看 Alertmanager 的日志,通常能一击命中。
5.5 数据在 Grafana 正常,网页端却对不上
还有一个我遇到过不少次的“灵异现象”:Grafana 图上显示和直接敲 PromQL 查出来的结果不一致。通常这是因为 Grafana 面板的“Query type”被设置成了 Instant 而不是 Range。Instant 只取当前时间点的值,画出来就是单点;Range 才会返回整段时间序列。另外一个容易被忽略的是 Grafana 时区设置,如果服务器本地时间和浏览器不一致,图上的时间轴就会“偏移”。
5.6 长期维护:容量规划与备份恢复
最后聊点运维层面的经验。Prometheus 本身不提供高可用能力,但多副本 + 联邦是常见的生产方案。存储上务必留 2 到 3 倍的余量,因为指标量只会越加越多。Grafana 的 Dashboard 和 Datasource 配置建议定期用 JSON 导出备份,或者直接用 Provisioning 方式管理。这块我吃过亏:一次服务器迁移,Grafana 的数据目录没备份,几十个手工调好的 Dashboard 一夜回到解放前。从此以后,所有面板配置都纳入了版本管理。
写在最后的一点心得
如果你准备在生产环境落地监控可视化,我最后再啰嗦几句务实的建议。第一,监控体系是一层一层搭出来的,先有稳定可达的采集,再做可视化,再谈告警,顺序别反过来。第二,别追求面板数量的极致,追求“能被用起来”的面板,一两个核心大屏解决 80% 的日常问题,比一百个花花绿绿的图表更有价值。第三,告警规则里写的每一个阈值,背后都要有真实的业务场景支撑,别凭空想一个数字就配上去。Prometheus 和 Grafana 这套组合,上手确实不难,但做得越深,越能体会到“数据模型”这四个字的分量。把基础打牢了,后面做日志关联、链路追踪统一视图,都是顺水推舟的事。