生产环境里的Grafana仪表盘,用久了就会变成一座迷宫:仪表盘名字千奇百怪,有的是“监控1”,有的是“test”,还有的是“最终版最终版2”。面板挨挨挤挤堆在一起,找半天找不到一个指标。如果你不想在半夜两三点被叫起来时,还要靠肉眼在一个混乱的页面里找“CPU使用率”,那么这套从命名到标签的整理思路,就是你需要的。

在开始之前,先交代一下示例的统一技术栈:本文所有示例代码都使用 Node.js 编写,运行后调用 Grafana HTTP API 管理仪表盘;监控查询虽然没有单独成段,但都以 PromQL 字符串形式嵌入在 Node.js 代码中。

一、为什么需要系统化设计规范

1.1 生产环境的痛点

生产环境的监控图表,往往是多个团队、多个时期、多个需求拼凑出来的。你可能会看到这样的场景:A团队建了一个叫“接口监控”的仪表盘,B团队又建了一个叫“接口监控副本”的仪表盘,两个盘里的面板混在一起,却没人说得清哪个是权威版本。有人习惯用“CPU”“内存”这种泛泛的名字,有人用“主机性能查看”。这些名字在搜索框里一打,出来的结果五花八门,根本没法快速定位。

更麻烦的是标签。标签是监控数据上挂着的“小牌子”,用来区分数据来自哪台机器、哪个应用。如果标签命名随意,比如有人用“server”,有人用“host”,还有人用“node”,哪怕他们指的是同一个东西,聚合查询的时候也要写好几条规则,稍有不慎就查错数据。标签的值不够统一也是大问题,比如环境名一会儿叫“prod”,一会儿叫“production”,一会儿叫“生产”,在做环境对比或者发布追踪时,很容易被这种不一致带偏。

1.2 规范能带来什么

一套好的规范,不是给大家添麻烦,而是把“约定”变成“习惯”。有了统一的命名和标签,你搜一个仪表盘,输入关键字就能立刻找到;看到面板标题,不用打开也知道它展示的是什么指标;写PromQL的时候,标签选择器可以复用同一套规则,不用每回都去猜“那个标签叫什么来着”。

从团队协作的角度看,规范还能减少交接成本。新人来了,对照规范看两天,就能自己上手做面板。老员工也不用反复解释“我们这里的命名规则是……”。规范看起来是约束,其实是效率。

二、命名规范

2.1 仪表盘命名规则

仪表盘名字是用户进入Grafana第一眼看到的东西,也是搜索的主要依据。我推荐使用“环境 - 业务 / 子系统 - 监控维度”的格式。比如“生产 - 订单中心 / 服务状态 - 核心指标”,一眼就能看出它属于生产环境,业务是订单中心,关注的是服务状态,内容围绕核心指标展开。这个格式的好处有三个:第一,环境明确,不会把测试和生产搞混;第二,业务归属清楚,每个团队都知道自己该看哪个盘;第三,维度可扩展,比如说“核心指标”和“详细诊断”可以做成两个盘,互相配合。

尽量不要用“最终版”“新建仪表盘”“本地测试”这类没有信息量的名字。也别用“12345”这种仅靠编号区分的名字,因为编号背后没有含义,时间一长没人记得住。

2.2 面板命名规则

面板是仪表盘的组成单元,每个面板展示一个或一组指标。面板命名建议采用“指标描述 + 聚合方式”,比如“HTTP请求数(rate)”或者“CPU使用率(平均)”。如果面板展示的是比率,一定要在标题里写明“率”字,例如“错误率”“成功率”,避免读者误以为看到的是绝对值。另外,面板标题尽量不要超过二十个字,太长会显得拥挤,而且移动端展示不全。

这里给出一个在Grafana里创建面板时用到的模型片段,注意看标题字段是怎么定义的:

// 定义面板配置对象
const panel = {
  title: 'HTTP请求数(rate)',       // 面板标题:指标 + 聚合方式
  type: 'timeseries',                // 面板类型:时序图
  fieldConfig: {
    defaults: {
      unit: 'reqps',                 // 单位:请求每秒
      min: 0                         // 最小值,避免负值误导
    }
  },
  targets: [
    {
      expr: 'sum(rate(http_requests_total[$__rate_interval])) by (app)',
      legendFormat: '{{app}}'
    }
  ]
};

上面的变量$__rate_interval是Grafana内置的,会自动根据时间范围调整步长。legendFormat里的{{app}}会从标签里动态取值,这样每条线的图例名就是应用名,非常直观。

2.3 变量命名规则

Grafana的变量是指仪表盘顶部的下拉框,比如“环境”“应用”“地域”。变量的命名要短、要通用。推荐使用小写字母加下划线,比如$env$app$region,不要用$v1这种没有含义的命名。变量定义时,要设置“All”选项,方便用户一次性查看全部数据。在模板变量中,标签选择器会用到变量值,所以要确保定义和查询的标签名完全一致。

看一个变量定义示例,这里用JavaScript返回Grafana需要的变量配置:

// 定义环境变量
const envVariable = {
  name: 'env',                       // 变量名,使用时写 $env
  label: '环境',                      // 下拉框显示名称
  type: 'query',                     // 变量类型:基于查询生成
  query: {
    datasource: 'Prometheus',        // 数据源名称
    expr: 'label_values(node_info, env)' // 从 node_info 指标中提取所有 env 标签值
  },
  current: {},
  options: [],
  includeAll: true,                  // 允许用户选择“所有”
  multi: true,                       // 支持多选
  allValue: '.*'                     // 所有时用正则匹配全部
};

注意label_values是PromQL中常用的一个函数,专门用来获取某个标签的所有取值。它不会扫描全量数据,只查询匹配到的指标,性能还算不错。

三、标签分类规范

3.1 标签设计原则

标签(Label)在Prometheus里是附着在指标上的键值对,比如http_requests_total{code="200", app="order"} ,其中codeapp就是标签。标签的价值在于筛选和聚合,但标签并不是越多越好。设计标签时,要遵守三个原则:

第一,基数要小。一个标签的取值数量如果特别大,比如把用户ID、请求ID放到标签里,会导致数据量爆炸,查询效率严重下降。生产环境中,标签的取值个数一般控制在几百个以内,超过十万个就要立刻评估是不是设计错了。第二,含义要稳定。标签所代表的信息应该长期不变,比如“环境”“应用”“地域”都是稳定的;“当前CPU温度”这种频繁变化的就不适合做标签。第三,命名要统一。同一个含义的标签,在所有指标上必须同名,不要出现“app”和“application”混用的情况。

3.2 常用标签体系

我建议每个团队维护一套公开的标签字典,把常用标签固定下来。下面是一套在大多数公司直接可用的标签体系:

  • env:环境,取值如devstagingprod
  • app:应用名,对应微服务名称
  • team:负责团队,方便按团队筛选告警
  • region:地域,比如cn-north-1cn-east-2
  • instance:实例地址,通常由导出器自动生成,格式是ip:port
  • job:监控目标分组,例如node-exportermysql-exporter

注意,jobinstance是Prometheus自动加上的标签,建议保留并充分利用。team标签虽然看起来业务相关,但它能帮助不同团队只看自己的指标,建议在抓取配置里统一打上。

3.3 标签在变量和查询中的运用

标签的存在,让Grafana的变量变得非常简单。你可以让变量直接从一个指标的标签中取值,这样下拉框里的选项永远和真实数据一致。同时,PromQL查询可以通过标签选择器过滤出目标数据。比如下面这段查询,就是先按应用过滤,再按环境过滤,最后计算速率:

// 查询订单服务的HTTP请求量,区分环境
const query = `
  sum(
    rate(
      http_requests_total{
        app="order",
        env="$env"
      }[$__rate_interval]
    )
  ) by (app)
`;
// 解释:
// app="order" 精确匹配应用名
// env="$env"  使用Grafana变量,用户选择哪个环境就查哪个
// by (app)    结果按应用聚合,如果只有一个应用,可省略

这种写法非常直观,而且由于标签名统一,写的时候不需要去查文档。如果你在变量里配置了“所有”选项,$env会展开成正则,比如env=~"$env",Grafana会自动处理,不需要手动改。

四、应用场景示例

4.1 场景一:Web服务监控

假设你有一个订单中心服务,需要监控QPS、延迟和错误率。按照规范,仪表盘命名是生产 - 订单中心 / 服务状态 - 核心指标。面板标题分别是请求量(QPS)平均延迟(P99)错误率(5xx)。下面是用JavaScript通过Grafana API创建该仪表盘时,定义面板的示例:

const dashboardModel = {
  title: '生产 - 订单中心 / 服务状态 - 核心指标', // 仪表盘标题遵守命名格式
  tags: ['production', 'order', 'web'],            // 仪表盘标签,便于搜索
  panels: [
    {
      id: 1,
      type: 'timeseries',
      title: '请求量(QPS)',                       // 指标+聚合方式
      gridPos: { x: 0, y: 0, w: 12, h: 8 },
      targets: [
        {
          expr: 'sum(rate(http_requests_total{app="order", env="$env"}[$__rate_interval])) by (env)',
          legendFormat: '{{env}}'
        }
      ]
    },
    {
      id: 2,
      type: 'timeseries',
      title: '平均延迟(P99)',
      gridPos: { x: 12, y: 0, w: 12, h: 8 },
      targets: [
        {
          expr: 'histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{app="order", env="$env"}[$__rate_interval])) by (le))',
          legendFormat: 'P99'
        }
      ]
    },
    {
      id: 3,
      type: 'timeseries',
      title: '错误率(5xx)',
      gridPos: { x: 0, y: 8, w: 12, h: 8 },
      targets: [
        {
          expr: 'sum(rate(http_requests_total{app="order", env="$env", code=~"5.."}[$__rate_interval])) / sum(rate(http_requests_total{app="order", env="$env"}[$__rate_interval]))',
          legendFormat: '错误率'
        }
      ]
    }
  ],
  templating: {
    list: [
      {
        name: 'env',
        label: '环境',
        type: 'query',
        query: {
          datasource: 'Prometheus',
          expr: 'label_values(http_requests_total, env)'
        },
        includeAll: true,
        multi: true
      }
    ]
  }
};
// 实际调用API时,就用这个 model 作为请求体

这个例子已经是一个非常完整的仪表盘骨架了。注意gridPos定义了面板在仪表盘上的位置和大小,它由x、y、w、h四个整数控制,单位是栅格。合理的栅格规划能避免面板重叠,也让截图或者投屏时更美观。

4.2 场景二:数据库监控

数据库面板的命名和Web服务类似,但指标不同。比如MySQL监控,常见指标有连接数、慢查询数、InnoDB缓冲池命中率。我们仍然遵循同一套标签逻辑,把job="mysql"作为数据库实例的分组。下面给出一个MySQL连接数的面板示例:

// 定义MySQL连接数面板
const mysqlConnPanel = {
  title: 'MySQL连接数(当前)',          // 标题清晰
  type: 'timeseries',
  targets: [
    {
      expr: `sum(mysql_global_status_threads_connected{job="mysql", env="$env"}) by (instance)`,
      legendFormat: '{{instance}}'
    }
  ],
  fieldConfig: {
    defaults: {
      unit: 'short',                     // 单位:count
      thresholds: {
        steps: [
          { color: 'green', value: null },
          { color: 'orange', value: 100 }, // 连接数超过100变橙色
          { color: 'red', value: 200 }     // 超过200变红色
        ]
      }
    }
  }
};
// 这里没有写完整仪表盘,只展示面板部分,实际组装时按同样方式放到panels数组即可

注意阈值在这里只是视觉上的提醒,并不是真正的告警。告警还是需要单独配置在通知规则里,但阈值可以让值班人员一眼看出问题严重性。

4.3 场景三:Kubernetes集群监控

Kubernetes环境下的仪表盘,需要监控每个Pod的资源使用量。此时标签通常由kube-state-metric和cAdvisor自动生成,比如namespacepodcontainer。我们依然可以用同样的命名规范,只是标签体系要额外推荐使用namespace作为第一筛选维度。下面是一个容器内存使用量的面板定义:

// 容器内存使用量面板
const memoryPanel = {
  title: '容器内存使用量(bytes)',
  type: 'timeseries',
  targets: [
    {
      expr: 'sum(container_memory_usage_bytes{namespace="$namespace", container!=""}) by (pod)',
      legendFormat: '{{pod}}'
    }
  ],
  units: 'bytes'
};

// 对应的namespace变量定义
const nsVariable = {
  name: 'namespace',
  label: '命名空间',
  type: 'query',
  query: {
    datasource: 'Prometheus',
    expr: 'label_values(kube_namespace_labels, namespace)'
  },
  includeAll: true,
  multi: true
};

你也可能想知道,为什么不用container_memory_working_set_bytes,那是另一个能反映真实工作集内存的指标。在K8s监控里,通常更关注工作集大小,这里为了例子简单,使用了基础指标。在真实生产环境,需要结合业务调整。

五、技术优缺点分析

5.1 Grafana + Prometheus 的优势

这套技术栈最突出的优点是“灵活”。指标先被Prometheus收集,Grafana只负责展示和配置,两边通过标签建立联系。标签几乎是透明的,你可以随时在面板里增加一个筛选条件,不需要改代码或重启服务。另一个优点是完全开放,不绑定任何商业闭源协议。社区非常活跃,各式各样的导出器、插件、分享模板遍地都是,遇到问题很容易找到解决方案。

5.2 不可忽视的缺点

缺点也很明显。第一,Prometheus的标签高基数问题,如果设计不当,会直接把内存和查询拖垮。我们前面说标签基数要小,这不是吓唬人。第二,Grafana的权限模型比较简单,对“文件夹级”的隔离支持不够细,需要结合LDAP或反代做更多控制。第三,PromQL对新手不友好,聚合运算的语法和传统SQL差别较大,学习成本有点高。

5.3 关联技术:PromQL 聚合操作

既然提到PromQL,这里稍微展开一点。PromQL的聚合操作通常和bywithout连用,用来把多个时间序列合并成一个。比如sum(rate(...)) by (app),就是先对每个序列计算速率,再按照app标签分组求和。by是保留指定标签,without是去掉指定标签,语义正好相反。如果你希望同时看到多个维度的数据,可以写by (app, env)。理解这两个关键字,很多面板的查询写法就变得顺理成章了。在Grafana中,legendFormat通常写成{{app}},其实对应的标签名就是by后面保留的那个标签,两者配合起来才不会有“图例明明是空”的问题。

六、注意事项

6.1 版本管理

仪表盘本质上是一份JSON文件,完全可以像代码一样纳入Git仓库。建议把每个仪表盘导出为独立JSON文件,放在一个叫grafana-dashboards的目录下面,提交时使用统一的命名。这样任何改动都有记录,回滚也方便。如果用了Grafana的Provisioning功能,可以自动从仓库加载这些JSON文件,几乎能做到“配置即代码”。下面是一个用Git管理时的目录结构示意,注意这不是代码,只是一个参考结构:

grafana-dashboards/
  production/
    order-service.json
    mysql.json
  staging/
    order-service.json
  common/
    shared-labels.json

实际上,你还可以用脚本批量校验JSON的格式和命名,比如检查标题是否带有“-”分隔符,防止有人漏掉规范。

6.2 权限控制

生产环境的仪表盘,最好限制普通用户直接编辑。Grafana支持设置管理员、编辑者、查看者三种角色。建议每个业务团队有独立的文件夹或组织,只有团队负责人拥有编辑权限,其他成员只读。如果公司有多个环境,可以通过添加组织或使用团队权限来控制可见范围。实施时注意,不要创造过多的“超级管理员”,否则又回到混乱状态。

6.3 数据源与告警一致性

很多仪表盘查询语句里会写死数据源名称,比如datasource: 'Prometheus'。如果生产环境有多个Prometheus实例,最好为每个环境创建独立的数据源,并在变量中绑定数据源,避免跨环境查错数据。告警规则和仪表盘要共用一套标签体系,比如告警里用了env="prod",仪表盘变量里也要有对应的prod选项。不然会出现仪表盘显示正常,但告警永远不触发的乌龙。

七、文章总结

生产环境的Grafana仪表盘不是个人笔记,而是团队的公共资产。命名规范解决的是“找得到”的问题,标签分类解决的是“查得对”的问题,版本管理解决的是“回得去”的问题。本文构建了一套从命名到标签的系统化思路:仪表盘名采用“环境 - 业务 / 子系统 - 维度”,面板名写清指标和聚合方式,变量名保持简短统一;标签遵循低基数、稳定含义、统一命名三个原则,并推荐了envappteam等基础标签。通过三个场景示例——Web服务、MySQL、Kubernetes——可以看出,只要前期把命名和标签理顺,后期所有面板都能非常丝滑地组装在一起,团队协作也会变得轻松很多。

最后想说的是,没有哪套规范是万能的,但“没有规范”一定是万万不能的。你可以先照着这套思路搭一个“种子仪表盘”,然后逐步推广,慢慢让团队里的每个成员都感受到,原来干净整齐的监控画面,真的能省下大量用来挠头的时间。