先说结论:Grafana provisioning 官方文档写的像屎一样。
不是我嘴臭。上周我们团队刚把 50 多个生产环境的仪表盘从手动导入迁移到 GitOps 流水线,中间踩的坑能写一本《Grafana 血泪史》。Reddit 上 r/sre 板块有人吐槽“配置 provisioning 本身比写仪表盘还痛苦”,我完全同意。
但一旦跑起来,真香。
为什么非要搞 Provisioning?
手动导入仪表盘?那是 2020 年的玩法了。
- 环境一致性:开发、预发、生产三个环境,手动导入三次?翻车概率 100%。
- 版本控制:JSON 文件丢 Git 里,出问题直接
git revert,不用跪着求 DBA 恢复数据库。 - 多租户隔离:不同 Org 需要不同仪表盘?provisioning 天然支持,不用写脚本硬怼 API。
目录结构:别瞎放
Grafana 默认从 /etc/grafana/provisioning/ 读取配置。子目录结构长这样:
provisioning/
├── dashboards/ # 仪表盘配置
│ ├── main.yaml
│ └── sample.json
├── datasources/ # 数据源配置
│ └── prometheus.yaml
├── notifiers/ # 告警通知(5.0+)
│ └── slack.yaml
├── plugins/ # 插件(7.0+)
└── alerting/ # 告警规则(8.0+)
关键点:dashboards/ 目录下放的是 YAML 配置文件,不是直接把 JSON 丢进去。YAML 文件告诉 Grafana 去哪里找 JSON 仪表盘定义。
第一个 YAML 文件怎么写?
这是最简单的配置,从本地文件系统加载仪表盘:
# /etc/grafana/provisioning/dashboards/main.yaml
apiVersion: 1
providers:
- name: 'Production Dashboards'
orgId: 1
folder: 'Production'
type: file
disableDeletion: true
updateIntervalSeconds: 30
allowUiUpdates: false
options:
path: /var/lib/grafana/dashboards
几个参数解释一下:
disableDeletion: true— 防止有人在 UI 上误删,但 Git 里还在,下次同步又回来了,很烦。设成 true 更安全。updateIntervalSeconds: 30— Grafana 每 30 秒扫描一次目录。调太短浪费 CPU,调太长变更延迟高。allowUiUpdates: false— 禁止 UI 修改。既然用了 GitOps,UI 改完 Git 里没有,下次同步直接覆盖,等于白改。
多数据源配置:别踩这个坑
数据源配置在 datasources/ 目录下:
# /etc/grafana/provisioning/datasources/prometheus.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
jsonData:
timeInterval: 15s
queryTimeout: 60s
坑来了:isDefault: true 只能给一个数据源。如果多个数据源都设成 true,Grafana 会随机选一个当默认。我们预发环境翻车就是这原因——Prometheus 和 Thanos 都设了默认,仪表盘有的连 Thanos 有的连 Prometheus,数据对不上,排查了俩小时。
多 Org 配置:官方文档跳着写的部分
Reddit 上有人问“怎么给不同 Org 配不同仪表盘”,官方文档的回答简直就是“你自己看着办”。
实际做法是这样:
# provisioning/dashboards/org1.yaml
apiVersion: 1
providers:
- name: 'Org1 Dashboards'
orgId: 1
folder: 'Team Alpha'
type: file
options:
path: /var/lib/grafana/dashboards/org1
---
# provisioning/dashboards/org2.yaml
apiVersion: 1
providers:
- name: 'Org2 Dashboards'
orgId: 2
folder: 'Team Beta'
type: file
options:
path: /var/lib/grafana/dashboards/org2
注意目录路径要分开,否则 Org1 的仪表盘会出现在 Org2 的列表里,数据源不对应,全是 N/A。
JSON 仪表盘模板化:变量注入
直接硬编码 JSON 是低端玩法。用 Go 模板语法注入变量:
{
"title": "{{ .Name }} - Overview",
"panels": [
{
"title": "CPU Usage",
"targets": [
{
"expr": "avg(rate(node_cpu_seconds_total{mode!=\"idle\", instance=\"{{ .Instance }}\"}[5m]))"
}
]
}
]
}
然后在 YAML 里传变量:
providers:
- name: 'Template Dashboards'
orgId: 1
type: file
options:
path: /var/lib/grafana/dashboards
jsonData:
Name: "Web Server"
Instance: "web-01:9100"
这个功能 7.0+ 才有,老版本别想了。
和 Docker Compose 集成
我们生产环境用 Docker Compose 跑,配置挂载方式:
version: '3.8'
services:
grafana:
image: grafana/grafana:10.4.2
volumes:
- ./provisioning:/etc/grafana/provisioning
- ./dashboards:/var/lib/grafana/dashboards
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
启动后 Grafana 自动加载,不用手动操作。Reddit 上有人问“怎么在 Docker 里自动加载仪表盘”,答案就是挂载 provisioning 目录。
常见翻车现场
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 仪表盘重复出现 | 同一个仪表盘出现多次 | 检查 uid 是否唯一,JSON 里必须有唯一 uid |
数据源显示 N/A | 面板显示无数据 | 检查 orgId 是否匹配,不同 Org 的数据源不通用 |
| 修改后不生效 | 改完 JSON 没变化 | updateIntervalSeconds 设太长,或者 allowUiUpdates: false 没生效 |
| 权限错误 | 无法读取文件 | 检查文件权限,Grafana 容器默认用 grafana 用户(uid 472) |
| 模板变量不渲染 | {{ .Name }} 原样显示 | 检查 Grafana 版本是否 >= 7.0,YAML 里 jsonData 配置是否正确 |
FAQ
问:Provisioning 和 Grafana API 有什么区别?
Provisioning 是声明式配置,Grafana 启动时自动加载,适合 GitOps 流程。API 是命令式操作,适合动态场景。线上环境建议 provisioning 为主,API 为辅。
问:能同时用 provisioning 和 UI 管理仪表盘吗?
可以,但不建议。allowUiUpdates: true 允许 UI 修改,但 provisioning 下次同步会覆盖。最佳实践是只用 provisioning,UI 只做只读查看。
问:Provisioning 支持哪些数据源?
所有官方数据源都支持:Prometheus、InfluxDB、Elasticsearch、MySQL、PostgreSQL、CloudWatch、Azure Monitor 等。第三方插件需要额外配置。
问:怎么调试 provisioning 不生效?
查看 Grafana 日志:docker logs grafana | grep -i provisioning。常见问题是文件路径错误、权限不足、YAML 格式错误。用 yamllint 检查语法。
问:Provisioning 支持告警规则吗?
8.0+ 支持。在 alerting/ 目录下放 YAML 文件,配置告警规则和通知策略。语法和仪表盘类似,但更复杂,建议从官方示例开始。
社区灵感与参考 (References & Community Insights)
本文探讨的架构演进与技术实现方案,深度提炼自 Hacker News、Reddit 等极客社区的真实工程师讨论、线上事故复盘(Post-mortems)以及一线技术博客的实战经验分享。