Grafana Dashboard 设计最佳实践:变量、告警、Dashboard as Code
系统讲解 Grafana Dashboard 的专业设计方法:模板变量(Template Variables)实现动态下钻、Panel 类型选择与 PromQL 实战(时序图/Stat/Gauge/Heatmap)、Dashboard 告警配置、使用 Grafonnet 和 Terraform 实现 Dashboard as Code、Grafana 数据源代理与权限管理的生产配置。
Grafana 是可观测性平台的可视化核心,但很多团队的 Dashboard 充斥着意义不明的图表、无法下钻的静态视图、以及无法复用的一次性配置。本文讲解如何设计真正有用的生产级 Dashboard。
Dashboard 设计原则
黄金法则:
1. 每个 Panel 回答一个问题("这个服务现在健康吗?")
2. 从全局到局部(服务概览 → 节点详情 → 单个指标)
3. 告警和 Dashboard 指标一致(看到问题能直接关联告警)
4. 变量驱动(一个 Dashboard 服务多个集群/命名空间)
布局建议(从上到下):
第一行:关键 SLI 数字(Stat Panel:可用率/延迟/错误率)
第二行:过去 24 小时趋势(Time Series Panel)
第三行:详细分解(按服务/节点/错误类型分类)
第四行:慢速指标(JVM 内存/GC/连接池)
一、模板变量(Template Variables)
变量让一个 Dashboard 可以动态切换集群、命名空间、服务实例,而不是为每个环境建一个 Dashboard。
// Dashboard JSON 中的变量定义(在 Grafana UI 中通过 Settings → Variables 配置)
// 变量类型1:Query(从 Prometheus 查询选项值)
{
"name": "cluster",
"type": "query",
"datasource": "Prometheus",
"query": "label_values(up, cluster)", // 查询所有 cluster 标签的值
"refresh": 2, // 每次 Dashboard 刷新时更新
"multi": true, // 允许多选
"includeAll": true // 包含"All"选项
}
// 变量类型2:Custom(固定选项列表)
{
"name": "environment",
"type": "custom",
"options": [
{"text": "production", "value": "production"},
{"text": "staging", "value": "staging"}
]
}
// 变量类型3:Interval(时间间隔)
{
"name": "interval",
"type": "interval",
"options": ["1m", "5m", "15m", "30m", "1h"]
}
// 变量类型4:Datasource(动态选择数据源)
{
"name": "datasource",
"type": "datasource",
"pluginId": "prometheus"
}
# 在 Panel 中使用变量(用 $变量名 引用)
# 多选变量使用 =~ 和 .* 语法
# 过滤特定集群和命名空间
sum(
rate(http_requests_total{
cluster=~"$cluster", # 匹配选择的集群(多选)
namespace=~"$namespace",
job=~"$job"
}[$interval])
) by (service)
# 变量级联(命名空间依赖集群)
# namespace 变量的查询:
label_values(up{cluster=~"$cluster"}, namespace)
# 当切换 cluster 时,namespace 选项自动更新
二、Panel 类型选择指南
Stat Panel(关键数字展示)
// 适用:单个关键指标、SLO 状态
// PromQL 示例:服务可用率
{
"targets": [{
"expr": "sum(rate(http_requests_total{status!~'5..', cluster=~'$cluster'}[5m])) / sum(rate(http_requests_total{cluster=~'$cluster'}[5m])) * 100",
"legendFormat": "Success Rate"
}],
"type": "stat",
"options": {
"reduceOptions": {"calcs": ["lastNotNull"]},
"thresholds": {
"steps": [
{"color": "red", "value": null},
{"color": "yellow", "value": 99},
{"color": "green", "value": 99.9}
]
},
"unit": "percent"
}
}
Time Series Panel(趋势图)
# HTTP 请求速率(按状态码分类)
sum by (status_code) (
rate(http_requests_total{
cluster=~"$cluster",
namespace=~"$namespace"
}[$interval])
)
# P50/P90/P99 延迟
histogram_quantile(0.99,
sum by (le) (
rate(http_request_duration_seconds_bucket{
cluster=~"$cluster"
}[$interval])
)
)
Heatmap Panel(延迟分布热力图)
# 延迟分布热力图(用 histogram bucket)
sum(
rate(http_request_duration_seconds_bucket{
cluster=~"$cluster",
job=~"$job"
}[$interval])
) by (le)
# Heatmap 能清晰展示延迟的分布变化,比 P99 更直观
# 横轴:时间,纵轴:延迟 bucket,颜色深浅:请求数量
Table Panel(明细列表)
# Top 10 慢接口
topk(10,
sum by (handler) (
rate(http_request_duration_seconds_sum{cluster=~"$cluster"}[$interval])
) /
sum by (handler) (
rate(http_request_duration_seconds_count{cluster=~"$cluster"}[$interval])
)
)
三、生产级 PromQL 实战
服务概览面板
# 1. 错误率(5xx 响应比例)
sum(rate(http_requests_total{status=~"5.."}[$interval])) /
sum(rate(http_requests_total[$interval])) * 100
# 2. 请求延迟(百分位)
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket[$interval]))
)
# 3. 吞吐量(每秒请求数)
sum(rate(http_requests_total[$interval]))
# 4. 活跃连接数
sum(nginx_connections_active)
Kubernetes 节点面板
# CPU 使用率(含 steal 时间提示,适合云环境)
1 - avg by (node) (
rate(node_cpu_seconds_total{mode="idle", cluster=~"$cluster"}[$interval])
)
# 内存使用率(排除 cache 的真实内存压力)
1 - (
node_memory_MemAvailable_bytes{cluster=~"$cluster"}
/
node_memory_MemTotal_bytes{cluster=~"$cluster"}
)
# 磁盘 IO 利用率
rate(node_disk_io_time_seconds_total{cluster=~"$cluster"}[$interval])
# 网络收发速率
rate(node_network_receive_bytes_total{device!="lo", cluster=~"$cluster"}[$interval])
数据库监控面板(MySQL)
# QPS(每秒查询数)
rate(mysql_global_status_queries{cluster=~"$cluster"}[$interval])
# 慢查询速率
rate(mysql_global_status_slow_queries{cluster=~"$cluster"}[$interval])
# 连接数使用率
mysql_global_status_threads_connected{cluster=~"$cluster"}
/
mysql_global_variables_max_connections{cluster=~"$cluster"}
# InnoDB 缓冲池命中率
1 - (
rate(mysql_global_status_innodb_buffer_pool_reads[$interval])
/
rate(mysql_global_status_innodb_buffer_pool_read_requests[$interval])
)
四、Dashboard 告警配置
Grafana 支持在 Panel 内直接配置告警规则(Grafana Managed Alerts)。
# Panel 中的告警配置(通过 UI 配置,等效 YAML)
alertRule:
name: "高错误率告警"
condition: "B" # 用计算结果字段 B
data:
- refId: "A"
relativeTimeRange:
from: 600 # 过去 10 分钟
to: 0
datasourceUid: "prometheus-uid"
model:
expr: "sum(rate(http_requests_total{status=~'5..'}[5m])) / sum(rate(http_requests_total[5m])) * 100"
- refId: "B"
datasourceUid: "-100" # 内置计算数据源
model:
type: reduce
conditions:
- evaluator:
type: gt
params: [5] # 错误率 > 5% 触发告警
- type: "query"
query:
params: ["A", "5m", "now"]
noDataState: "NoData"
execErrState: "Error"
for: "5m"
labels:
severity: "critical"
team: "backend"
annotations:
summary: "HTTP 错误率过高"
description: "错误率 {{ $values.B }}%,超过 5% 阈值"
runbook_url: "https://wiki.company.com/runbooks/http-errors"
五、Dashboard as Code
使用 Grafonnet(Jsonnet 方案)
# 安装 Grafonnet
go install github.com/grafana/grizzly/cmd/grr@latest
# 安装 jsonnet 和 jb
go install github.com/google/go-jsonnet/cmd/jsonnet@latest
go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest
# 创建项目
mkdir my-dashboards && cd my-dashboards
jb init
jb install github.com/grafana/grafonnet/gen/grafonnet-latest@main
// dashboards/service-overview.jsonnet
local grafonnet = import 'github.com/grafana/grafonnet/gen/grafonnet-latest/main.libsonnet';
local dashboard = grafonnet.dashboard;
local panel = grafonnet.panel;
local query = grafonnet.query;
dashboard.new('Service Overview')
+ dashboard.withUid('service-overview')
+ dashboard.withRefresh('1m')
+ dashboard.withPanels([
// 错误率 Stat Panel
panel.stat.new('Error Rate')
+ panel.stat.gridPos.withH(4)
+ panel.stat.gridPos.withW(6)
+ panel.stat.gridPos.withX(0)
+ panel.stat.gridPos.withY(0)
+ panel.stat.queryOptions.withTargets([
query.prometheus.new(
'$datasource',
'sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) * 100'
)
+ query.prometheus.withLegendFormat('Error Rate')
])
+ panel.stat.standardOptions.withUnit('percent')
+ panel.stat.options.withColorMode('background'),
])
# 推送 Dashboard 到 Grafana
grr apply service-overview.jsonnet
使用 Terraform
# main.tf
terraform {
required_providers {
grafana = {
source = "grafana/grafana"
version = "~> 2.0"
}
}
}
provider "grafana" {
url = "http://grafana:3000"
auth = "admin:${var.grafana_password}"
}
resource "grafana_dashboard" "service_overview" {
config_json = file("${path.module}/dashboards/service-overview.json")
overwrite = true
folder = grafana_folder.monitoring.id
}
resource "grafana_folder" "monitoring" {
title = "Monitoring"
}
# 数据源
resource "grafana_data_source" "prometheus" {
type = "prometheus"
name = "Prometheus"
url = "http://prometheus:9090"
}
# 部署
terraform init
terraform plan
terraform apply
六、权限管理与数据源代理
# grafana.ini 关键配置
[security]
allow_embedding = true # 允许嵌入 iframe(大屏展示)
cookie_secure = true
content_security_policy = true
[auth]
disable_login_form = false
oauth_auto_login = true # 企业 SSO 自动登录
[auth.generic_oauth]
enabled = true
name = 企业 SSO
client_id = grafana-client
client_secret = secret
scopes = openid profile email groups
auth_url = https://sso.company.com/auth
token_url = https://sso.company.com/token
api_url = https://sso.company.com/userinfo
role_attribute_path = contains(groups[*], 'ops-admins') && 'Admin' || 'Viewer'
# 使用 API 批量管理数据源权限
curl -X POST http://grafana:3000/api/datasources \
-H 'Content-Type: application/json' \
-u admin:secret \
-d '{
"name": "Prometheus-Production",
"type": "prometheus",
"url": "http://prometheus:9090",
"access": "proxy",
"isDefault": true
}'
小结
好的 Grafana Dashboard 应该做到:变量驱动(一个 Dashboard 服务多个环境)、层次分明(从 SLI 概览到单实例详情的下钻路径)、告警关联(告警触发时能直接在 Dashboard 上找到对应指标)。Dashboard as Code 是生产必备:使用 Grafonnet/Jsonnet 或 Terraform 管理 Dashboard 配置,纳入 Git 版本控制,通过 CI/CD 部署。避免常见反模式:不要在一个 Panel 里塞 20 条线(看不清)、不要用颜色区分超过 5 个系列(色盲友好)、不要缺少时间窗口变量(固定 1h 看不到趋势)。
