Grafana 运维看板怎么设计?从资源指标到业务可用性
一、问题背景
1.1 一个"什么都有"的看板,为什么没人看
某公司运维团队花了两个月,做了一个"全链路监控大屏"。32 个面板,从 CPU、内存、磁盘、网络,到 JVM 堆、GC 次数、数据库连接数、Redis 命中率,应有尽有。上线第一周大家还新鲜,第二周就没人打开了。
真出故障时,值班同学的第一反应不是打开这个大屏,而是 SSH 上机器敲 top。问原因,回答很直接:
- 屏幕上 32 条曲线,看不出哪条是"现在有问题";
- CPU 从 40% 涨到 60%,不知道这算不算异常;
- 业务方说"下单失败",大屏上没有"下单成功率"这个指标;
- 定位到某个服务后,还要再开三个看板才能看到那个服务的实例级指标。
这个看板的问题不是"指标不全",而是没有设计。它把所有能采集到的指标平铺在屏幕上,把"判断"这件事留给了人。而人在凌晨三点被叫醒时,最缺的就是判断力。
1.2 看板的三种失败模式
失败模式一:指标堆砌,没有主线。一个面板里塞 20 条曲线,图例被截断,颜色重复。读者要花 30 秒才能找到自己关心的那条,而这个时间足够故障扩大。
失败模式二:只有资源,没有业务。CPU、内存、磁盘都正常,但用户在下单页看到 502。资源指标和业务可用性之间没有桥接,运维看到"一切正常",业务看到"全挂了"。
失败模式三:有数据,没结论。面板显示 P99 延迟 850ms,但没有基线、没有阈值、没有颜色。读者不知道 850ms 是好是坏,只能凭经验猜。
这三种失败模式的根因是同一个:看板被当成"数据展示工具",而不是"决策工具"。好的看板应该让读者在 5 秒内回答三个问题:现在有没有问题?问题在哪一层?下一步看什么?
1.3 本文的环境假设
| 项目 | 版本 / 取值 |
|---|---|
| Grafana | 13.2.4(2026-10-13 发布),OSS 版 |
| 数据源 | Prometheus 3.15.1(另有 3.13.5 LTS 环境),Loki 3.x |
| 采集 | node_exporter、kube-state-metrics、cAdvisor、DCGM Exporter、blackbox_exporter |
| 部署 | Kubernetes,Helm 部署,后端存储 PostgreSQL 16 |
| 规模 | 约 120 个节点、900 个 Pod、40 个业务服务、每日 800 万活跃序列 |
| 告警 | Grafana 统一告警(Unified Alerting)+ 部分 Prometheus 规则 |
| 代码管理 | 看板 JSON 存 Git,通过 provisioning 下发;部分团队用 Git Sync |
Grafana 13 有几个必须知道的破坏性变更,直接影响本文的部署和升级章节:
grafana-cli和grafana-server命令被移除,改用grafana cli和grafana server;- 旧版 Image Renderer 插件被彻底移除,截图和报表要用新版渲染服务(JWT 认证);
- API 路径从
/api迁移到/apis,旧的数字 id 数据源 API 默认禁用; - HTTP 压缩
enable_gzip默认开启; - 从开启了 Git Sync 的 12.x 升级到 13.0.0 存在数据迁移 bug,可能丢看板,必须先升到 13.0.1 并备份数据库。
这些变更在升级章节会展开,先记住一点:Grafana 13 不是一个小版本升级。
二、适用场景与边界
2.1 本文覆盖的看板类型
| 类型 | 读者 | 刷新频率 | 回答的问题 |
|---|---|---|---|
| 值班总览(L0) | 一线值班、on-call | 30s-1min | 现在有没有故障?哪块红了? |
| 服务视图(L1) | 服务 owner、SRE | 1min | 这个服务的 RED 指标如何?依赖健康吗? |
| 资源视图(L2) | 系统运维 | 1-5min | 节点、磁盘、网络、GPU 有没有瓶颈? |
| 排查视图(L3) | 故障处理人 | 按需 | 某个实例在某个时间点发生了什么? |
| 业务视图 | 业务方、管理层 | 5-15min | 订单量、成功率、GMV 是否正常? |
这五类看板不是五个独立的 dashboard,而是一套有层级和跳转关系的体系。L0 发现异常,点进去到 L1,再点进 L2/L3,这条路径必须在设计时就打通。
2.2 不在本文范围
- 日志分析看板(Loki 的 Explore 用法),只在"从指标跳到日志"时提一句;
- 链路追踪(Tempo/Jaeger)的面板设计,只在 exemplar 部分涉及;
- 大屏投放(TV 模式、轮播),那是展示需求,不是运维需求;
- Grafana Cloud 的托管服务特性,本文全部基于自建 OSS 版。
2.3 一个前提:指标本身要靠谱
看板是"指标的上层建筑"。如果指标采集有问题,再好的看板也是错的。三个前置条件:
- 采集间隔统一。Prometheus 的
scrape_interval如果是 15s,看板上所有rate()的窗口必须是 15s 的整数倍,否则会出现锯齿。 - 标签规范统一。
instance、job、service、namespace这几个标签的语义要全公司一致,否则没法跨服务聚合。 - 计数器不重置。进程重启导致的 counter 归零,
rate()能处理,但increase()的估算会有偏差,这点在 PromQL 章节细说。
三、核心知识:三层指标体系
3.1 为什么需要分层
监控指标有成百上千个,但人的短期记忆只能同时处理 5-7 个信息。看板设计的第一原则是分层:每一层只回答一个问题,层与层之间有明确的跳转关系。
text 第三层:业务指标(订单量、支付成功率、GMV)
↑ 业务方关心
第二层:服务指标(RED:请求率、错误率、延迟)
↑ 服务 owner 关心
第一层:资源指标(USE:CPU、内存、磁盘、网络)
↑ 系统运维关心
故障排查的方向是从上往下:业务指标异常 → 看服务指标定位是哪个服务 → 看资源指标定位是哪台机器/哪个依赖。
但告警的方向应该从下往上:资源打满会引发服务异常,服务异常会导致业务指标下跌。资源告警要早于业务告警,否则等业务报警时已经影响用户了。
3.2 USE 方法:资源层
USE 是 Brendan Gregg 提出的资源监控方法,三个维度:
- Utilization(使用率):资源有多忙,比如 CPU 使用率 65%;
- Saturation(饱和度):有多少任务在排队,比如 run queue 长度、磁盘 IO 等待队列;
- Errors(错误):发生了多少错误,比如网卡丢包、磁盘坏块。
对每一类资源,三个维度都要有指标:
| 资源 | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | rate(node_cpu_seconds_total{mode!="idle"}) |
node_load1 / node_pressure_cpu_waiting_seconds_total |
node_cpu_seconds_total 无错误维度,看 dmesg |
| 内存 | 1 - MemAvailable/MemTotal |
node_pressure_memory_waiting_seconds_total、swap 使用 |
OOM killer 次数 |
| 磁盘 | rate(node_disk_io_time_seconds_total) |
node_pressure_io_waiting_seconds_total |
node_disk_io_errors 类指标 |
| 网络 | rate(node_network_receive_bytes_total) |
node_netstat_Tcp_RetransSegs、node_netstat_TcpExt_ListenOverflows |
node_network_receive_errs_total |
| 文件描述符 | node_filefd_allocated / node_filefd_maximum |
无 | 应用日志里的 EMFILE |
只做 Utilization 是最常见的错误。CPU 使用率 70% 看起来还好,但如果 run queue 长度是 32,说明任务在严重排队,实际体验已经很差。
3.3 RED 方法:服务层
RED 适合请求驱动的服务(HTTP、gRPC、消息消费):
- Rate:每秒请求数;
- Errors:每秒错误数或错误率;
- Duration:请求耗时分布(P50/P90/P99)。
对应的 PromQL:
promql # Rate
sum by (service) (rate(http_requests_total[5m]))
# Errors
sum by (service) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by (service) (rate(http_requests_total[5m]))
# Duration P99
histogram_quantile(0.99,
sum by (le, service) (rate(http_request_duration_seconds_bucket[5m]))
)
RED 的三个指标必须同时看。只有 Rate 没有 Errors,看不出成功率;只有 Errors 没有 Rate,1 个错误和 1000 个错误看起来一样;只有 Duration 没有前两个,不知道延迟升高是不是因为流量涨了。
3.4 四个黄金信号与 SLO
Google SRE 提出的四个黄金信号是 Latency、Traffic、Errors、Saturation,和 RED + USE 有重叠。工程上更实用的是 SLO 体系:
- SLI(Service Level Indicator):可量化的指标,比如"HTTP 请求成功率";
- SLO(Service Level Objective):SLI 的目标值,比如"30 天内成功率 ≥ 99.9%";
- 错误预算(Error Budget):1 - SLO,比如 0.1%,即 30 天允许 43.2 分钟不可用;
- 燃烧率(Burn Rate):当前错误率相对预算消耗的速度。燃烧率 1 表示按当前速度 30 天刚好用完预算;燃烧率 14.4 表示 1 小时就烧掉 2% 的预算,按这个速度约 50 小时(两天出头)预算见底。
SLO 的价值在于把"服务好不好"变成一个可以计算的数字,而不是靠感觉。看板上放一个"错误预算剩余 62%"的面板,比放 20 条延迟曲线有用得多。
3.5 指标类型与 PromQL 的坑
Prometheus 有四种指标类型,看板设计必须知道它们的区别:
| 类型 | 特点 | 常用函数 | 典型错误 |
|---|---|---|---|
| Counter | 单调递增,重启归零 | rate、increase |
直接画原值,看到一条斜线 |
| Gauge | 可增可减 | avg_over_time、max_over_time |
用 rate,得到无意义的抖动 |
| Histogram | 分桶统计 | histogram_quantile |
忘了 sum by (le) |
| Summary | 客户端算分位数 | 直接取值 | 无法跨实例聚合 |
坑一:rate() 的窗口必须大于采集间隔的 4 倍。scrape_interval 是 15s,用 rate(x[1m]) 只有 4 个采样点,结果抖动很大。Grafana 提供 $__rate_interval 变量,它会自动算出合适的窗口(通常是 4 倍采集间隔),优先用它。
坑二:histogram_quantile 必须按 le 聚合。
promql # 错误:没有 sum by (le),多个实例的桶直接相加,分位数算出来是错的
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
# 正确
histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
histogram_quantile 的输入必须是"每个 le 值对应一个累计计数",如果不聚合,同一个 le 会有多条序列,函数行为未定义。
坑三:分位数不能求平均。avg(histogram_quantile(0.99, ...)) 是错的。分位数不是线性可加的,要先把桶聚合再算分位数。
坑四:increase() 在窗口边界会外推。increase(x[1h]) 会把窗口两端不完整的采样按比例外推,结果可能比实际值大或小。对精度要求高的场景,用 sum_over_time 配合 resets() 自己算,或者接受这个误差。
坑五:没有数据时 histogram_quantile 返回 NaN。服务刚上线、或者某个时间段没有请求,面板会显示 NaN 或空白。用 or vector(0) 兜底,或者在面板的 “No value” 设置里填 0。
3.6 Grafana 的数据模型
一个 panel 的渲染流程:
text Data source query → DataFrame → Transformations → Field options → Visualization
- Query:PromQL 查询,一个 panel 可以有多个 query(A、B、C),用于对比或计算;
- DataFrame:Grafana 的统一数据格式,每列有类型(time、number、string);
- Transformations:数据加工,比如 Join by field、Calculate field、Filter data by value;
- Field options:单位、小数位、阈值、颜色、显示名;
- Visualization:图形类型。
理解这个流程的意义:很多"看板显示不对"的问题,出在 Transform 或 Field options,而不是查询。比如单位设成了 short 而不是 percent,0.95 会显示成 0.95 而不是 95%。
3.7 Grafana 13 的看板能力变化
Grafana 13 对看板做了几处影响设计的改动:
Dynamic dashboards(GA):新的布局引擎,所有看板打开时自动迁移。面板可以按行/标签做动态分组,配合"区块级变量"(public preview),可以解决"一个 $instance 变量影响所有面板"的老问题。
Filters 变量:原来的 Ad hoc filters 改名为 Filters(schema 里仍是 AdhocVariable)。对多租户看板来说,这是一个比手写变量更省事的下钻方式。
Panel styles(public preview):可以一键把颜色、阈值、显示配置应用到多个面板,适合统一风格。以前要改 20 个面板的阈值,现在改一次。
图例数量上限(GA):海量序列的面板渲染性能提升,但这不应该成为你不做聚合的理由。一个面板 500 条曲线,即使渲染不卡,人也看不懂。
Git Sync(GA):看板可以直接和 GitHub/GitLab 仓库同步,在 UI 里编辑后提交 PR。这让"看板即代码"的门槛降低了很多,但也带来新的风险(见第十七章)。
Grafana Advisor(GA):实例健康检查,会提示数据源健康检查失败、插件过时、SSO 配置错误。默认每周跑一次,建议开启并把结果接入告警。
四、整体思路:五层看板体系
4.1 分层与跳转
text L0 值班总览
├── 顶部:4 个 SLO 数字(可用性、错误预算、P99、错误率)
├── 中部:40 个服务的红绿灯表格(可排序、可下钻)
└── 底部:当前 firing 的告警列表
│ 点击服务名
▼
L1 服务视图(每个服务一个 dashboard,用变量区分)
├── 顶部:本服务的 SLO 与燃烧率
├── 中部:RED 三联图 + 延迟分位数 + 状态码分布
├── 依赖:下游服务的健康状态
└── 底部:实例列表(点击进入 L3)
│ 点击实例
▼
L2 资源视图(节点维度)
├── USE 三维度:CPU、内存、磁盘、网络、FD
├── 容器资源:cgroup 限制与实际使用
└── GPU:利用率、显存、温度、XID
│ 点击时间点
▼
L3 排查视图(Explore 或临时 dashboard)
├── 原始指标 + 日志 + trace
└── 按需组合,不长期保存
关键设计原则:每一层只回答一个问题,跳转链路必须是单向向下的。L0 不展示细节,L3 不承担总览职责。
4.2 命名与目录规范
看板多了以后,找不到比看不懂更痛苦。命名规范:
text [层级] 业务域 / 对象 - 用途
示例:
[L0] 全局 / 值班总览
[L1] 交易 / 订单服务
[L1] 交易 / 支付服务
[L2] 基础设施 / 节点资源
[L2] 基础设施 / GPU 资源
[L3] 交易 / 订单服务 - 实例排查
文件夹结构对应组织架构:
text General/
├── 00-值班总览/
├── 10-业务服务/
│ ├── 交易/
│ ├── 用户/
│ └── 营销/
├── 20-基础设施/
├── 30-中间件/
└── 90-归档/
90-归档 放废弃的看板,不要直接删。删了之后有人问"去年那个看板呢",你还能找回来。
4.3 变量的设计
变量是看板复用性的关键。一个好的看板应该能用一套 JSON 服务所有服务,靠变量切换。
text 变量名 类型 查询
$env query label_values(up, env)
$service query label_values(http_requests_total{env="$env"}, service)
$instance query (multi) label_values(up{env="$env", service="$service"}, instance)
$interval interval -
使用时的注意事项:
promql # 单值变量,直接引用
sum(rate(http_requests_total{service="$service"}[$__rate_interval]))
# 多值变量,必须用 =~ 和 regex 格式化
sum(rate(http_requests_total{instance=~"${instance:regex}"}[$__rate_interval]))
${instance:regex} 会把 ["a","b"] 格式化成 a|b,这是多值变量的正确用法。写成 =~"$instance" 在 Grafana 里也能工作(它会自动加锚点),但在复杂表达式里容易出错,显式用 :regex 更稳。
变量的 refresh 设置:
On dashboard load:只在打开时刷新,适合$env这种很少变的;On time range change:时间范围变了就刷新,适合$instance(实例可能扩缩容);On dashboard load是默认值,如果实例列表经常变,要改成On time range change。
4.4 时间范围与刷新
| 看板 | 默认时间范围 | 刷新间隔 | 理由 |
|---|---|---|---|
| L0 总览 | 最近 1 小时 | 30s | 值班需要近实时 |
| L1 服务 | 最近 6 小时 | 1min | 看趋势和对比 |
| L2 资源 | 最近 24 小时 | 5min | 容量趋势 |
| 业务视图 | 最近 7 天 | 15min | 业务方看日/周趋势 |
刷新间隔不要小于 scrape_interval。采集是 15s,看板 5s 刷新只会看到重复数据,还增加 Prometheus 压力。
绝对时间范围(比如 2026-10-20 14:00 ~ 15:30)用于故障复盘。复盘时要把时间范围固定下来,否则过几天再看,数据已经过期了。
4.5 面板数量的控制
一个 dashboard 的面板数量控制在 12-20 个。超过 20 个,滚动就成了负担;少于 8 个,通常信息不够。
控制手段:
- 用 row 折叠不常用的部分;
- 用
Repeat by variable把一个面板复制成多行,而不是手工加 10 个面板; - 把细节移到 L3,L1 只留关键指标。
五、指标盘点:先确认你有什么
5.1 资源层指标(node_exporter)
node_exporter 1.x 的关键指标,按 USE 分类:
| 维度 | 指标 | 说明 |
|---|---|---|
| CPU 使用率 | node_cpu_seconds_total{mode="idle"} |
用 1 - idle 比例算 |
| CPU 饱和度 | node_load1、node_pressure_cpu_waiting_seconds_total |
PSI 指标需要内核 4.20+ 且 cgroup v2 |
| 内存使用率 | node_memory_MemAvailable_bytes |
用 Available 而不是 Free |
| 内存饱和度 | node_pressure_memory_waiting_seconds_total、node_memory_SwapCached_bytes |
swap 使用是危险信号 |
| 磁盘使用率 | node_filesystem_avail_bytes / node_filesystem_size_bytes |
注意过滤 tmpfs、overlay |
| 磁盘饱和度 | node_disk_io_time_seconds_total、node_pressure_io_waiting_seconds_total |
io_time 是设备忙碌时间 |
| 网络流量 | node_network_receive_bytes_total |
注意 direction 和 device |
| 网络错误 | node_network_receive_errs_total、node_network_receive_drop_total |
非 0 就要查 |
| TCP 重传 | node_netstat_Tcp_RetransSegs |
重传率高说明网络质量差 |
| 文件描述符 | node_filefd_allocated / node_filefd_maximum |
接近 1 会 EMFILE |
| 时间偏移 | node_timex_offset_seconds |
超过 1s 要查 NTP |
node_memory_MemAvailable_bytes 和 node_memory_MemFree_bytes 的区别很重要:Free 是完全没用的内存,Available 是包含可回收的 page cache。用 Free 算使用率,会看到"内存永远 90%",因为 Linux 会用空闲内存做缓存。
5.2 容器与 K8s 指标
cAdvisor(通过 kubelet 的 /metrics/cadvisor)提供容器资源:
text container_cpu_usage_seconds_total
container_memory_working_set_bytes
container_fs_reads_bytes_total
container_network_receive_bytes_total
container_memory_working_set_bytes 是判断容器是否接近 OOM 的正确指标,它排除了可回收的 page cache。用 container_memory_usage_bytes 会高估。
kube-state-metrics 提供对象状态:
text kube_deployment_status_replicas_available
kube_deployment_spec_replicas
kube_pod_container_status_restarts_total
kube_pod_container_status_waiting_reason
kube_node_status_condition
kube_job_status_failed
这些指标是"业务可用性"的桥梁:副本数不足、Pod 反复重启、Job 失败,都能直接反映到看板上。
5.3 应用指标
应用需要暴露什么,取决于看板要回答什么。最小集:
text http_requests_total{service, method, path, status} # counter
http_request_duration_seconds_bucket{service, path, le} # histogram
http_requests_in_flight{service} # gauge
http_request_size_bytes{service} # histogram
标签基数是最大的坑。path 如果是 /order/12345 这种带 ID 的,每个订单一个序列,几天就能把 Prometheus 撑爆。正确做法是做路径模板化:
text /order/{id} → path="/order/:id"
/user/{id}/profile → path="/user/:id/profile"
Spring Boot 的 Micrometer 默认会做 uri 标签的模板化(用 @RequestMapping 的模板),Go 的 promhttp 需要自己处理。上线前一定要检查 path 标签的基数:
promql # 基数最高的 10 个标签值
topk(10, count by (path) ({__name__=~"http_requests_total"}))
如果某个 path 的序列数超过 100,基本可以确定没做模板化。
5.4 黑盒探测
有些可用性问题,应用指标看不出来:域名解析失败、证书过期、端口不通、首页白屏。这些要用 blackbox_exporter 从外部探测:
yaml # blackbox.yml
modules:
http_2xx:
prober:http
timeout:5s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
valid_status_codes: [200, 301, 302]
follow_redirects:true
preferred_ip_protocol:ip4
tcp_connect:
prober:tcp
timeout:3s
icmp:
prober:icmp
timeout:3s
对应的探测结果指标:
text probe_success{instance, job} # 1 成功,0 失败
probe_duration_seconds{instance} # 探测耗时
probe_ssl_earliest_cert_expiry{instance} # 证书过期时间戳
probe_http_status_code{instance} # HTTP 状态码
证书过期是最值得做成看板的一项:
promql # 距离证书过期还有多少天
(probe_ssl_earliest_cert_expiry - time()) / 86400
低于 30 天变黄,低于 7 天变红。这一条能消灭掉大部分"证书过期导致全站不可用"的事故。
六、L0 值班总览:5 秒内判断有没有事
6.1 布局
text ┌─────────────────────────────────────────────────────────────┐
│ [可用性 99.94%] [错误预算 62%] [P99 320ms] [错误率 0.06%] │ 4 个 stat
├─────────────────────────────────────────────────────────────┤
│ 服务健康总表(40 行,可排序) │ table
│ 服务名 | 可用性 | 错误率 | P99 | QPS | 状态 │
├─────────────────────────────────────────────────────────────┤
│ 全局 QPS 与错误率趋势 │ timeseries
├─────────────────────────────────────────────────────────────┤
│ 容量水位(CPU/内存/磁盘/连接数 四个 gauge) │ gauge ×4
├─────────────────────────────────────────────────────────────┤
│ 当前告警(firing + pending) │ alert list
└─────────────────────────────────────────────────────────────┘
6.2 四个核心数字
可用性(30 天滚动):
promql sum(rate(http_requests_total{env="prod", status!~"5.."}[30d]))
/ sum(rate(http_requests_total{env="prod"}[30d]))
单位设为 percentunit(0-1 显示成百分比),阈值:< 0.999 变黄,< 0.995 变红。
注意 [30d] 这种大窗口查询很重,Prometheus 要扫 30 天的数据。正确做法是用 recording rule 预聚合:
yaml groups:
-name:slo
interval:1m
rules:
-record:job:sli_availability:ratio_rate30d
expr:|
sum(rate(http_requests_total{env="prod", status!~"5.."}[30d]))
/ sum(rate(http_requests_total{env="prod"}[30d]))
看板查 job:sli_availability:ratio_rate30d,一条序列,毫秒级返回。
错误预算剩余:
promql 1 - (
(1 - job:sli_availability:ratio_rate30d) / (1 - 0.999)
)
结果 0.62 表示还剩 62% 的预算。这个数字比"可用性 99.94%"更直观:99.94% 看起来很好,但如果 SLO 是 99.99%,预算已经用掉 94% 了。
P99 延迟:
promql histogram_quantile(0.99,
sum by (le) (rate(http_request_duration_seconds_bucket{env="prod"}[5m]))
)
错误率:
promql sum(rate(http_requests_total{env="prod", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{env="prod"}[5m]))
这四个数字放在最上面,用大字号 stat 面板,带 sparkline(迷你趋势线)。值班同学扫一眼就知道当前状态。
6.3 服务健康总表
这是 L0 最有价值的面板。用 table 展示所有服务的 RED 指标,按状态排序,异常的排在最上面。
实现方式:多个 instant query + Transform。
promql # Query A:错误率
sum by (service) (rate(http_requests_total{env="prod", status=~"5.."}[5m]))
/ sum by (service) (rate(http_requests_total{env="prod"}[5m]))
# Query B:P99
histogram_quantile(0.99,
sum by (le, service) (rate(http_request_duration_seconds_bucket{env="prod"}[5m]))
)
# Query C:QPS
sum by (service) (rate(http_requests_total{env="prod"}[5m]))
# Query D:副本可用数(kube-state-metrics 的标签是 deployment)
sum by (deployment) (kube_deployment_status_replicas_available)
Query D 这里有个细节:kube-state-metrics 的标签是 deployment,不是 service。要把它和前三列 join 到一起,有两种做法:
- 在 Prometheus 的
metric_relabel_configs里把deployment重命名成service(要求命名规范一致,比如 Deployment 名就是服务名); - 在 Transform 里用
deployment作为连接键,前提是前三列也能提供同名的deployment标签。
命名不统一时,最省事的办法是维护一张"服务名 → Deployment 名"的映射表,用 label_replace 在查询里补标签:
promql label_replace(
sum by (deployment) (kube_deployment_status_replicas_available),
"service", "$1", "deployment", "(.*)-deploy"
)
Transform 配置:
Join by field,字段选service,模式outer join;Organize fields,重命名列、隐藏多余的 Time 列;Sort by错误率降序。
Field options:
- 错误率列:unit
percentunit,thresholds 0.001 黄、0.01 红,cell display modeColor background; - P99 列:unit
s,thresholds 按服务基线; - 状态列:用
Value mapping,把错误率 > 1% 映射成 “异常”,否则 “正常”。
这样一张表,40 个服务的健康状态一屏看完,异常的还在最上面。
6.4 容量水位
四个 gauge,展示全局水位:
promql # CPU 平均使用率
avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
# 内存平均使用率
avg(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# 磁盘最高使用率
max(1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100
# 连接数水位(以 Nginx 为例)
max(nginx_connections_active / (nginx_worker_connections * nginx_worker_count)) * 100
最后一个表达式里的 nginx_worker_connections 不是标准指标,需要自己用 textfile collector 或者 recording rule 注入。更简单的做法是直接看绝对值加阈值:
promql max(nginx_connections_active)
gauge 的阈值设置:< 70% 绿,70%-85% 黄,> 85% 红。这些阈值必须结合业务基线,一个常年 80% CPU 的集群,85% 才告警就太晚了。
6.5 告警列表
Grafana 的 Alert list 面板,过滤条件设为 State: firing,按严重程度排序。这个面板的作用是让值班同学不用切换页面就能看到所有 firing 的告警。
如果告警在 Alertmanager 里,可以用 Alertmanager 数据源;如果用 Grafana 统一告警,直接选 Grafana 数据源即可。
七、L1 服务视图:定位到服务之后看什么
7.1 顶部:SLO 与燃烧率
L1 的顶部不要放资源指标,放 SLO:
promql # 当前燃烧率(1 小时窗口)
(1 - (
sum(rate(http_requests_total{service="$service", status!~"5.."}[1h]))
/ sum(rate(http_requests_total{service="$service"}[1h]))
)) / (1 - 0.999)
燃烧率的解读:
| 燃烧率 | 含义 | 处理 |
|---|---|---|
| < 1 | 消耗速度低于预算 | 正常 |
| 1-6 | 预算会在 5-30 天内耗尽 | 关注,不需要立即处理 |
| 6-14.4 | 预算会在 2-5 天内耗尽 | 当天处理 |
| > 14.4 | 按此速度 2 天内耗尽预算 | 立即处理,等同故障 |
7.2 RED 三联图
三个面板并排,共享时间轴:
promql # Rate
sum by (path) (rate(http_requests_total{service="$service"}[$__rate_interval]))
# Errors
sum by (status) (rate(http_requests_total{service="$service", status=~"5.."}[$__rate_interval]))
# Duration(P50/P90/P99 三条线)
histogram_quantile(0.50, sum by (le) (rate(http_request_duration_seconds_bucket{service="$service"}[$__rate_interval])))
histogram_quantile(0.90, sum by (le) (rate(http_request_duration_seconds_bucket{service="$service"}[$__rate_interval])))
histogram_quantile(0.99, sum by (le) (rate(http_request_duration_seconds_bucket{service="$service"}[$__rate_interval])))
延迟面板放三个分位数,是因为它们回答不同问题:
- P50 上升:所有请求都变慢了,通常是依赖变慢或资源不足;
- P99 上升但 P50 不变:少量请求变慢,通常是长尾(GC、锁竞争、慢查询、缓存击穿);
- P99 剧烈抖动:调度问题、网络抖动、上游超时重试。
7.3 状态码分布
堆叠面积图,按状态码分组:
promql sum by (status) (rate(http_requests_total{service="$service"}[$__rate_interval]))
用 Stack 模式,Percent 堆叠更容易看出比例。5xx 用红色系,4xx 用黄色系,2xx 用绿色系。
这里要注意:4xx 不一定正常。401/403 突增可能是鉴权配置错误,429 突增是限流误伤。看板上要把 4xx 单独画一条线,不要和 2xx 混在一起。
7.4 依赖健康
服务的错误往往来自下游。L1 要展示下游依赖的健康状态:
promql # 下游服务的错误率
sum by (target_service) (
rate(http_client_requests_total{service="$service", status=~"5.."}[$__rate_interval])
) / sum by (target_service) (
rate(http_client_requests_total{service="$service"}[$__rate_interval])
)
前提是应用暴露了带 target_service 标签的出向请求指标。如果没有,至少要有:
promql # 按下游地址的错误率(从网关或服务网格拿)
sum by (upstream) (rate(nginx_http_requests_total{status=~"5.."}[5m]))
依赖健康面板的价值:当订单服务报错时,能立刻看出是订单服务自己挂了,还是支付服务挂了。
7.5 实例对比
用 table 或 heatmap 展示每个实例的指标,快速找出"某个实例拖后腿":
promql # 每个实例的错误率
sum by (instance) (rate(http_requests_total{service="$service", status=~"5.."}[$__rate_interval]))
/ sum by (instance) (rate(http_requests_total{service="$service"}[$__rate_interval]))
如果 8 个实例里 7 个错误率 0.01%,1 个是 5%,那问题就在这一个实例上,不用看整体。这是"平均指标掩盖问题"的典型场景。
用 heatmap 展示延迟分布也很有效:
promql sum by (le) (rate(http_request_duration_seconds_bucket{service="$service"}[$__rate_interval]))
Heatmap 能看出"延迟分布整体右移"还是"只有一小撮请求变慢",比单条 P99 曲线信息量大。
7.6 部署标注
面板上的竖线标注(annotation)能直接把"延迟升高"和"发布"对应起来:
promql # 查询部署变更
changes(kube_deployment_status_observed_generation{namespace="prod"}[10m]) > 0
在 Dashboard settings → Annotations 里添加,颜色设成灰色或蓝色,标签显示 {{deployment}}。
这个功能的价值在于:故障复盘时,80% 的"原因不明"都能靠标注解释。延迟在 14:32 升高,标注显示 14:31 有一次发布,因果关系一目了然。
八、L2 资源视图:USE 三维度
8.1 布局原则
资源看板不要按"指标类型"分组,要按"资源类型"分组。每一行是一个资源,行内按 USE 排列:
text Row: CPU
├── 使用率(按实例,热力图)
├── 饱和度(load / 核数,折线)
└── PSI 等待时间(折线)
Row: 内存
├── 使用率(按实例)
├── Available 趋势
└── swap 使用 + OOM 事件
Row: 磁盘
├── 使用率(按 mountpoint)
├── 预测满盘时间
└── IO 利用率与 await
Row: 网络
├── 带宽(rx/tx)
├── 丢包与错误
└── TCP 重传
Row: 进程与 FD
├── 文件描述符使用率
└── 进程数
用 Repeat by $instance 可以让每个实例一行,但实例多了会很长。更好的做法是"全局热力图 + 单实例下钻"。
8.2 CPU
promql # 使用率(按实例,1 - idle)
100 * (1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[$__rate_interval])))
# 归一化负载(load1 / 核数),> 1 表示排队
sum by (instance) (node_load1)
/ count by (instance) (node_cpu_seconds_total{mode="idle"})
# PSI:CPU 因等待而停滞的时间占比
rate(node_pressure_cpu_waiting_seconds_total[$__rate_interval])
node_pressure_cpu_waiting_seconds_total 需要内核 4.20+ 且 node_exporter 开启 PSI 采集。它的值接近 1 表示 CPU 严重不足,比 load 更准确——load 高但 PSI 低,说明是 IO 等待导致的,不是 CPU 不够。
面板选择:
- 使用率用 heatmap(横轴时间,纵轴实例,颜色表示值),一眼看出哪个实例异常;
- 归一化负载用 timeseries,加一条
y=1的阈值线; - PSI 用 timeseries,阈值 0.2 黄、0.5 红。
8.3 内存
promql # 使用率
100 * (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
# swap 使用
100 * (node_memory_SwapTotal_bytes - node_memory_SwapFree_bytes)
/ node_memory_SwapTotal_bytes
# OOM 事件(需要 node_exporter 的 vmstat 采集或从日志转指标)
increase(node_vmstat_oom_kill[$__rate_interval])
node_vmstat_oom_kill 是 node_exporter 从 /proc/vmstat 采集的,值为累计 OOM kill 次数。这个指标非 0 增长,说明有进程被内核杀了,必须查。
内存面板要特别注意单位。node_memory_* 是字节,Grafana 里设 unit 为 bytes(IEC),显示成 GiB。设成 decbytes 会显示成 GB,和 free -h 的数字对不上,容易误导。
8.4 磁盘
磁盘是运维事故的高发区,要三个面板:
promql # 使用率(排除虚拟文件系统)
100 * (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay|squashfs"})
# 预测满盘时间(线性回归,6 小时窗口,预测 4 小时后)
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 4*3600) < 0
# inode 使用率
100 * (1 - node_filesystem_files_free{fstype!~"tmpfs|overlay"}
/ node_filesystem_files{fstype!~"tmpfs|overlay"})
# IO 利用率
100 * rate(node_disk_io_time_seconds_total{device=~"sd.*|nvme.*"}[$__rate_interval])
# 平均读等待(毫秒):读耗时 / 读完成次数
1000 * rate(node_disk_read_time_seconds_total[5m])
/ rate(node_disk_reads_completed_total[5m])
# 平均写等待(毫秒)
1000 * rate(node_disk_write_time_seconds_total[5m])
/ rate(node_disk_writes_completed_total[5m])
predict_linear 是磁盘看板上最有价值的一个函数。它回答的不是"现在用了多少",而是"按当前写入速度,多久会满"。返回负值表示在预测时间点会满:
promql # 4 小时内会满的磁盘
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 4*3600) < 0
这条查询直接可以做成告警。比"使用率 > 85%"更早发现问题,也更少误报(一个稳定在 88% 的磁盘不会告警,一个从 60% 快速涨到 80% 的会告警)。
inode 耗尽是容易被忽略的故障:磁盘还有空间,但无法创建新文件,报 No space left on device。日志目录、缓存目录、容器层最容易出这个问题。
8.5 网络
promql # 入向带宽(bit/s)
sum by (instance) (rate(node_network_receive_bytes_total{device!~"lo|veth.*|docker.*"}[$__rate_interval])) * 8
# 出向带宽
sum by (instance) (rate(node_network_transmit_bytes_total{device!~"lo|veth.*|docker.*"}[$__rate_interval])) * 8
# 丢包率
sum by (instance) (rate(node_network_receive_drop_total{device!~"lo"}[$__rate_interval]))
/ sum by (instance) (rate(node_network_receive_packets_total{device!~"lo"}[$__rate_interval]))
# TCP 重传率
sum by (instance) (rate(node_netstat_Tcp_RetransSegs[$__rate_interval]))
/ sum by (instance) (rate(node_netstat_Tcp_OutSegs[$__rate_interval]))
* 8 是把字节转成比特,配合 unit bps 显示。如果不乘 8,显示的是 B/s,和网卡标称的 1Gbps/10Gbps 对不上。
重传率超过 0.5% 就说明网络有问题(丢包、拥塞、双工不匹配)。这个指标在"应用变慢但 CPU 内存都正常"时特别有用。
8.6 文件描述符与进程
promql # FD 使用率
100 * node_filefd_allocated / node_filefd_maximum
# 进程数(node_exporter 的 processes collector)
node_processes_pids
# 进程数上限(接近时 fork 会失败)
node_processes_max_processes
FD 使用率超过 80% 要警惕。Nginx、Java 应用、数据库都是 FD 消耗大户,耗尽后表现为 Too many open files,服务直接不可用。
8.7 Kubernetes 层
资源看板之外,还需要一个 K8s 状态看板:
promql # 副本不足的 Deployment
kube_deployment_status_replicas_available
< kube_deployment_spec_replicas
# 反复重启的 Pod(1 小时内重启超过 3 次)
sum by (namespace, pod) (increase(kube_pod_container_status_restarts_total[1h])) > 3
# Pending 超过 10 分钟的 Pod
count by (namespace) (
kube_pod_status_phase{phase="Pending"} == 1
) > 0
# 节点 NotReady
kube_node_status_condition{condition="Ready", status="true"} == 0
# Job 失败
kube_job_status_failed > 0
这些指标的价值在于把"业务不可用"翻译成"K8s 对象异常"。用户看到 502,运维看到"3 个 Pod 在 CrashLoopBackOff",因果链就清楚了。
8.8 GPU 资源
如果集群有 GPU,单独做一个 GPU 看板。核心指标(DCGM Exporter):
promql # 显存使用率
100 * DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)
# SM 实际活跃度(比 GPU_UTIL 更准)
avg by (gpu, instance) (DCGM_FI_PROF_SM_ACTIVE)
# 显存带宽利用率
avg by (gpu, instance) (DCGM_FI_PROF_DRAM_ACTIVE)
# 温度
DCGM_FI_DEV_GPU_TEMP
# 功耗
DCGM_FI_DEV_POWER_USAGE
# XID 错误
DCGM_FI_DEV_XID_ERRORS
GPU 看板的特殊之处:DCGM_FI_DEV_GPU_UTIL 显示 100% 不代表算力用满,要看 DCGM_FI_PROF_SM_ACTIVE。这一点在 GPU 相关的看板设计里必须单独说明,否则会误导容量决策。
九、业务视图:让业务方也看得懂
9.1 业务指标从哪来
业务指标不能靠埋点"顺便"产生,要有明确的定义和采集链路。三种来源:
- 应用埋点:在关键业务动作处打点,比如下单成功、支付成功;
- 数据库查询:定时跑 SQL 统计,转成指标(适合对实时性要求不高的);
- 日志聚合:从访问日志里提取业务事件,用 Loki 或 Fluent Bit 转指标。
第一种最准,第三种最灵活,第二种最容易做但压力大。生产上通常是"关键指标埋点 + 次要指标日志聚合"。
9.2 业务看板的核心面板
promql # 订单创建速率
sum(rate(business_order_created_total[5m])) * 60
# 下单成功率
sum(rate(business_order_created_total[5m]))
/ sum(rate(business_order_submit_total[5m]))
# 支付成功率
sum(rate(business_payment_success_total[5m]))
/ sum(rate(business_payment_attempt_total[5m]))
# GMV(每分钟)
sum(rate(business_gmv_yuan_total[5m])) * 60
业务看板的设计要点:
- 用业务语言,不用技术术语。写"下单成功率",不写"order_created / order_submit 比值";
- 给对比基准。今天的曲线要和昨天、上周同一天对比(Grafana 的
Compare to功能或offset 1d); - 异常自动标注。大促、发版、故障时间点用 annotation 标出来。
9.3 同比与环比
promql # 今天 vs 昨天同一时刻
sum(rate(business_order_created_total[5m]))
sum(rate(business_order_created_total[5m] offset 1d))
# 今天 vs 上周同一天
sum(rate(business_order_created_total[5m] offset 7d))
offset 是 PromQL 里做时间对比的标准方式。注意 offset 要放在区间选择器后面:metric[5m] offset 1d,不能写成 metric offset 1d [5m]。
9.4 漏斗分析
业务方常问"为什么转化率下降了",这需要漏斗:
promql # 各环节转化率
sum(rate(business_funnel_step_total{step="view"}[5m]))
sum(rate(business_funnel_step_total{step="cart"}[5m]))
sum(rate(business_funnel_step_total{step="order"}[5m]))
sum(rate(business_funnel_step_total{step="pay"}[5m]))
用 Bar gauge 或 Stat 面板展示每个环节的绝对值,用另一个面板展示相邻环节的转化率。漏斗的价值在于定位"哪一步掉了",而不是只看最终结果。
十、PromQL 进阶:让看板查得快、算得对
10.1 Recording rules 的命名与分层
看板上的复杂查询应该固化成 recording rule,原因有三个:
- 查询快(预计算好的序列,毫秒级返回);
- 口径统一(所有人用同一个表达式,不会各写各的);
- Prometheus 压力小(不用每次刷新都扫大量原始数据)。
命名规范(参考 Prometheus 官方建议):
text level:metric:operations
level → 聚合级别:job、instance、service、node
metric → 指标名,去掉 _total 后缀
operations → 运算,如 rate5m、ratio、sum
示例:
yaml groups:
-name:slo-rules
interval:30s
rules:
# 服务级请求速率
-record:service:http_requests:rate5m
expr:sumby(service)(rate(http_requests_total{env="prod"}[5m]))
# 服务级错误率
-record:service:http_errors:ratio_rate5m
expr:|
sum by (service) (rate(http_requests_total{env="prod", status=~"5.."}[5m]))
/ sum by (service) (rate(http_requests_total{env="prod"}[5m]))
# 服务级 P99
-record:service:http_request_duration_seconds:p99_5m
expr:|
histogram_quantile(0.99,
sum by (le, service) (rate(http_request_duration_seconds_bucket{env="prod"}[5m]))
)
# 30 天可用性(用 1h 的预聚合再聚合,避免扫 30 天原始数据)
-record:service:sli_availability:ratio_rate30d
expr:|
sum by (service) (avg_over_time(service:http_errors:ratio_rate5m[30d]))
最后一条要注意:avg_over_time 对错误率做平均是近似算法,严格来说应该用"总成功数/总请求数"。更准的做法是保留两个 counter 的预聚合:
yaml -record:service:http_requests:increase1h
expr:sumby(service)(increase(http_requests_total{env="prod"}[1h]))
-record:service:http_errors:increase1h
expr:sumby(service)(increase(http_requests_total{env="prod",status=~"5.."}[1h]))
-record:service:sli_availability:ratio_rate30d
expr:|
1 - (
sum by (service) (sum_over_time(service:http_errors:increase1h[30d]))
/ sum by (service) (sum_over_time(service:http_requests:increase1h[30d]))
)
10.2 聚合与基数控制
看板查询慢,90% 是基数问题。三个原则:
原则一:先过滤再聚合。
promql # 差:先算所有服务的速率,再过滤
sum by (service) (rate(http_requests_total[5m])){service="order"}
# 好:先过滤,再聚合(语法上要写在选择器里)
sum by (service) (rate(http_requests_total{service="order"}[5m]))
原则二:用 sum without 去掉高基数标签。
promql # 保留除 instance、pod 之外的所有标签
sum without (instance, pod) (rate(http_requests_total[5m]))
原则三:避免在面板上做 count by (path) 这种高基数聚合。 路径如果有几千个,一条查询就能拖垮 Prometheus。
10.3 几个容易写错的函数
rate vs irate:
rate用窗口内首尾两个点算平均速率,平滑,适合看趋势和告警;irate用最后两个点算瞬时速率,敏感,适合看瞬时峰值,但曲线抖动大。
看板上的长期趋势用 rate,排查瞬时尖刺用 irate。告警一律用 rate,irate 容易误报。
increase 的外推问题:
promql # 窗口 1h,采集间隔 15s,实际只有 240 个点
increase(http_requests_total[1h])
increase 会把首尾不完整的部分按比例外推。如果窗口边界刚好切在一个点的中间,结果会有几个百分点的误差。对账类场景(比如计费)不能用 increase,要用 sum_over_time 配合 resets()。
histogram_quantile 的插值误差:
分位数是从桶的边界插值算出来的,桶越粗误差越大。默认的 bucket 是 [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],如果实际延迟集中在 0.1-0.25 之间,P99 的精度就很差。要让分位数准,bucket 要在业务关心的区间加密。
holt_winters 已重命名:Prometheus 3.x 里 holt_winters 改名为 double_exponential_smoothing,老看板升级后会报错。
10.4 时间修饰符
promql # 固定时间点(用于复盘对比)
sum(rate(http_requests_total[5m] @ 1760000000))
# 相对偏移
sum(rate(http_requests_total[5m] offset 1h))
# 子查询:先算 1 分钟的 rate,再取 1 小时的最大值
max_over_time(rate(http_requests_total[1m])[1h:1m])
@ 修饰符在故障复盘时特别有用:把"故障时刻"固定住,然后对比"故障前 1 小时"和"故障后 1 小时"。
10.5 空值与缺失
promql # 服务没有流量时,返回 0 而不是空
sum(rate(http_requests_total{service="x"}[5m])) or vector(0)
# 检测目标消失(比 up == 0 更可靠)
absent_over_time(up{job="node"}[5m])
# 某个指标不存在时告警
absent(http_requests_total{service="payment"})
or vector(0) 会让面板在无数据时显示 0,而不是空白。这在"错误率"类面板上很重要:没有请求时错误率应该是 0,不是 NaN。
但要注意:or vector(0) 也会掩盖"采集挂了"的问题。如果指标完全没采集,面板显示 0,看起来一切正常。所以关键指标要用 absent() 单独告警,不能只靠看板。
十一、钻取:从看板到根因的路径
11.1 Data links
在 table 面板的实例列上加 data link,点击跳到 L3:
text URL: /d/instance-detail/instance?var-instance=${__data.fields.instance}&var-env=${env}&from=${__from}&to=${__to}
可用的内置变量:
| 变量 | 含义 |
|---|---|
${__data.fields.instance} |
当前行的 instance 字段值 |
${__from} / ${__to} |
当前时间范围(毫秒时间戳) |
${__url_time_range} |
from=...&to=... 形式的参数串 |
${__value.time} |
数据点的时间 |
${__url_time_range} 是最省事的,直接拼在 URL 后面就能带上时间范围。
11.2 从指标跳到日志
Loki 数据源支持从指标面板跳转:
logql {namespace="prod", pod="$pod"} |= "error"
配合 Grafana 的 “Explore” 链接,或者在 panel 上加 data link:
text /d/explore?left={"datasource":"loki","queries":[{"expr":"{pod=\"${__data.fields.pod}\"} |~ \"ERROR\""}],"range":{"from":"${__from}","to":"${__to}"}}
URL 里的 JSON 需要 encode,手工拼很容易出错。更简单的做法是用 Grafana 的 “Derived fields”:在 Loki 数据源里配置从日志中提取 trace_id,然后在日志面板里直接点 trace_id 跳到 Tempo。
11.3 Exemplars
Exemplar 是"指标数据点上挂的 trace id"。配置后,在延迟面板上点击某个尖刺,能直接跳到那条请求的 trace。
配置步骤:
- 应用侧在 histogram 观测时带上 trace_id(OpenTelemetry 的
Exemplar支持); - Grafana 数据源配置里开启 “Exemplars”,填 trace_id 的标签名;
- Tempo 数据源配置好,并在 Prometheus 数据源的 “Exemplar internal link” 里指向 Tempo。
这一步的收益很高:从"P99 升高"到"看具体哪条请求慢",原来要十几分钟,配好 exemplar 后是两次点击。
11.4 Dashboard links
在看板右上角放几个固定链接:
- 返回 L0 总览;
- 跳到本服务的日志(Loki Explore,带好 namespace 和 service 过滤);
- 跳到本服务的告警规则列表;
- 跳到 Runbook(Wiki 或内部文档)。
Runbook 链接特别值得做。故障时最缺的不是数据,是"下一步该干什么"。把 Runbook 放在看板上,能把平均恢复时间缩短一半。
十二、告警:看板的另一半
12.1 看板和告警的分工
看板是"人主动看",告警是"系统主动叫人"。两者的判断逻辑应该一致,但阈值可以不同:
- 看板上的红色阈值是"值得关注";
- 告警阈值是"必须现在处理"。
如果两者用同一个阈值,要么告警太多(狼来了),要么看板上的红色没人理。
12.2 多窗口多燃烧率
单阈值告警的问题是:阈值低了误报,阈值高了漏报。SRE 的做法是用"燃烧率 + 多窗口":
| 严重度 | 长窗口 | 短窗口 | 燃烧率阈值 | 含义 |
|---|---|---|---|---|
| page(立即处理) | 1h | 5m | 14.4 | 1 小时消耗 2% 预算,约 2 天耗尽 |
| page(立即处理) | 6h | 30m | 6 | 6 小时消耗 5% 预算,约 5 天耗尽 |
| ticket(当天处理) | 3d | 6h | 1 | 3 天消耗 10% 预算,30 天耗尽 |
两个窗口同时满足才告警,作用是"快速恢复"和"防抖":短窗口负责快速发现,长窗口负责确认不是瞬时抖动。
PromQL 实现:
promql # 长窗口燃烧率(1h)
(
1 - (
sum(rate(http_requests_total{service="order", status!~"5.."}[1h]))
/ sum(rate(http_requests_total{service="order"}[1h]))
)
) / (1 - 0.999)
# 短窗口燃烧率(5m)
(
1 - (
sum(rate(http_requests_total{service="order", status!~"5.."}[5m]))
/ sum(rate(http_requests_total{service="order"}[5m]))
)
) / (1 - 0.999)
在 Grafana 告警规则里配两个 query(A 和 B),条件用 A > 14.4 && B > 14.4。
12.3 告警规则的组织
按"影响面"而不是"指标类型"分组:
text Alerts/
├── 00-slo/ # SLO 燃烧率(page)
├── 10-availability/ # 服务不可用、探测失败(page)
├── 20-capacity/ # 磁盘、内存、连接数(ticket)
├── 30-infra/ # 节点、网络、GPU(ticket/page 视情况)
└── 90-meta/ # 采集自身健康(ticket)
90-meta 经常被忽略,但它很重要:监控系统自己挂了,必须有告警。
promql # Prometheus 抓不到任何 target
absent(up{job="node"} == 1)
# 采集延迟过高
prometheus_target_scrape_pool_sync_total 的变化率异常
# 规则评估失败
increase(prometheus_rule_evaluation_failures_total[10m]) > 0
# 告警没发出去
rate(alertmanager_notifications_failed_total[5m]) > 0
12.4 告警内容的设计
一条好的告警应该包含五要素:
yaml annotations:
summary:"订单服务错误率 2.3%,超过 SLO 阈值"
description:"过去 1 小时错误率 2.3%(SLO 0.1%),影响约 1200 次请求。主要错误码 500(占 87%),集中在 order-service-7d9f 实例。"
runbook_url:"https://wiki.example.com/runbook/order-service"
dashboard_url:"https://grafana.example.com/d/order-l1?var-service=order"
impact:"用户下单失败,预计每分钟损失 40 单"
description 里带"主要错误码、集中实例"这种信息,需要告警规则里做聚合。Grafana 的模板变量支持从查询结果里取值:
text {{ with printf "service:http_errors:ratio_rate5m{service=\"order\"}" | query }}
{{ . | first | value | humanizePercentage }}
{{ end }}
runbook_url 和 dashboard_url 是最有价值的两项。值班同学收到告警后,第一件事是点链接,不是思考。
12.5 静默、抑制与维护窗口
- 静默(Silence):临时屏蔽某类告警,比如发版期间屏蔽该服务的 5xx 告警。必须设过期时间,最长 24 小时。
- 抑制(Inhibition):高级别告警触发时,抑制低级别。比如"节点 NotReady"抑制该节点上所有 Pod 的告警,避免一次故障产生 200 条告警。
- 维护窗口(Mute timing):定期维护(比如每周日凌晨的备份)期间不告警。
三者都容易滥用。静默必须有审批和记录,否则会变成"把告警关掉就没人报故障了"。
12.6 告警与看板的联动
告警通知里带 dashboard 链接,点进去直接定位到出问题的服务和时间段。这要求:
- 看板的 URL 支持变量传参(
?var-service=order&from=now-1h); - 告警的
dashboard_url用模板变量拼出来; - 看板的默认时间范围能被 URL 覆盖。
这条链路打通后,从"收到告警"到"看到异常曲线"是 10 秒,而不是 3 分钟。
十三、部署与配置管理
13.1 grafana.ini 关键配置
ini # /etc/grafana/grafana.ini
[server]
protocol = http
http_port = 3000
domain = grafana.internal.example.com
root_url = https://grafana.internal.example.com/
serve_from_sub_path = false
enable_gzip = true
[database]
type = postgres
host = pg.internal.example.com:5432
name = grafana
user = grafana
password = $__env{DB_PASSWORD}
ssl_mode = require
max_open_conns = 50
max_idle_conns = 10
[security]
admin_user = admin
cookie_secure = true
cookie_samesite = lax
disable_gravatar = true
[users]
allow_sign_up = false
auto_assign_org_role = Viewer
[auth.anonymous]
enabled = false
[analytics]
reporting_enabled = false
check_for_updates = false
check_for_plugin_updates = false
[log]
mode = console
level = info
[metrics]
enabled = true
interval_seconds = 30
[unified_alerting]
enabled = true
ha_listen_address = 0.0.0.0:9094
ha_advertise_address = 10.40.1.11:9094
ha_peers = 10.40.1.11:9094,10.40.1.12:9094
几个要点:
password = $__env{DB_PASSWORD}是 Grafana 原生支持的写法,从环境变量读密码,配置文件里不出现明文。注意不要写成${DB_PASSWORD}——那种 shell 风格的替换在 grafana.ini 里不生效,只有容器启动脚本才会处理。另一种等价做法是用GF_DATABASE_PASSWORD环境变量覆盖(GF_前缀 + 段名 + 键名,下划线分隔)。enable_gzip在 Grafana 13 默认已经是 true,显式写上是为了让配置可读。[unified_alerting] ha_peers是多实例告警高可用的关键。三个实例都要配同样的 peer 列表,否则告警会重复发送或者漏发。[analytics]三项关掉,减少外网请求,内网环境更干净。
grafana.ini 的生效方式和其他配置不同,要分清三类:
| 配置类型 | 路径 | 生效方式 | 验证 |
|---|---|---|---|
| 主配置(grafana.ini) | /etc/grafana/grafana.ini |
必须重启进程(systemctl restart grafana-server 或重启 Pod) |
curl /api/health 看版本与数据库状态 |
| 数据源 provisioning | /etc/grafana/provisioning/datasources/ |
文件变更后约 10 秒自动加载,无需重启 | 数据源页面点 “Save & test”,或调 /api/datasources |
| 看板 provisioning | /etc/grafana/provisioning/dashboards/ |
按 updateIntervalSeconds 周期扫描,默认 10 秒 |
看板列表出现新文件对应的条目 |
改 grafana.ini 不要用 systemctl reload——Grafana 不支持 reload,命令会静默失败或者报 “Job type reload is not applicable”。改完必须 restart,重启期间看板不可用约 5-15 秒,要安排在低峰。
13.2 数据源 provisioning
yaml # /etc/grafana/provisioning/datasources/prometheus.yaml
apiVersion:1
datasources:
-name:Prometheus
uid:prom-main
type:prometheus
access:proxy
url:http://prometheus.monitoring.svc:9090
isDefault:true
jsonData:
timeInterval:15s
httpMethod:POST
prometheusType:Prometheus
prometheusVersion:3.15.1
exemplarTraceIdDestinations:
-name:trace_id
datasourceUid:tempo-main
editable:false
-name:Loki
uid:loki-main
type:loki
access:proxy
url:http://loki.monitoring.svc:3100
jsonData:
maxLines:1000
derivedFields:
-name:trace_id
matcherRegex:"trace_id=(\\w+)"
datasourceUid:tempo-main
uid 必须显式指定。看板 JSON 里引用数据源用的是 uid,如果 provisioning 时不指定,每次重建都会生成新 uid,所有看板都会报 “Datasource not found”。
timeInterval: 15s 要和 Prometheus 的 scrape_interval 一致,Grafana 用它来计算 $__rate_interval。
13.3 看板 provisioning
yaml # /etc/grafana/provisioning/dashboards/default.yaml
apiVersion:1
providers:
-name:'ops-dashboards'
orgId:1
folder:'10-业务服务'
folderUid:'biz'
type:file
disableDeletion:true
allowUiUpdates:false
updateIntervalSeconds:30
options:
path:/var/lib/grafana/dashboards/biz
foldersFromFilesStructure:false
三个参数决定行为:
disableDeletion: true:不允许在 UI 里删除 provisioning 的看板,防止误删;allowUiUpdates: false:UI 上改了不保存,重启后恢复成文件版本。这强制了"看板即代码";updateIntervalSeconds: 30:文件变化后 30 秒内自动加载,不用重启 Grafana。
allowUiUpdates: false 是双刃剑。它保证了配置一致性,但也意味着业务方想在 UI 上调个阈值必须先提 PR。如果团队还没建立这个习惯,可以先设 true,等流程跑顺了再关。
13.4 看板即代码的两种方式
方式一:JSON 文件 + Git + provisioning(传统方式)
bash # 从 Grafana 导出看板 JSON
curl -s -H "Authorization: Bearer $GRAFANA_TOKEN" \
"https://grafana.example.com/api/dashboards/uid/order-l1" \
| jq '.dashboard' > dashboards/biz/order-l1.json
# 提交
git add dashboards/biz/order-l1.json
git commit -m "feat(dashboard): 订单服务增加燃烧率面板"
导出时要注意:JSON 里的 id 字段要去掉(否则导入时会覆盖同 id 的看板),version 字段也要处理,否则会有版本冲突。
方式二:Git Sync(Grafana 13 GA)
在 UI 里连接 GitHub/GitLab/Bitbucket 仓库,看板可以直接在 Grafana 里编辑并提交 PR。优点是门槛低,业务方不用学 Git 命令;缺点是:
- 同步冲突的处理不透明,两个人在 UI 上同时改同一个看板,可能丢失改动;
- 从 12.x 升级到 13.0.0 有已知的迁移 bug,可能导致看板和文件夹丢失。
如果要用 Git Sync,必须先升级到 13.0.1 并备份数据库。已经踩过坑的团队反馈,从 13.0.0 升级到 13.0.1 无法恢复丢失的数据,只能还原数据库再升。
13.5 高可用部署
Grafana 本身是无状态的(除了 SQLite 模式),高可用方案:
text ┌── Grafana 实例 1 ──┐
LB (HTTPS) ────────>│ │──── PostgreSQL(共享)
└── Grafana 实例 2 ──┘
│
告警 HA 集群(9094 互连)
要点:
- 数据库必须用 PostgreSQL 或 MySQL,不能用 SQLite(SQLite 不支持多实例并发写);
- 两个实例的
[unified_alerting] ha_peers必须包含彼此; root_url必须一致,否则告警通知里的链接会指向错误的实例;- 会话存储在数据库里(默认),不需要额外的 session 共享。
13.6 权限模型
| 层级 | 机制 | 用途 |
|---|---|---|
| 组织 | Org | 多租户隔离(一般一个公司一个 Org) |
| 团队 | Team | 按业务线分组 |
| 文件夹 | Folder permissions | 控制谁能看/改某个业务域的看板 |
| 数据源 | Data source permissions | 企业版功能,OSS 版只能靠 Org 隔离 |
| API | Service Account + Token | 自动化脚本用,不要用 admin 账号 |
最小权限实践:
bash # 创建 service account(Grafana 9.1+)
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
https://grafana.example.com/api/serviceaccounts \
-d '{"name":"dashboard-ci","role":"Editor"}'
# 生成 token
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
https://grafana.example.com/api/serviceaccounts/1/tokens \
-d '{"name":"ci-token"}'
注意 Grafana 13 里 API 路径正在从 /api 迁到 /apis,旧的 /api 仍然可用但已标记弃用。新写的自动化脚本建议直接用 /apis,避免下次升级再改。
十四、性能优化
14.1 看板慢的三个原因
原因一:查询本身重。 30 天窗口的 rate()、高基数聚合、没有预聚合的分位数。
判断方法:打开面板的 Inspect → Query,看每个 query 的耗时。超过 2 秒的就要优化。
原因二:面板太多。 一个 dashboard 20 个面板,每个 3 个 query,打开一次就是 60 个请求打向 Prometheus。
原因三:刷新太快。 5 秒刷新 + 20 个面板,等于每秒 4 个查询,值班室里开着 5 个大屏就是每秒 20 个。
14.2 优化手段
ini [dataproxy]
timeout = 60
keep_alive_seconds = 30
logging = false
[database]
max_open_conns = 50
[alerting]
# 告警规则的评估并发,太高会打爆数据源
max_attempts = 3
看板层面:
- 把复杂查询做成 recording rule;
- 面板的
Max data points设为面板宽度的 2 倍(默认是宽度像素数,宽屏下会请求过多点); - 用
Min interval限制最小步长(设为15s,和采集间隔一致); - 不常用的面板放进折叠的 row,Grafana 不会加载折叠 row 的数据(需要开启 “Keep rows collapsed” 的懒加载行为,具体看版本)。
Prometheus 层面:
yaml # prometheus.yml 启动参数
--query.timeout=2m
--query.max-samples=50000000
--query.lookback-delta=5m
--query.max-samples 是硬限制,超过就报错。看板查询报 query processing would load too many samples into memory 就是这个限制触发了,说明查询范围太大或基数太高。
14.3 缓存
Grafana 企业版有查询缓存,OSS 版没有。OSS 的替代方案:
- 用 recording rule 预计算(最有效);
- 用 Prometheus 的
--storage.tsdb.retention.time控制数据量,查询更快; - 在前面加一层 Prometheus 的 remote read 缓存(比如 Thanos Query 的缓存);
- 用 Grafana 的 “Snapshot” 功能把看板定格(只适合汇报,不适合运维)。
十五、故障排查
15.1 看板显示 No data
按顺序排查:
bash # 1. 确认时间范围有数据
curl -s "http://prometheus:9090/api/v1/query?query=up{job='node'}&time=$(date +%s)"
# 2. 确认标签匹配
curl -s "http://prometheus:9090/api/v1/labels" | jq '.data' | head -30
# 3. 确认变量值
# 在看板上把变量切成 "All",看是否有数据
# 4. 确认采集目标在线
curl -s "http://prometheus:9090/api/v1/targets?state=active" | jq '.data.activeTargets[] | select(.health!="up")'
常见原因:
| 现象 | 原因 | 处理 |
|---|---|---|
| 所有面板 No data | 数据源配置错误或 Prometheus 挂了 | 检查数据源健康状态 |
| 单个面板 No data | 标签写错、指标名拼错 | 在 Explore 里验证查询 |
| 切换变量后 No data | 变量值在目标时间范围不存在 | 改变量 refresh 为 On time range change |
| 时间范围拉长后 No data | 数据已过期(retention) | 检查 retention 配置 |
15.2 面板显示 NaN
NaN 通常来自 histogram_quantile 在没有数据时返回 NaN,或者 0/0。
处理:
- 面板的 “No value” 设为
0; - 查询加
or vector(0); - 如果是除法,加
> 0过滤掉分母为 0 的情况。
但要注意:NaN 有时是正确的。服务确实没有请求时,错误率就是未定义,显示 0 会掩盖"服务挂了没流量"的问题。这种情况应该用 absent() 单独告警。
15.3 变量不生效
三个常见原因:
- 多值变量没用
=~:instance="$instance"在多选时会被拼成a,b,匹配不到任何序列。改成instance=~"${instance:regex}"。 - 变量依赖顺序错了:
$instance的查询里用了$service,但$service排在后面,Grafana 会按顺序加载,导致$instance查不到值。在变量设置里调整顺序,或者用 “Show dependants” 显式声明依赖。 All值的处理:多值变量选 All 时,值是$__all或者正则.*,取决于 “All value” 的设置。用.*时要注意它也会匹配空字符串。
15.4 时间不对
三种时间问题:
- 时区:Grafana 默认用浏览器时区,Prometheus 存的是 UTC。看板上显示 14:00,Prometheus 里是 06:00,这是正常的。团队内部要统一用哪个时区看,建议统一用 UTC+8。
- 时钟偏移:服务器时间不同步,导致指标时间戳错乱。
node_timex_offset_seconds超过 1 秒就要查 NTP。 - 时间范围与采集间隔不匹配:选 1 分钟范围、15 秒采集,只有 4 个点,曲线会很奇怪。最小时间范围建议是采集间隔的 10 倍以上。
15.5 Grafana 自身的问题
bash # 看 Grafana 日志
kubectl logs -n monitoring deploy/grafana --tail=200
# 看健康状态
curl -s http://grafana:3000/api/health | jq
# 看指标(需要 [metrics] enabled = true)
curl -s http://grafana:3000/metrics | grep -E "grafana_http_request_duration_seconds|grafana_datasource_request"
常见错误:
| 日志 | 原因 | 处理 |
|---|---|---|
failed to connect to database |
数据库连接失败 | 检查连接串、网络、密码 |
datasource not found |
看板引用的 uid 不存在 | 检查 provisioning 的 uid |
context deadline exceeded |
查询超时 | 调 [dataproxy] timeout 或优化查询 |
too many open connections |
数据库连接池不足 | 调 max_open_conns |
如果 Grafana 前面有 Nginx,Live 功能(实时推送)需要 WebSocket 支持:
nginx location / {
proxy_pass http://grafana_backend;
proxy_http_version1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout3600s;
}
漏了 Upgrade 和 Connection 头,Live 功能会一直重连,控制台报 WebSocket connection failed。
十六、升级风险与回滚
16.1 Grafana 13 的破坏性变更清单
| 变更 | 影响 | 应对 |
|---|---|---|
grafana-cli 移除 |
所有用 grafana-cli 的脚本失效 |
改成 grafana cli |
grafana-server 移除 |
systemd 单元里的启动命令失效 | 改成 grafana server |
| Image Renderer 插件移除 | 截图、报表、告警图片全部失效 | 部署新版渲染服务,配 JWT |
/api 迁到 /apis |
自动化脚本可能失效 | 逐步迁移,旧路径暂时兼容 |
| 数字 id 数据源 API 禁用 | 用 id 而非 uid 的脚本失效 | 改用 uid |
enable_gzip 默认开 |
CPU 略增,带宽降 | 一般无需处理 |
| React 18 → 19 | 部分第三方面板插件无法渲染 | 升级插件或替换 |
| 文件夹与看板统一存储 | 升级时迁移数据 | 备份数据库 |
16.2 升级步骤
bash # 1. 备份数据库(最重要)
pg_dump -h pg.internal -U grafana -Fc grafana > /backup/grafana-$(date +%F).dump
# 2. 备份 provisioning 和看板 JSON
tar czf /backup/grafana-conf-$(date +%F).tar.gz /etc/grafana /var/lib/grafana/dashboards
# 3. 记录当前版本
grafana server -v
# 4. 在预发环境用生产数据库的副本升级验证
# (不要直接在生产试)
# 5. 升级(以 Helm 为例)
helm upgrade grafana grafana/grafana -n monitoring \
--set image.tag=13.2.4 \
-f values-prod.yaml
# 6. 验证
curl -s https://grafana.example.com/api/health | jq '.version'
16.3 回滚
bash # 1. 回滚镜像
helm rollback grafana -n monitoring
# 2. 如果数据库 schema 已经迁移,必须还原备份
pg_restore -h pg.internal -U grafana -d grafana --clean --if-exists /backup/grafana-2026-10-20.dump
# 3. 重启
kubectl rollout restart -n monitoring deploy/grafana
关键点:Grafana 的数据库迁移是单向的。 升级到 13 之后,数据库 schema 变了,直接回滚镜像到 12 会启动失败。必须先还原数据库备份,再回滚镜像。
所以升级前的数据库备份不是"建议",是"必须"。备份要验证可恢复(在测试库上 restore 一次),不能只看文件大小。
16.4 灰度策略
Grafana 是内部工具,灰度方式简单:
- 先升级一个实例(HA 部署下有两个),观察 24 小时;
- 确认看板加载、告警评估、数据源查询都正常;
- 再升级第二个实例。
如果只有一个实例,就在低峰期升级,并准备好 30 分钟内可回滚。
十七、生产注意事项
17.1 看板的生命周期
看板会腐烂。一个两年前设计的看板,指标可能已经改名、服务可能已经下线、阈值可能已经不适用。管理方式:
- 每个看板标注 owner 和最后review时间(放在 dashboard 的 description 里);
- 每季度清理一次,90 天没人访问的看板移到归档文件夹;
- 用 Grafana 的 usage insights(企业版)或查询日志统计访问量。
17.2 不要把所有东西都放进 Grafana
Grafana 擅长展示时序指标,不擅长:
- 拓扑图(用专门的工具或 Graphviz 面板);
- 大表格(几千行的表格在 Grafana 里很卡);
- 复杂交互(下钻超过 3 层,体验就很差)。
判断标准:如果一个面板需要"点击、筛选、再点击"才能得到答案,它可能应该是一个独立的应用,而不是 Grafana 面板。
17.3 指标治理
看板的问题往往是指标的问题。三个治理动作:
- 命名规范:
<namespace>_<subsystem>_<name>_<unit>,比如http_request_duration_seconds; - 标签规范:
service、instance、env、version必须有,高基数标签(user_id、order_id)禁止; - 基数监控:定期看
prometheus_tsdb_head_series,增长过快说明有指标爆炸。
promql # 序列数最多的 10 个指标
topk(10, count by (__name__) ({__name__=~".+"}))
17.4 文档与交接
看板设计要有文档,至少包含:
- 每个面板回答什么问题;
- 每个阈值的依据(基线数据、SLO);
- 异常时的处理步骤(Runbook);
- 指标的采集链路(从应用到 Prometheus 到 Grafana)。
没有文档的看板,换个人就废了。这是运维团队最常见的知识流失。
十八、上线检查清单
18.1 设计阶段
| 项 | 检查内容 | 通过标准 |
|---|---|---|
| 分层 | L0/L1/L2 是否齐全 | 每层有明确的读者和用途 |
| 跳转 | 层级之间能否下钻 | 点击链路可用 |
| 变量 | 是否支持环境/服务/实例切换 | 切换后数据正确 |
| 时间范围 | 默认范围是否合理 | 与刷新频率匹配 |
| 面板数 | 是否在 12-20 之间 | 一屏能看完关键信息 |
18.2 指标阶段
| 项 | 检查方式 | 通过标准 |
|---|---|---|
| 采集 | up == 1 |
所有 target 在线 |
| 基数 | count by (__name__) |
无异常增长的指标 |
| 标签 | 抽样查询 | service/instance/env 齐全 |
| 精度 | 与 top、free 对比 |
误差 < 5% |
| 分桶 | 检查 histogram bucket | 覆盖业务关心的区间 |
18.3 告警阶段
| 项 | 检查方式 | 通过标准 |
|---|---|---|
| 规则 | 逐条 review | 有 for、有 severity、有 runbook |
| 通知 | 发测试告警 | 能收到,内容完整 |
| 抑制 | 模拟节点故障 | 不产生告警风暴 |
| 静默 | 检查过期时间 | 无长期静默 |
| 自监控 | absent(up) |
采集挂了能告警 |
18.4 运维阶段
| 项 | 检查方式 | 通过标准 |
|---|---|---|
| 备份 | 每周 pg_dump | 可恢复(验证过) |
| 版本 | grafana server -v |
无已知高危漏洞 |
| 性能 | 打开 L0 耗时 | < 3 秒 |
| 权限 | 抽查文件夹权限 | 业务方只能看自己的 |
| 文档 | Runbook 链接 | 每个告警都有 |
十九、总结
Grafana 看板设计的核心不是"画得好看",而是让人在最短时间内做出正确判断。这需要三件事:
第一,分层。 L0 回答"有没有事",L1 回答"哪个服务",L2 回答"哪台机器"。每层只做一件事,层与层之间用链接串起来。一个 32 面板的大屏之所以没人看,就是因为它试图在一屏里回答所有问题。
第二,选对指标。 资源层用 USE(使用率、饱和度、错误),服务层用 RED(速率、错误、延迟),业务层用 SLO(可用性、错误预算、燃烧率)。只有 CPU 曲线没有业务成功率,等于只有体温计没有病历。
第三,把判断固化下来。 阈值、基线、燃烧率、告警规则,这些都是"判断"的固化。看板上的一条红色阈值线,背后应该有一份基线数据支撑;告警里的一条 runbook 链接,背后应该有一次故障复盘。
最后是一份可以直接落地的顺序:
- 盘清现有指标,确认采集覆盖和标签规范;
- 定义 3-5 个核心 SLO,算出错误预算;
- 做 L0 总览:4 个数字 + 服务健康表 + 告警列表;
- 做 L1 服务视图:RED 三联图 + 依赖健康 + 实例对比;
- 做 L2 资源视图:USE 三维度,重点是磁盘预测和 PSI;
- 配 recording rules,把重查询预聚合;
- 配多窗口燃烧率告警,每条告警带 runbook;
- 打通 L0 → L1 → L2 的跳转和日志链接;
- 把看板 JSON 进 Git,用 provisioning 下发;
- 每季度 review 一次,清理没人看的看板。
看板是运维团队的"共同语言"。设计得好的看板,能让一个新人在凌晨三点独立判断故障范围;设计得差的看板,只会让所有人继续 SSH 上去敲 top。

