Grafana 是业界最广泛使用的开源监控可视化平台,从基础设施指标到业务数据仪表盘,几乎无处不在。本文基于 Hugo 在实际项目中的 Grafana 使用经验,系统梳理面板定制的核心概念、查询语法、变量机制、告警配置以及常见踩坑记录,帮助你从"能画图"进阶到"能搭体系"。
Grafana 本身不存储数据,它是一个可视化层,负责从多种数据源拉取数据并渲染为图表。典型架构如下:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 数据源 │────▶│ Grafana │────▶│ 面板/仪表盘 │
│ (Prometheus │ │ (查询+渲染) │ │ (可视化) │
│ InfluxDB │ │ │ │ │
│ MySQL 等) │ │ │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
关键认知: Grafana 的查询能力严重依赖底层数据源。同样的"CPU 使用率"指标,Prometheus 用 PromQL,InfluxDB 用 InfluxQL,MySQL 用 SQL,语法完全不同。面板迁移时,查询语句必须重写。
| 数据源 | 适用场景 | 查询语言 | Hugo 项目使用经验 |
|---|---|---|---|
| Prometheus | 云原生指标、Kubernetes、服务监控 | PromQL | 主力数据源,配合 Alertmanager 做告警 |
| InfluxDB | 时序数据、IoT、高频写入场景 | InfluxQL/Flux | 早期项目用过,后迁移到 Prometheus |
| MySQL/PostgreSQL | 业务数据、关系型数据展示 | SQL | 用于业务报表面板,注意查询性能 |
| Elasticsearch | 日志分析、APM 数据 | Lucene/DSL | 配合 Filebeat 做日志检索面板 |
| Loki | 日志聚合(Grafana 官方方案) | LogQL | 比 ES 轻量,适合 Kubernetes 日志 |
| Jaeger/Tempo | 分布式链路追踪 | 专用查询 | 微服务链路分析 |
| CloudWatch | AWS 基础设施监控 | CloudWatch 查询 | 多云场景下统一展示 |
踩坑记录: 早期项目同时接入了 InfluxDB 和 Prometheus,两个数据源的"磁盘使用率"指标命名不一致(disk_used_percent vs node_filesystem_avail_bytes),导致跨面板对比时经常搞混。后来统一规范:所有基础设施指标走 Prometheus,业务指标走 MySQL,日志走 Loki。
Grafana 提供 20+ 种可视化类型,选对类型比调样式更重要:
| 面板类型 | 最佳场景 | 避坑提示 |
|---|---|---|
| Time series | 时序趋势图(默认首选) | 多指标叠加时注意 Y 轴单位统一 |
| Stat | 单值展示(当前值、总计) | 可配置阈值变色,适合做 SLO 面板 |
| Gauge | 仪表盘/进度环形图 | 范围必须手动设置,否则默认 0-100 可能不适用 |
| Table | 列表数据、Top N 排名 | 大数据集时开启分页,避免前端卡顿 |
| Bar chart | 分类对比(如各服务 QPS) | 时序数据不要用柱状图,趋势会误导 |
| Pie chart | 占比分布 | 类别超过 7 个时可读性极差,慎用 |
| Heatmap | 分布密度(如请求延迟分布) | 桶(bucket)划分直接影响可视化效果 |
| Logs | 日志流展示 | 必须配合 Loki 或 ES 数据源 |
| Node graph | 拓扑关系(服务依赖) | 需要数据源支持图结构返回 |
| Alert list | 当前告警展示 | 仅展示,不配置告警规则 |
Hugo 的偏好: 监控仪表盘 80% 用 Time series + Stat 组合,业务报表用 Table + Bar chart,故障排查用 Logs + Node graph。Pie chart 几乎不用——信息密度太低。
面板编辑器分为五个标签页,新手常卡在"不知道在哪设置":
┌─────────────────────────────────────────────┐
│ Query │ Transform │ Alert │ Panel │ Code │
└─────────────────────────────────────────────┘
技巧: 当你需要复制一个复杂面板到另一个仪表盘时,进入 Code 标签页,复制 JSON,在目标面板粘贴——比重新配置快 10 倍。
Grafana 自动注入的时间变量是查询时序数据的基石。不同数据源的时间格式要求不同:
# 查询时间范围内的 HTTP 请求总量
sum(increase(http_requests_total[$__range]))
# 用 $__rate_interval 自动处理采样间隔
rate(cpu_usage_seconds_total[$__rate_interval])
关键区别:
$__from / $__to:毫秒级 Unix 时间戳(如 1704067200000)$__timeFrom / $__timeTo:ISO 8601 格式(如 2024-01-01T00:00:00.000Z)$__range:当前选择的时间范围时长(如 1h、7d)$__rate_interval:推荐使用,自动计算 max($__interval + 采样间隔, 4 * 采样间隔),避免 rate 计算出现空窗-- InfluxQL 示例
SELECT mean("value") FROM "cpu_usage"
WHERE time >= $__timeFrom AND time <= $__timeTo
-- 注意:InfluxDB 原生支持时间范围,不需要转换
-- 错误:直接比较会导致全表扫描
SELECT * FROM metrics WHERE ts > $__from; -- ❌
-- 正确:将毫秒转为秒,或确保字段类型匹配
SELECT * FROM metrics
WHERE ts > FROM_UNIXTIME($__from / 1000); -- ✅
-- Timestream 需要显式用 from_milliseconds 转换
WHERE time BETWEEN from_milliseconds($__timeFrom) AND from_milliseconds($__timeTo)
这是 Hugo 在某 AWS 项目中的实际踩坑记录——当时直接把 $__timeFrom 丢进 Timestream 查询,结果始终查不到数据,调试了半小时才发现 Timestream 的 time 字段是 TIMESTAMP 类型,而 $__timeFrom 返回的是毫秒整数,必须包一层 from_milliseconds() 转换。
| 变量 | 含义 | 示例值 |
|---|---|---|
$__dashboard |
当前仪表盘名称 | API Gateway Monitoring |
$__from |
开始时间(毫秒) | 1704067200000 |
$__to |
结束时间(毫秒) | 1704153600000 |
$__timeFrom |
开始时间(ISO) | 2024-01-01T00:00:00.000Z |
$__timeTo |
结束时间(ISO) | 2024-01-02T00:00:00.000Z |
$__interval |
根据面板宽度自动计算的采样间隔 | 30s、5m |
$__range |
当前时间范围 | 1h、24h |
$__org |
当前组织 ID | 1 |
$__user |
当前用户名 | admin |
在仪表盘设置中定义变量,实现"一个模板,多套环境复用":
Dashboard Settings → Variables → Add variable
典型变量配置示例:
| 变量名 | 类型 | 查询/配置 | 用途 |
|---|---|---|---|
$namespace |
Query | label_values(kube_pod_info, namespace) |
选择 Kubernetes 命名空间 |
$pod |
Query | label_values(kube_pod_info{namespace=~"$namespace"}, pod) |
级联选择 Pod(依赖 $namespace) |
$env |
Custom | prod,staging,dev |
环境切换 |
$interval |
Interval | 1m,5m,10m,30m,1h |
采样粒度切换 |
级联变量技巧: 变量查询中引用其他变量时,使用 =~"$var" 语法(正则匹配),这样多选时也能正常工作:
# $pod 变量定义中的查询
label_values(container_cpu_usage_seconds_total{namespace=~"$namespace"}, pod)
踩坑记录: 某次把 $namespace 变量类型设成了 Text box 而不是 Query,结果用户输入 production 但指标里的标签是 prod,查不到数据。后来统一规范:所有环境标识类变量用 Query 类型从数据源动态拉取,禁止手动输入。
Prometheus + Grafana 是云原生监控的黄金组合,以下是 Hugo 总结的 PromQL 实用模式:
-- 1. 瞬时增长率(必须配合 rate/increase)
rate(http_requests_total[5m])
-- 2. 分位数延迟(histogram 指标)
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
)
-- 3. 聚合 + 过滤
sum by (service) (
rate(errors_total[5m])
) / sum by (service) (
rate(requests_total[5m])
)
-- 4. 同比/环比(offset)
rate(http_requests_total[5m])
- rate(http_requests_total[5m] offset 1w)
-- 5. 缺失数据填充(or vector(0))
up{job="api"} or vector(0)
count by (user_id) 如果 user_id 有 10 万个,面板会卡死。聚合维度必须控制 cardinality。1h 间隔,查实时用 5s 间隔,不要让 Grafana 在前端做降采样。histogram_quantile)在 Prometheus 里预计算,Grafana 只查结果指标。{{label_name}} 语法让图例可读,例如 {{pod}} - {{container}}。Transform 是对查询结果的二次加工,不依赖数据源类型,纯前端处理。
| Transform | 功能 | 典型场景 |
|---|---|---|
| Filter by name | 按列名过滤 | 查询返回 20 列,只展示关键 5 列 |
| Add field from calculation | 计算衍生字段 | 原始有 requests 和 errors,计算 error_rate = errors / requests |
| Merge | 合并多个查询结果 | 把"CPU 使用率"和"内存使用率"两个查询合并到一张表 |
| Organize fields | 重命名/隐藏/排序列 | 把 value 重命名为 CPU %,把 timestamp 隐藏 |
| Group by | 按维度聚合 | 把分钟级数据聚合成小时级 |
| Series to rows | 多序列转表格 | Time series 面板想展示成表格 |
场景: 做一个"服务健康度"面板,原始查询返回每个服务的 availability(可用性)和 latency_p99(P99 延迟),需要计算一个综合健康分。
Query A: availability 指标
Query B: latency_p99 指标
Transform 1: Merge(合并 A 和 B)
Transform 2: Add field from calculation
- 新字段名:health_score
- 计算:$availability * 0.6 + (1 - $latency_p99 / 1000) * 0.4
Transform 3: Organize fields
- 保留:service, availability, latency_p99, health_score
- 隐藏:所有原始 value 字段
注意: Transform 是纯前端操作,数据量大时(如万行以上)浏览器会卡顿。复杂计算建议在数据源端完成。
Grafana 的告警经历了两代变革:
Hugo 的建议: 如果你还在用 Grafana 8 以下的 Legacy Alerting,升级 Grafana 并迁移到 Unified Alerting。独立告警规则更灵活,且支持 Alertmanager 生态。
# 告警规则结构(Grafana 10+)
rule:
title: "API 高错误率"
condition: "B" # 引用哪个查询/表达式
data:
- refId: A # 查询:原始指标
query: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
- refId: B # 表达式:阈值判断
expression: "$A > 0.05" # 错误率 > 5%
eval:
interval: 1m # 评估频率
for: 5m # 持续 5 分钟才触发(防抖动)
labels:
severity: critical
team: platform
annotations:
summary: "API 错误率超过 5%"
description: "当前错误率: {{ $values.B }}"
Grafana 10 的 Notification Policy 可以按标签路由告警到不同渠道:
根路由
├── severity=critical → PagerDuty + 短信 + 电话
├── severity=warning → 企业微信 + 邮件
├── team=data → 数据团队 Slack
└── team=platform → 平台团队飞书
踩坑记录: 早期配置时把 for: 1m 设得太短,服务一抖动就狂发告警,导致告警疲劳。后来统一规范:
for: 5m(确认真故障)for: 15m(容忍短暂波动)for: 1h(趋势性通知)Hugo 在项目中按受众和用途把仪表盘分为四层:
| 层级 | 受众 | 刷新频率 | 典型内容 | 面板数量 |
|---|---|---|---|---|
| L0:全局概览 | CTO/总监 | 5min | 核心业务指标、SLA、成本 | 6-10 |
| L1:服务大盘 | 团队负责人 | 30s | 各服务 QPS/延迟/错误率/饱和度 | 15-25 |
| L2:服务详情 | 开发/运维 | 10s | 单服务 Pod 级指标、JVM、DB 连接池 | 20-40 |
| L3:故障排查 | 值班工程师 | 实时 | 日志、链路、事件、告警关联 | 灵活组合 |
Grafana 仪表盘本质是 JSON,理解结构有助于批量管理和版本控制:
{
"dashboard": {
"title": "API Gateway Monitoring",
"tags": ["platform", "api"],
"timezone": "browser",
"refresh": "30s",
"panels": [
{
"id": 1,
"title": "QPS",
"type": "timeseries",
"targets": [{ "expr": "sum(rate(http_requests_total[5m]))" }],
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 }
}
],
"templating": {
"list": [
{ "name": "namespace", "type": "query", "query": "..." }
]
}
}
}
版本控制实践: 把仪表盘 JSON 导出后存入 Git,配合 Grafana 的 Provisioning 功能实现"代码即配置"。
# /etc/grafana/provisioning/dashboards/platform.yaml
apiVersion: 1
providers:
- name: 'platform-dashboards'
folder: 'Platform'
type: file
options:
path: /var/lib/grafana/dashboards/platform
当变量改变时,默认所有面板都刷新。如果某个面板查询很慢(如大数据量 Table),可以关闭自动刷新:
Panel → Query options → Refresh on time range change: Off
Grafana 10.3+ 支持 Mixed 数据源,一个面板可以同时查 Prometheus + MySQL:
Query A (Prometheus): 基础设施指标
Query B (MySQL): 业务订单数据
Transform: Merge → 在一张图里展示"CPU 使用率"和"订单量"的关联
注意: 时间戳必须对齐,否则合并后会出现错位。
Annotations 是 Grafana 被低估的功能——在时间轴上标记事件(如发布、故障、配置变更),看图时一眼知道"那个尖峰是因为发版"。
Dashboard Settings → Annotations → Add annotation query
Query: tag='deployment' AND env='$env'
数据源: Elasticsearch / Loki / 手动 API 添加
Hugo 的内部约定: 每次发版后自动调用 Grafana API 写入 Annotation:
curl -X POST "${GRAFANA_URL}/api/annotations" \
-H "Authorization: Bearer ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"dashboardId": 123,
"panelId": 0,
"time": '$(date +%s%3N)',
"tags": ["deployment", "v1.2.3"],
"text": "Release v1.2.3 - API Gateway"
}'
Grafana 的权限模型:
踩坑记录: 曾经把生产数据源权限设为 Editor 可访问,结果有同事在 Explore 里写了 count({__name__=~".+"}) 全量拉取,Prometheus OOM。后来收紧数据源权限,Explore 功能仅对 Admin 开放,普通用户只能看预置面板。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 面板加载慢 | 查询返回数据量太大 | 增加聚合粒度($__interval),或加 limit |
| 浏览器卡顿 | 时间范围太大 + 高刷新率 | 大数据范围时自动降低刷新率(5min) |
| Prometheus 压力大 | 复杂查询实时计算 | 用 Recording Rule 预计算 |
| 图例重叠 | 序列太多 | 开启 Legend 分页,或改用 Table 面板 |
| 时区混乱 | 数据源和 Grafana 时区不一致 | 统一设 UTC 或 browser,不要混用 |
| 快捷键 | 功能 |
|---|---|
e |
编辑当前面板 |
v |
进入面板全屏视图 |
Esc |
退出编辑/全屏 |
Ctrl + S |
保存仪表盘 |
d + r |
刷新当前仪表盘 |
t + z |
缩小时间范围(Zoom in) |
t + shift + z |
放大时间范围(Zoom out) |
? |
显示所有快捷键 |
scripts/grafana-annotate.sh