第10篇 Grafana 可视化进阶:变量、下钻与统一 Dashboard

0 阅读6分钟

前面八篇把指标采集、告警规则和 Alertmanager 路由全梳理了一遍,但若全配置成面板,只能看见满屏的曲线和一堆意义不明的下拉框。

这是可视化层面最典型的问题:数据全了,但信息没出来。

本篇集中解决三个事——变量(让面板动态起来)、下钻(从宏观到微观的一条路)、以及统一 Dashboard(一张屏看完所有层)。本篇不是教 Grafana 所有功能,而是提供一套经过验证的「从 Overview 到根因」的面板结构。

本篇基于 Grafana 13.0.7,PromQL 指标名沿用本系列第3~7篇,变量与标签约定沿用第2篇的 job / svc / type 标签体系。


一、变量:让面板不再一把死尺

没有变量的 Dashboard 是一幅静态截图。有了变量,同一个面板可以根据选中的 job、svc、ip、instance 切换数据源,不需要为每种组合重复建图。

1、变量的类型选择

Grafana 支持七种变量类型,日常监控用得最多的只有三种:

变量类型作用典型用法
Query从数据源实时查出一组值作为选项查所有 job、svc、instance
Custom手写固定选项组环境快捷切换、固定标签值
Interval按时间范围自动匹配间隔给 $__rate_interval 或 $interval 传参

其余类型(Constant、Ad hoc filters、Data sources、Text box)在监控场景下用得少:

  • Ad hoc 适合临时过滤但不可控,
  • Text box 需要用户手输——值班场景下「能选就不让输」。

2、一套实用的变量体系

变量之间不追求多层级联,而是按维度拆平。

全局变量推荐清单(按选值粒度从粗到细):

变量名类型定义或 PromQL用途
$jobQuerylabel_values(up, job)系统/应用标识,最顶层筛选
$svcQuerylabel_values(up{job=~"$job"}, svc)服务标识,第二层,一个系统/应用一般包含多组服务
$typeQuerylabel_values(up{job=~"$job", svc=~"$svc"}, type)资源类型,如 node / jvm / ...,在告警抑制中用于跨层压制(见第8篇),一组服务涉及多种资源
$ipQuerylabel_values(up{job=~"$job", svc=~"$svc"}, ip)服务器 IP,一台服务器部署了多个实例时有用,或同时查看一台服务器多层监控数据时有用,一组服务一般涉及多台服务器
$instanceQuerylabel_values(up{job=~"$job", svc=~"$svc", ip=~"$ip"}, instance)具体实例,一组服务涉及多个实例
$envCustomprod, emergency一般分为生产、应急;一个系统/应用可能会部署多套环境,但只有一套时该变量无意义
$intervalIntervalauto自适应时间窗口

联动逻辑:每一级变量在 PromQL 中引用了上一级的值作为过滤条件,切换 $job 时 $svc 自动刷新,切换 $svc 时 $type、$ip、$instance 自动刷新。

# 定义 $svc 的查询语句(关联 $job)
label_values(up{job=~"$job"}, svc)

# 定义 $type 的查询语句(关联 $job、$svc)
label_values(up{job=~"$job", svc=~"$svc"}, type)

# 定义 $ip 的查询语句
label_values(up{job=~"$job", svc=~"$svc"}, ip)

# 定义 $instance 的查询语句(关联 $job、$svc、$ip)
label_values(up{job=~"$job", svc=~"$svc", ip=~"$ip"}, instance)

⚠️ $ip 和 $instance 的区别在于:$ip 是服务器维度的筛选,当一台服务器上同时跑了多个实例(例如同一台机器上既跑 node_exporter 又跑 jvm exporter)时,用 $ip 可以把这台机器的主机层和 JVM 层数据同时展示出来;$instance 是具体实例维度的筛选,只看某个单一进程或 exporter。两者不可互相替代。

变量间的层级关系:

$job(系统/应用,最顶层)
  ├── $svc(服务,第二层,一个 job 包含多组服务)
  │     ├── $type(资源类型,一组服务涉及多种资源)
  │     ├── $ip(服务器 IP,一组服务涉及多台服务器)
  │     └── $instance(具体实例,一组服务涉及多个实例)
  └── $env(环境,一个系统/应用可能部署多套)

变量体系控制在三层以内($job → $svc → 其余同级),$svc 是 Dashboard 中最核心的筛选维度,下钻链路中按 $svc 聚合、按 $ip 和 $instance 展开。

3、$type 在告警抑制中的关键作用

抑制规则依赖 type 标签:

# 第8篇 inhibit_rules - 跨层抑制(依赖 type 标签)
inhibit_rules:
  # 规则 1:主机层 critical 压住同 job+svc 的应用层 warning
  - source_matchers:
      - severity = "critical"
      - type = "node"
    target_matchers:
      - severity = "warning"
    equal: ['job', 'svc']

$type 变量在 Dashboard 中不仅用于过滤面板数据,更是值班人员通过面板上的 $type 能一眼看出当前的资源归属哪一层,结合告警面板确认抑制链路是否按预期工作。

4、变量的三个常见坑

坑一:label_values() 与 query_result() 的区别

  • label_values(up, job) 直接从指标返回的标签取值,速度快。
  • query_result(count by (job) (up)) 会执行完整查询再提取结果,慢一到两个数量级。
  • 能用 label_values 就别用 query_result。

坑二:标签值不存在时面板报错

当 $job 选了某个应用而 $svc 下的 $type 没有「node」时,面板会显示空白并报 No data。这不是故障,但值班的人看到会慌。

解法:在面板的查询选项里加过滤条件,或者在变量定义时增加正则排除。

坑三:Multi-value 开启后的曲线显示问题

当变量允许多选(Multi-value)且面板查询中用了 svc=~"$svc",如果用户没选任何值,默认会匹配全部。这个行为在小环境里没事,但在有几十个服务的大环境里,一次全选会让 Dashboard 加载慢到不可用。

建议只对 $ip 和 $instance 开多选,$job、$svc、$type 保持单选。


二、下钻:从 Overview 到根因的一条路

变量解决了「同一面板看不同对象」的问题,但值班的人面对 Dashboard 时的真实操作路径常常是这样的:

  1. 入口面板发现「应用层错误率 > 5%」
  2. 点进去看具体是哪个 svc 在报错
  3. 再点进去看这个 svc 对应的主机资源(通过 $svc 传递)
  4. 最后定位到是某台服务器的磁盘 I/O 排队了

每一步都需要从一个更宏观的面板「钻进」一个更微观的面板。Grafana 里实现这个链路不靠插件,只靠链接。

1、三种下钻方式对比

方式实现如何传递上下文适用场景
Dashboard 链接在面板配置里加 Link → Dashboard手动映射变量名(var-svc=$svc)跨 Dashboard 下钻
数据链接在面板配置里加 Link → 自定义 URL 或字段提取当前数据点的标签值从曲线点到日志、追踪系统
Explore 跳转面板上选「Inspect → Query → Run in Explore」自动携带 PromQL 和变量临时排查,不需要长期保留

日常用最多的是 Dashboard 链接:从入口 Overview 跳到主机的 USE 面板,跳转时把 job、svc、type、instance 带过去,到目标面板后变量自动选好,不用再手选一次。

2、配置一个标准的 Dashboard 链接

以「从总览面板点击某个 instance 跳转到主机 USE 面板」为例:

类型:Dashboard
目标 Dashboard:选择「主机 USE 面板」
在 URL 参数里添加:
  var-job       → ${__field.labels.job}
  var-svc       → ${__field.labels.svc}
  var-instance  → ${__field.labels.instance}
  var-type      → node              # 固定值,因为目标面板只看主机层

关键在这个 var- 前缀:Grafana 的 Dashboard 链接中,var-变量名=值 的格式会把值自动填入目标面板的同名变量里。

如果目标面板里有多个变量(job、svc、type、instance),链接里至少要把必需的传过去,否则目标面板打开后变量是空的,图也空着。

⚠️ 注意: 使用 Dashboard 链接时,目标面板的变量定义和源面板的标签命名必须对齐。

3、下钻链路设计

一张 Dashboard 里塞进所有指标,不是「统一」,是「堆砌」。建议按下钻深度拆成三层,核心传递标识是 $svc:

第一层:服务 Overview(入口)

  • 一行:延迟 + 流量 + 错误 + 饱和度(黄金信号四条曲线)
  • 第二行:按 svc 拆开的小图阵列(每个 svc 一张 RED 三图 Row)
  • 变量:$job、$svc、$instance
  • 下钻路径:点击 instance → 跳转到对应的资源 USE 面板

第二层:资源 USE(下钻一层)

  • CPU、内存、磁盘、网络 各一块面板
  • 变量:$job、$svc、$type、$ip、$instance
  • 下钻路径:点击「磁盘 I/O 高」→ 跳转到专项磁盘面板

第三层:专项深度(下钻两层)

  • 只有特定场景才需要看,不在主面板中展示
  • 例如:磁盘延迟分布、CPU 各核心使用率、网络重传率具体趋势
  • 变量:$svc、$ip、$instance 为主

下钻链表示例:

[服务 Overview] ──点击 instance──→ [资源 USE 面板] ──点击高磁盘→ [磁盘专项面板]
       │                                    │
       └──点击 instance──→ [JVM 面板] ──────┘  (如果 type=jvm,跳 JVM)

下钻链路一旦固定,排查路径就是条件反射。不需要每次出故障重新想「该看哪个面板」。

4、数据链接:从曲线点到根因

Dashboard 链接跳的是另一个 Dashboard,数据链接跳的是某个数据点关联的外部系统。

// Grafana 面板 → 数据链接配置示例
// 链接类型:自定义 URL
// URL 模板:
https://your-jaeger.example.com/search?service=$svc&tags=instance%3D$instance

这个链接会被渲染成:点击曲线上的任意数据点 → 弹出菜单 → 一键跳转到 Jaeger 搜索页,自动带上当前 svc 和 instance。(前提是你已经部署了链路追踪。)

告警面板也可以用同样的思路:从告警规则直接链到 Alertmanager 的 UI,查看当前告警是否被抑制或静默。


三、统一 Dashboard:一张屏看完所有层

有了变量和下钻,下面再聊一聊 Layout 。

一张合格的监控 Dashboard 不是把图全部平铺,而是按层组织、按行对齐、从左到右流动。

1、Row 组织原则

推荐按「监控对象的层次」排 Row,而不是按「指标类型」:

Row 1: 入口层(黄金信号)      —— 延迟、流量、错误、饱和度
Row 2: 应用层(RED)           —— QPS、错误率、响应时间
Row 3: 运行时层(JVM 等)      —— 堆内存、GC、线程池
Row 4: 主机层(USE)           —— CPU、内存、磁盘、网络
Row 5: 依赖层(探活/外部)     —— probe_success、证书过期

每一行内部再按「左状态 / 中趋势 / 右下钻」来布局:

  • 左侧:当前时刻的 Stat 或 Gauge(一眼知道「现在好不好」)
  • 中间:Time Series 趋势曲线(看「什么时候开始变差的」)
  • 右侧(有需要时):最近告警列表或表格

布局的好处:从上往下看就是一次排查路径。入口层发现延迟高 → 到应用层看哪个 svc 慢了 → 到运行时看 GC 是否频繁 → 到主机层看哪台 ip 的资源有没有打满。

2、一个可复制的 Row 模板

以下是第7篇的分层指标清单表对应的 Dashboard Row 骨架,所有 PromQL 中的 $job / $svc / $instance 均由 Dashboard 面板变量传入:

Row 1 - 入口层(黄金信号)

面板指标 / PromQL位置
存活状态up{job=~"$job"}左上
黄金延迟histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le))中上
黄金流量sum(rate(http_server_requests_seconds_count[5m]))中
黄金错误sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) / sum(rate(http_server_requests_seconds_count[5m])) * 100中下
告警列表—右下

Row 2 - 应用层(RED)

面板指标 / PromQL位置
总 QPSsum(rate(http_server_requests_seconds_count{job=~"$job", svc=~"$svc"}[5m])) by (instance)左
5xx 错误率sum(rate(http_server_requests_seconds_count{job=~"$job", svc=~"$svc", status=~"5.."}[5m])) by (instance) / sum(rate(http_server_requests_seconds_count{job=~"$job", svc=~"$svc"}[5m])) by (instance) * 100中
平均响应时间sum(rate(http_server_requests_seconds_sum{job=~"$job", svc=~"$svc"}[5m])) by (instance) / sum(rate(http_server_requests_seconds_count{job=~"$job", svc=~"$svc"}[5m])) by (instance)右

指标来源:第4篇 Spring Boot 应用监控 / 第5篇 Nginx 监控。如果监控对象是 Nginx(OSS 版),RED 只能做到半个,只有 QPS 完整,状态码和延迟需要看应用层。

Row 3 - 主机层(USE)

# CPU 使用率
100 - avg(rate(node_cpu_seconds_total{job=~"$job", svc=~"$svc", mode="idle"}[5m])) by (instance) * 100

# 内存使用率
(node_memory_MemTotal_bytes{job=~"$job", svc=~"$svc"} - node_memory_MemAvailable_bytes{job=~"$job", svc=~"$svc"}) / node_memory_MemTotal_bytes{job=~"$job", svc=~"$svc"} * 100

# 磁盘空间使用率
100 - (node_filesystem_avail_bytes{job=~"$job", svc=~"$svc", fstype!~"tmpfs|overlay|devtmpfs"} / node_filesystem_size_bytes{job=~"$job", svc=~"$svc", fstype!~"tmpfs|overlay|devtmpfs"}) * 100

# 磁盘 I/O 延迟(读写平均)
rate(node_disk_io_time_seconds_total{job=~"$job", svc=~"$svc"}[5m]) / rate(node_disk_io_time_weighted_seconds_total{job=~"$job", svc=~"$svc"}[5m])

主机层面板里的 $type 已经被 Dashboard 链接固定为 node(其实不固定也可以,因为指标名与其他层的不一样),但 $svc 需要保留联动。

3、$env 变量的处理

$env 只推荐在以下场景使用:

  • 同一套监控系统覆盖了生产与应急(emergency)环境
  • 不同环境的指标需要严格隔离展示

如果只有一套环境,建议直接省略 $env 变量,减少 Dashboard 加载时的额外查询。

当有 $env 时,建议在 Prometheus 的 file_sd 或 relabel 配置中增加 env 标签,然后在 Grafana 变量定义中用 label_values(up, env) 拉取值。

# 定义 $env 的查询语句(当 env 标签存在时)
label_values(up, env)

# 面板 PromQL 中使用(带环境过滤)
sum(rate(http_server_requests_seconds_count{job=~"$job", svc=~"$svc", env=~"$env"}[5m])) by (instance)

4、时间轴同步

Grafana 默认所有面板共享右上角的全局时间选择器——不要对单个面板设独立时间范围,除非是在做对比。

唯一可以单独设置的是 $__rate_interval,它由 Grafana 根据抓取间隔自动计算,在 Dashboard 的 PromQL 查询里代替硬编码的 [5m]:

# 用 $__rate_interval 代替固定 [5m]
sum(rate(http_server_requests_seconds_count[$__rate_interval]))

好处是:如果后续在 Prometheus 端调整了 scrape_interval,Dashboard 不需要手动改窗口。

5、导入导出与版本管理

一个生产级 Dashboard 不是一次配好的,会随着监控目标变更、指标增减不断调整。建议:

  • Dashboard 的 JSON 模型纳入 Git 管理
  • 每次修改后用 Grafana 的「Save → Copy JSON」导出替换
// 在 Dashboard 的 description 字段里标注
{
  "description": "统一监控 Dashboard v2\n"
    "依赖标签规范:第2篇 Prometheus 服务发现(job/svc/type 标签体系)\n"
    "指标来源:第3篇 node_exporter / 第4篇 Spring Boot / 第5篇 Nginx\n"
    "变量约定:$job=系统/应用、$svc=服务、$type=资源类型、$ip=服务器IP、$instance=具体实例\n"
    "下钻链路:本面板 → 资源 USE 面板 → 磁盘专项面板"
}

四、常见问题

1、图太多,Dashboard 加载慢

  • 优先用 Stat 面板代替 Time Series(一张 Stat 相当于一条 Gauge,加载开销一个数量级)
  • 变量不超过三层,减少 label_values() 的查询次数
  • 同一 Row 内不要放超过 4 张 Time Series 图,水平滚动不解决问题
  • 考虑用 Dashboard 的「Refresh」间隔避开 1s 刷新,生产环境 30s~1m 完全够用

2、变量下拉框里的值太多(几十个 ip 或 instance 时)

  • 给 $ip 和 $instance 加搜索提示:变量配置里启用「Include All option」+ 搜索即可
  • 或者用 Ad hoc filter 代替,但风险是值班的人不一定知道怎么用

3、下钻跳转后目标面板空白

  • 确认目标面板的变量名和源链接中的 var- 参数完全一致
  • 确认目标面板里的 PromQL 使用了 $svc / $type / $instance 等变量
  • 使用 Debug 模式:在目标面板 URL 后面加 &debug=1,查看传入的变量值是否符合预期

4、$env 只有一套环境时意义不大

如果生产环境只有一套,$env 变量不产生任何实际过滤效果,反而增加了 Dashboard 加载时的查询开销。建议在这种情况下直接移除 $env 变量,用固定配置替代。


五、小结

监控体系的核心组件包括 Prometheus Server、Exporters、Alertmanager 和 Grafana,前面把前三块全拆了一遍,本篇补上可视化这一环。

变量让面板不再一把死尺,下钻从 Overview 到根因只有三次点击,统一 Dashboard 让值班的人从上往下扫一遍就知道该看哪一层。

三件事做完之后,这套监控体系才真正从「指标能采到」走到了「人拿到信息就能做决策」。

​