起因
监控装好了,Prometheus + Loki + Grafana 都在跑,仪表盘有 30 多个。
出问题时点开仪表盘要翻几屏才看到关键指标。on-call 的同事中午吃饭
被叫起来排查,越急越慌越找不到。
"作战仪表盘"就是把"凌晨被叫醒后要看的 8 个图"压缩到一屏内。
解决方案
设计原则
- 一屏装下,不滚屏:1920×1080 上 8-12 个 panel 是甜点
- 金字塔布局:最上面是"系统健康分",下方是各组件细节
- 时间窗口默认 1h:足够看到趋势又不太久
- 每个 panel 自带告警阈值线:红色 horizontal line 标 SLO
- stats vs graph:当前值用 stat panel(巨大数字),趋势用 timeseries
顶部:4 个 stat panel(系统状态)
# Panel: 在线节点数 / 总节点数
sum(up{job="node"}) / count(up{job="node"})
# Panel: 当前 RPS(应用总)
sum(rate(http_requests_total[1m]))
# Panel: 当前 P95 延迟
histogram_quantile(0.95,
sum by (le) (rate(http_request_duration_seconds_bucket[5m])))
# Panel: 当前 5xx 错误率
sum(rate(http_requests_total{status=~"5.."}[1m]))
/ sum(rate(http_requests_total[1m]))
每个 stat panel 配 thresholds:
- 绿色:正常
- 黄色:警戒
- 红色:异常
例如 P95 延迟 < 100ms 绿、100-500ms 黄、> 500ms 红。
中部:时间序列趋势(2x2)
- RPS by endpoint:
sum by (path) (rate(http_requests_total[1m])) - 延迟 P50/P95/P99:
histogram_quantile(0.50, sum by (le) (rate(http_request_duration_seconds_bucket[5m]))) histogram_quantile(0.95, ...) histogram_quantile(0.99, ...) - 错误率 by status code:
sum by (status) (rate(http_requests_total[1m])) - 数据库 query 时间 P95:
histogram_quantile(0.95, sum by (le) (rate(db_query_duration_seconds_bucket[5m])))
下部:基础设施(2x2)
- 每节点 CPU:
100 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 - 每节点 RAM:
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) - 磁盘使用率:
100 - node_filesystem_avail_bytes{mountpoint="/"} * 100 / node_filesystem_size_bytes{mountpoint="/"} - 网络入/出:
sum by (instance) (rate(node_network_receive_bytes_total[1m])) sum by (instance) (rate(node_network_transmit_bytes_total[1m]))
底部:log 错误日志(Loki)
Logs panel,query:
{job="myapp"} |~ "ERROR|FATAL|panic" | json
直接看到最近的错误日志,配合上面的指标曲线看时间关联。
配置 tip
单位
每个 panel 设正确的 unit(seconds / percent / bytes/sec),
Grafana 自动用 K/M/G 缩写。{r}/s 表示每秒请求数。
颜色一致
所有"错误"用红、"延迟"用紫、"流量"用蓝、"资源"用橙。
形成视觉默契,眼睛快速分辨。
阈值线
Thresholds:
- color: green, value: 0
- color: yellow, value: 100
- color: red, value: 500
panel 上自动画水平线。
Variables(下拉切环境 / 服务)
仪表盘顶部:
Variable: env
Type: query
Query: label_values(up, env)
Variable: service
Type: query
Query: label_values(up{env="$env"}, job)
用户切 env=prod / service=api → 所有 panel 自动 filter。
自动化:dashboard as code
不要在 UI 里手动建。用 grafonnet
或者 grizzly 把 dashboard 写成
Jsonnet / YAML 进 git:
local g = import 'g.libsonnet';
g.dashboard.new('My App SLI Overview')
+ g.dashboard.withRefresh('30s')
+ g.dashboard.withPanels([
g.panel.stat.new('Active nodes')
+ g.panel.stat.queryOptions.withTargets([
g.query.prometheus.new('default', 'sum(up)')
]),
// ... 更多 panel
])
grr apply 一键部署到 Grafana。改动走 PR review。
Provisioning
放 dashboard JSON 到 /etc/grafana/provisioning/dashboards/,
Grafana 启动自动加载。这样部署 / 还原灾备时 dashboard 不需要手动重建。
效果
- 凌晨 on-call 收到告警 → 打开仪表盘 → 5 秒看清"哪个指标红了"
- mean time to diagnose 从 ~15 分钟降到 ~3 分钟
- 团队新人 onboarding 时一份仪表盘 = 一份系统全貌速成课
- "感觉慢" 类玄学反馈被替换为"看这个 panel 上 P95 涨了 3 倍"
一些进阶 panel
Service Level Objective (SLO) burn rate
1 - sum(rate(http_requests_total{status!~"5.."}[1h]))
/ sum(rate(http_requests_total[1h]))
定义 SLO(如"99.5% 成功率"),算每小时实际 vs 目标的 burn rate。
1 = 这小时消耗了超过本月预算的 1/30。
Apdex
(sum(rate(http_request_duration_seconds_bucket{le="0.1"}[5m]))
+ sum(rate(http_request_duration_seconds_bucket{le="0.4"}[5m])) / 2)
/ sum(rate(http_request_duration_seconds_count[5m]))
100ms 满意 + 400ms 容忍 → 0-1 分数。一个数字概括"用户满意度"。
USE method (Utilization / Saturation / Errors)
每资源(CPU / RAM / disk / network)三栏:当前用量 / 队列长度 / 错误数。
Brendan Gregg 的经典系统排查框架,dashboard 上也用得上。
踩过的坑
-
panel 用 sum 不加 by:所有节点 RPS 加一起看不出哪个节点出问题。
关键指标都按by (instance)或by (job)分组。 -
时间窗口太长:default 6h 仪表盘载入慢 + 看不清。设 1h 默认,
按需调长。 -
stat panel 数字闪烁:默认 refresh 5s 时每次刷新都重算 → 视觉
抖。配min_step: 30s平滑。 -
告警从 dashboard 配(Grafana alerting)vs Alertmanager rule:
两套别混。生产建议规则进 Prometheus rule files(与 Grafana 解耦),
Grafana 只展示。 -
dashboard 太多:超过 30 个仪表盘后没人知道用哪个。归类 + 命名
规范 + folder 组织,每月 review 删用不到的。
登录后参与评论。