1. 从一次面板迁移翻车说起:Grafana 在监控链路里到底管什么
第一次把 Grafana 面板从测试环境搬到生产环境时,我的预期是"导出 JSON、导入、收工",实际结果是所有图表变成一片空白,日志里反复刷着一行failed to upgrade legacy queries datasource im7_otuvz was not found。那一刻我才意识到,Grafana 面板本身不存数据,它只是一堆查询语句加展示配置的组合,真正决定这张图能不能出结果的,是背后那条"数据源 - 查询 - 展示"的完整链路。任何一个环节的标识对不上,面板就是一张好看的废纸。
先把定位说清楚。Prometheus 负责采集和存储时间序列数据,它提供了 PromQL 这个查询语言和一套 HTTP 接口;Grafana 不采集数据,它做的事是连接数据源、把查询结果画出来、再加上告警和权限这一层。很多人把这两个东西混在一起讲,导致排查问题时方向跑偏——Grafana 上图表不显示,你去查 Prometheus 的采集目标,查半天发现采集完全正常,问题其实在 Grafana 的数据源 UID 或者时间范围设置上。
1.1 Grafana 与 Prometheus 的职责边界
用一句话概括:Prometheus 管"数据从哪来、怎么存",Grafana 管"数据怎么看、怎么看懂"。
| 能力 | Prometheus | Grafana |
|---|---|---|
| 指标采集 | 核心能力,基于 pull 模型抓取 | 不做采集 |
| 时序存储 | 内置 TSDB,默认本地磁盘 | 不存储指标数据 |
| 查询语言 | PromQL | 转发 PromQL,自身不解析 |
| 可视化 | 只有简单的 Graph 页面 | 核心能力,多数据源统一展示 |
| 告警 | 规则在 Prometheus 侧求值 | 支持 Grafana 托管告警与展示 Prometheus 规则 |
| 通知发送 | 只负责把告警推给 Alertmanager | 可做联系人、通知策略,也可对接外部 Alertmanager |
这张表的意义在于划清排查边界。比如"告警没收到"这件事,可能是 Prometheus 规则没触发、可能是 Alertmanager 路由没匹配上、也可能是 Grafana 侧联系人配置错了。把链路拆成三段,逐段验证,比盲目改配置快得多。
1.2 什么时候该上 Grafana,什么时候一个 curl 就够了
我见过不少人一上来就搭全套,结果只有三台机器、五个指标,维护成本比收益还高。判断标准其实很朴素:
- 指标数量少于 20 个、看的人只有你自己,直接
curl localhost:9090/api/v1/query?query=up或者用 Prometheus 自带的 Graph 页面就够; - 需要多人共享、需要按团队分权限、需要历史趋势对比、需要把告警发到群里,这时候 Grafana 的价值才体现出来;
- 一旦出现"不同数据源要放在一张大屏上"的需求,比如同时看 Prometheus 的指标、数据库的连接数、日志系统的错误量,Grafana 的多数据源能力就是刚需。
提示:Grafana 本身对硬件要求不高,2 核 4G 跑几十个仪表盘完全够用。真正吃资源的是 Prometheus 的 TSDB 和 Grafana 里那些"一次查 30 天原始数据"的面板。
这套组合最常见的落地场景就是:业务服务暴露/metrics接口,Prometheus 定时抓取,Grafana 做展示层,Alertmanager 做通知层。整条链路跑通之后,日常新增一个监控项的工作量,基本就是改一个规则文件和加一个面板,边际成本很低。
2. Docker 环境下 Prometheus 与 Grafana 的镜像选型与部署
用 Docker 部署这套组合是现在最主流的方式,好处是环境隔离干净、版本切换方便、迁移时打包带走就行。但镜像标签怎么选、数据怎么持久化、配置文件怎么挂载,这三点没想清楚,后面全是坑。
2.1 镜像标签:别用 latest
官方镜像的latest标签会随上游更新而变动,某次docker compose pull之后版本悄悄从 9.x 跳到 10.x,仪表盘布局和告警配置格式都可能变。我在测试环境就吃过一次亏:Grafana 从 9 升到 10 之后,老版本的部分配置项改了默认值,触发了一堆告警抖动。
推荐做法是锁定具体版本:
docker pull prom/prometheus:v2.51.2 docker pull prom/alertmanager:v0.27.0 docker pull grafana/grafana-oss:10.4.2关于grafana/grafana和grafana/grafana-oss的区别,简单说前者在 2021 年之后变成了包含部分企业特性的构建,后者是纯开源版。自建场景下用grafana-oss更干净,功能对个人和中小团队完全够用。
内网环境没法直连镜像仓库时,用docker save/docker load做离线搬运:
# 在有网的机器上导出 docker save -o grafana-10.4.2.tar grafana/grafana-oss:10.4.2 docker save -o prometheus-2.51.2.tar prom/prometheus:v2.51.2 # 拷贝到目标机器后导入 docker load -i grafana-10.4.2.tar docker load -i prometheus-2.51.2.tar这里有个细节:导出时最好把prom/prometheus:v2.51.2这种带标签的完整名字写全,否则导入后镜像名会变成<none>,编排文件里再引用就找不到。另外docker save出来的 tar 包体积会比较大,Grafana 镜像通常几百 MB,用gzip压一下能省一半空间。
2.2 docker-compose 编排文件的逐行拆解
下面这份编排文件是我在几个项目里反复用过的版本,字段不多但每个都有用处:
version: "3.8" services: prometheus: image: prom/prometheus:v2.51.2 container_name: prometheus restart: unless-stopped volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./prometheus/rules:/etc/prometheus/rules:ro - prometheus-data:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.path=/prometheus" - "--storage.tsdb.retention.time=15d" - "--web.enable-lifecycle" ports: - "9090:9090" networks: - monitor alertmanager: image: prom/alertmanager:v0.27.0 container_name: alertmanager restart: unless-stopped volumes: - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - alertmanager-data:/alertmanager ports: - "9093:9093" networks: - monitor grafana: image: grafana/grafana-oss:10.4.2 container_name: grafana restart: unless-stopped environment: GF_SECURITY_ADMIN_USER: admin GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD} GF_USERS_ALLOW_SIGN_UP: "false" GF_INSTALL_PLUGINS: "" volumes: - ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro - grafana-data:/var/lib/grafana ports: - "3000:3000" depends_on: - prometheus networks: - monitor volumes: prometheus-data: grafana-data: alertmanager-data: networks: monitor: driver: bridge几个值得展开的点:
--web.enable-lifecycle开启之后,改完prometheus.yml不用重启容器,直接curl -X POST http://localhost:9090/-/reload就能热加载。这个参数在生产环境很实用,重启 Prometheus 会中断采集,虽然时间很短,但在告警密集的时段容易造成误报。
GF_SECURITY_ADMIN_PASSWORD用环境变量注入而不是写死在编排文件里,配合.env文件管理。.env记得加进.gitignore,我见过有人把带密码的编排文件直接推到公开仓库,虽说不是生产环境,但习惯一旦坏了很难改回来。
三个数据卷prometheus-data、grafana-data、alertmanager-data是命脉。grafana-data里存着grafana.db,所有仪表盘、用户、数据源配置都在里面,这个卷丢了等于整个 Grafana 归零。所以备份策略里,这个卷的优先级要排在最高。
2.3 配置文件挂载的三个常见失误
第一个失误:挂载目录写成文件路径。把./prometheus/prometheus.yml挂到/etc/prometheus(目录),结果容器启动直接报错说找不到配置文件。正确做法要么挂单个文件路径,要么把整个目录挂进去,两者不能混。
第二个失误:只读挂载忘了加:ro,然后容器里的进程写坏了宿主机上的文件。配置类挂载都建议加:ro,反正你本来也不打算让容器改配置。
第三个失误:改了配置文件忘了同步 reload。Prometheus 的规则文件、Alertmanager 的路由配置,改完之后都要走一次 reload 或者重启,光改文件是没用的。我习惯在改完配置后跑一遍promtool check config和promtool check rules,语法错误在容器外就能拦下来,比看日志快得多:
docker run --rm -v $(pwd)/prometheus:/etc/prometheus \ prom/prometheus:v2.51.2 \ promtool check config /etc/prometheus/prometheus.yml3. "datasource was not found" 报错的完整排查链路
回到开头那个问题。这行报错在 Grafana 8 之后特别常见,理解它的成因,基本上就理解了 Grafana 数据源引用机制的演进。
3.1 报错现场还原
从测试环境导出仪表盘 JSON,导入到生产环境之后,页面正常渲染,但每个面板都显示 "No data",同时 Grafana 日志里不断出现:
logger=context userId=1 orgId=1 uname=admin msg="failed to upgrade legacy queries" error="datasource im7_otuvz was not found"这里的关键信息是中间那串im7_otuvz。它看起来像随机字符串,实际上是源环境里那个 Prometheus 数据源的uid。Grafana 7 之前,面板 JSON 里引用数据源用的是名字:
"datasource": "Prometheus"Grafana 8 引入了 uid 机制,因为同一个组织里可以存在多个同名数据源,靠名字引用会产生歧义。新版面板 JSON 变成这样:
"datasource": { "type": "prometheus", "uid": "im7_otuvz" }当 Grafana 加载一份老格式的面板时,会尝试把字符串形式的数据源引用"升级"成对象形式——先按名字找,找不到再按 uid 找。如果目标环境里既没有同名数据源,也没有这个 uid 的数据源,升级就失败,查询发不出去,面板自然空白。这就是整个报错的来龙去脉。
3.2 逐层排查:从 uid 到数据源注册表
排查按这个顺序走,基本十分钟内能定位:
第一步,确认目标环境里到底有哪些数据源。打开Connections - Data sources,看列表里 Prometheus 数据源的 uid 是什么。也可以在 Grafana 里直接调 API:
curl -s -H "Authorization: Bearer $GRAFANA_TOKEN" \ http://localhost:3000/api/datasources | jq '.[] | {name, uid, type}'输出的 uid 如果和报错里的im7_otuvz不一致,问题就确认了。
第二步,确认是"部分面板"还是"全部面板"受影响。如果只有个别面板报错,说明这些面板硬编码了某个特定 uid,而其他面板用的是变量引用,能自动解析。这种情况下单独改那几个面板就行。
第三步,确认是不是导入时选了"外部共享"格式。导出面板时 Grafana 提供一个 "Export for sharing externally" 选项,勾上之后 JSON 里的数据源会被替换成${DS_PROMETHEUS}这种占位符,导入时弹窗让你手动选数据源。如果你用的是这个选项,但导入时选了"不指定"或者关掉了弹窗,占位符就没被替换,也会出现找不到数据源的情况。
3.3 修复方案与预防手段
修复思路有三个层次,按从快到慢排列:
方案一:把目标环境的数据源 uid 改成和源环境一致。这是最彻底的做法。如果用 provisioning 方式管理数据源,直接在 YAML 里显式声明 uid:
apiVersion: 1 datasources: - name: Prometheus type: prometheus uid: im7_otuvz access: proxy url: http://prometheus:9090 isDefault: true jsonData: timeInterval: 15s httpMethod: POSTuid这个字段是手动指定的,不指定的话 Grafana 会自动生成一个随机值,每次重建数据源都不一样——这正是迁移时 uid 对不上的根源。只要把 uid 固定下来写进 provisioning 文件,同一个面板 JSON 就能在任何环境直接导入使用。
方案二:批量替换面板 JSON 里的 uid。导出的 JSON 是纯文本,用sed或jq改一遍再导入:
# 把旧 uid 全部替换成新 uid sed -i 's/im7_otuvz/newuid123/g' dashboard.json # 或者用 jq 递归处理所有 datasource 字段 jq '(.panels[]?.datasource.uid) |= "newuid123"' dashboard.json > fixed.json这个方法适合一次性迁移几十个面板的场景。注意jq那条只能处理顶层 panels 数组,嵌套的行面板、重复面板要用递归写法才彻底。
方案三:改用模板变量。在仪表盘设置里定义变量DS_PROMETHEUS,类型选 Datasource,然后所有面板的数据源都引用${DS_PROMETHEUS}。这样切换环境时只要在仪表盘顶部下拉框里选一次数据源,全盘生效:
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }这是我最推荐的做法,尤其是需要长期维护的仪表盘。代价是前期要把已有面板改一遍,但改完之后迁移再也不用操心 uid 的事。
提示:如果仪表盘里既有 Prometheus 又有 Loki 这类多数据源,就定义多个变量,命名上区分开,比如
DS_PROM_METRICS、DS_PROM_LOGS,别都叫DS_PROMETHEUS。
4. 面板复制与整盘迁移:三种粒度的实操对比
"Grafana 拷贝整个面板"这个需求出现的频率非常高,但"面板"这个词在不同语境下指的东西不一样。有人指的是单个图表,有人指的是整个 Dashboard,还有人想连文件夹结构一起搬。粒度不同,操作方式完全不同。
4.1 三种复制粒度与适用场景
| 粒度 | 操作入口 | 适用场景 | 需要注意 |
|---|---|---|---|
| 单个面板 | 面板标题 - Edit - Panel JSON | 复用某个现成的查询和样式 | 粘贴后必须改id和gridPos |
| 整个仪表盘 | 仪表盘设置 - JSON Model | 同环境内复制一份改改 | 名称重复会被拒绝,改title |
| 跨实例迁移 | 导出 JSON 或走 API | 测试到生产、旧环境到新环境 | 数据源 uid 和变量要对齐 |
| 文件夹批量迁移 | API + 脚本 | 几十个仪表盘整体搬迁 | 分页、限流、失败重试 |
单个面板的复制最容易出问题。在面板编辑页找到 "Panel JSON",复制整段 JSON,到新面板里粘贴。这里有个坑:JSON 里的id字段是面板在当前仪表盘内的编号,粘贴时如果原id已经存在,Grafana 可能会报错或行为异常。稳妥做法是粘贴前把id删掉或者改成null,让 Grafana 自己分配,同时把gridPos的x、y调到你想要的位置,否则新面板会叠在老面板上面。
4.2 用 API 做批量迁移
手动导出导入适合三五个面板,超过二十个就必须上脚本。Grafana 的 Dashboard API 很规整,读和写各一个接口:
# 读取:按 uid 拉取仪表盘 curl -s -H "Authorization: Bearer $SRC_TOKEN" \ "http://old-grafana:3000/api/dashboards/uid/abc123" | jq . > dash.json # 写入:注意 payload 要用 dashboard 字段包裹 curl -s -X POST -H "Authorization: Bearer $DST_TOKEN" \ -H "Content-Type: application/json" \ -d "$(jq '{dashboard: (.dashboard | del(.id) | .id = null), overwrite: false, folderUid: "target-folder"}' dash.json)" \ "http://new-grafana:3000/api/dashboards/db"几个实操细节值得记下来:
读取接口返回的是一个信封结构,真正的仪表盘内容在dashboard字段里,外层还有meta字段记录文件夹、版本、权限等信息。写回时如果直接把整个返回体丢过去会失败,必须重新包一层{ dashboard, folderUid, overwrite }。
overwrite设为false时,如果目标环境已存在同名仪表盘,接口会返回 412 错误;设为true则会覆盖。批量迁移我倾向先用false跑一遍,看哪些重名,人工处理完再跑第二遍。
请求头里的 Token 建议用 Service Account Token 而不是 API Key。API Key 是旧机制,Grafana 9 之后主推 Service Account,权限粒度更细,可以只给dashboards:read或dashboards:write,泄露了损失也有限。
批量迁移时还要注意限流和分页。搜索接口GET /api/search?type=dash-db&limit=100&page=1默认每页返回数量有限,几百个仪表盘要循环翻页。另外短时间内大量写入会让 Grafana 的 sqlite 数据库压力上来,脚本里加个sleep 0.2之类的间隔会稳很多。
4.3 迁移后必须检查的四件事
迁移不是导入成功就完了,下面四项每次都要过一遍:
第一,数据源引用。回到第 3 节讲的 uid 问题,导入后随便点开一个面板看有没有 "No data"。
第二,模板变量。有些仪表盘用了label_values()这类查询变量,变量依赖数据源返回的标签名。如果目标环境的数据源虽然通了但指标标签不一样,变量下拉框会是空的,整个面板的筛选就废了。检查方式是点开变量下拉框,看有没有正常列出选项。
第三,时间范围和刷新间隔。源环境的面板可能设了"最近 7 天"的时间范围,搬到新环境后,如果数据只保留 3 天,图表前半段就是空白。刷新间隔也一样,测试环境设了 5 秒自动刷新,生产环境几百个面板一起刷会把 Prometheus 打满。
第四,告警规则关联。Grafana 8 之后面板可以绑定告警规则,迁移时规则不会跟着走。遗漏了这点,你会以为监控还在,实际上告警早就断了。检查方式是进Alerting - Alert rules,看有没有规则引用已经不存在的仪表盘或面板。
5. 把告警接到 Alertmanager:分工、配置与自测
Grafana 上能看数据之后,下一步自然就是"出问题得有人知道"。告警这块最容易混乱的地方在于,Grafana 和 Prometheus 都能做告警,Alertmanager 又能被两边同时使用,搞不清谁在什么时候起作用。
5.1 Grafana 托管告警与 Prometheus 规则的分工
先给结论:如果已经在用 Prometheus,规则优先写在 Prometheus 侧。原因有三个。
规则文件和指标定义放在一起,版本管理简单,改一条规则提交一个 commit 就完事。Grafana 的告警规则存在数据库里,虽然也能通过 provisioning 文件管理,但多了一层抽象。Prometheus 的规则用promtool check rules就能做语法校验,CI 里加一步即可,Grafana 规则没有这么成熟的命令行校验工具。最后,Prometheus 规则可以被多个 Grafana 实例共同展示,而 Grafana 托管告警是跟实例绑定的。
Grafana 托管告警更适合这些场景:需要跨数据源做告警(比如 Prometheus 的指标加上 MySQL 的查询结果一起判断)、团队没有独立维护 Prometheus 的人、需要 Grafana 的多维告警能力(比如按标签动态生成告警实例)。
5.2 两边接 Alertmanager 的配置差异
Prometheus 侧的配置在prometheus.yml里加一段:
alerting: alertmanagers: - static_configs: - targets: - "alertmanager:9093" rule_files: - "/etc/prometheus/rules/*.yml"规则文件示例,一条 CPU 使用率过高的告警:
groups: - name: host-alerts rules: - alert: HighCpuUsage expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85 for: 10m labels: severity: warning team: infra annotations: summary: "实例 {{ $labels.instance }} CPU 持续高于 85%" description: "当前值 {{ $value | printf \"%.1f\" }}%,已持续 10 分钟。"for: 10m是关键参数,它表示表达式连续成立 10 分钟才真正触发告警。没有这个字段,指标瞬间抖动一下就会发通知,一晚上能把你手机震没电。
Grafana 侧接外部 Alertmanager,在Alerting - Alertmanager页面能看到当前配置的实例列表。通过 provisioning 文件配置更规范,放在provisioning/alerting/alertmanager.yml:
apiVersion: 1 alertmanagers: - name: external-am url: http://alertmanager:9093 timeout: 10s配好之后,Prometheus 规则触发的告警会出现在 Grafana 的Alerting - Active alerts页面里,来源标记为 Prometheus。这是很多人不知道的一个便利点:用 Grafana 统一看告警列表,比来回切两个界面舒服得多。
5.3 路由、分组、静默的实战参数
Alertmanager 的配置文件看起来复杂,核心就四块:
route: receiver: default-receiver group_by: ["alertname", "instance"] group_wait: 30s group_interval: 5m repeat_interval: 4h routes: - match: severity: critical receiver: oncall-receiver group_wait: 10s repeat_interval: 1h receivers: - name: default-receiver webhook_configs: - url: http://notice-bridge:8060/webhook - name: oncall-receiver webhook_configs: - url: http://notice-bridge:8060/webhook inhibit_rules: - source_match: severity: critical target_match: severity: warning equal: ["alertname", "instance"]参数含义逐个说清楚:
group_by决定哪些告警被合并成一条通知。按alertname和instance分组意味着同一台机器上的同类告警会打包发送,避免一次故障刷出几十条消息。
group_wait是首次通知前的等待时间。设成 30 秒的意思是,新告警产生后先等 30 秒,看有没有同类告警一起进来,凑一批再发。设太短会碎片化,设太长则通知延迟。
group_interval控制同一组告警有新成员加入时的发送间隔,通常设 5 分钟。
repeat_interval是重复提醒周期。设 4 小时的含义是,如果告警一直没恢复,每 4 小时再提醒一次。生产环境别设太短,我见过设 10 分钟的,值班同事直接把这个群免打扰了,效果适得其反。
inhibit_rules是抑制规则,上面这段的意思是:如果同一实例同一告警名已经产生了 critical 级别的告警,那么对应的 warning 级别告警就不发了。避免"大问题和小问题一起响",让值班的人聚焦在最严重的那条上。
提示:
inhibit_rules里equal字段的标签匹配是精确匹配,标签名写错了不会报错,只会静默失效。配完之后一定要构造真实场景验证,别只看配置语法没问题就上线。
5.4 告警链路的三段自测方法
配置完不验证,等于没配。我习惯把链路拆成三段分别测:
第一段,规则求值。打开 Prometheus 的/alerts页面,看规则是否处于inactive/pending/firing状态。如果表达式本身有问题,这里会直接显示错误信息。想快速验证一条规则能不能触发,可以临时把阈值改得极低(比如> 1),看状态是否立刻从 pending 变 firing,验证完再改回去。
第二段,告警推送。访问 Alertmanager 的/api/v2/alerts接口,看 firing 状态的告警有没有进来。这一步能确认 Prometheus 到 Alertmanager 的网络和配置是通的。
第三段,通知发送。用 Alertmanager 的amtool手动造一条告警:
amtool alert add alertname=TestAlert severity=critical instance=test-host \ --alertmanager.url=http://localhost:9093如果通知能正常收到,说明路由和接收端配置没问题。这是最省事的验证方式,不用真去制造一次故障。
三段都通了,才算是真正的"告警链路可用"。我在几个项目里都是按这个顺序排查,能省掉大量翻日志的时间。
6. 长期运行后的性能调优与运维习惯
搭起来容易,跑得久还稳当,是另一回事。Grafana 这类工具的问题通常不是突然爆发的,而是随着仪表盘数量、查询复杂度、数据保留周期慢慢累积出来的。
6.1 查询侧的几个低成本优化
用$__rate_interval替代硬编码的[5m]。Grafana 提供了这个内置变量,它会根据面板的时间范围和数据源的最小采集间隔自动算出一个合适的时间窗口。好处是在看 1 小时和看 7 天的时候,查询用的步长自动适配,既不会因为窗口太小导致数据点过密,也不会因为窗口太大丢掉细节。
开启 Max data points 限制。面板设置里的Max data points控制单个图表最多返回多少个数据点,默认是自动计算。如果一个面板横跨 30 天还想要秒级精度,Prometheus 得吐出几十万个点,浏览器直接卡死。手动限制在 500 到 1000 之间,形状基本不受影响,性能差别是数量级的。
把高频使用的复杂查询落成 recording rules。如果某个仪表盘每次加载都要现算一个涉及十几个标签聚合的表达式,把这部分提前在 Prometheus 里算好存成新指标,Grafana 直接查这个新指标。代价是占一点存储,换来的是面板加载从几秒降到几百毫秒。
关掉不必要的自动刷新。大屏展示类的仪表盘设 10 秒或者 30 秒刷新就够了,没必要跟 Prometheus 的采集间隔完全对齐。生产环境里几十个面板同时以 5 秒间隔查询,Prometheus 的查询压力会明显上升。
6.2 版本管理与备份的落地做法
Grafana 的仪表盘如果只存在数据库里,出问题时就只能靠备份文件恢复,看不到变更历史。我现在的做法是双轨并行:
第一条轨道是 provisioning。把长期维护的核心仪表盘导出成 JSON,放进 Git 仓库,通过provisioning/dashboards的 provider 配置自动加载:
apiVersion: 1 providers: - name: core-dashboards orgId: 1 folder: "Core" type: file disableDeletion: false updateIntervalSeconds: 30 options: path: /var/lib/grafana/dashboardsdisableDeletion: false这个设置要注意:它允许 Grafana 删除 provisioning 目录里已经不存在的仪表盘。如果你希望 Git 是唯一事实来源,设成false;如果允许有人直接在界面里改,设成true更安全,否则界面上的临时修改会被文件同步覆盖掉。
第二条轨道是定期备份grafana.db。这个文件在grafana-data卷的/var/lib/grafana目录下,用一个定时任务每天复制一份到对象存储或者另一台机器上。sqlite 数据库在运行中直接复制可能拿到不一致的快照,稳妥点用sqlite3 grafana.db ".backup /backup/grafana-$(date +%F).db"这种方式。
6.3 常见问题速查表
跑久了会遇到的典型问题,整理成表方便对照:
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
| 面板全空白,日志有 datasource not found | 数据源 uid 不匹配 | 固定 uid 或用模板变量 |
| 个别面板无数据,其他正常 | 该面板硬编码了旧 uid | 单独修改该面板 JSON |
| 变量下拉框为空 | 查询变量依赖的标签不存在 | 检查目标环境指标标签 |
| 导入仪表盘报 412 | 同名仪表盘已存在 | 改名或设置 overwrite |
| 告警不触发 | for时间过长或阈值过高 | 查/alerts页面的状态 |
| 告警只有一条没有后续 | repeat_interval过长 | 按业务调整到 1 到 4 小时 |
| 首页加载慢 | 单屏面板数量过多 | 拆分仪表盘,限制数据点 |
| 容器重启后配置丢失 | 数据卷未挂载或挂错 | 检查 volume 映射 |
最后分享一个我踩过的坑。有次迁移完之后一切正常,过了两周同事反馈"某个仪表盘少了几个面板"。查了半天才发现,Grafana 里有人直接在界面上编辑过那个仪表盘,但 provisioning 的updateIntervalSeconds到了之后,文件版本把它覆盖回去了。界面上的改动看着保存成功了,实际上撑不过 30 秒。这类问题的根源是"谁是权威数据源"这件事没说清楚——要么全走 Git,要么全走界面,混着用一定会打架。我现在统一按 Git 管核心资产、界面管临时探索的方式来做,并且明确告诉团队:改核心面板要提 MR,不要直接在界面上动。