做一块只回答一个问题的看板
目标
启动真实 Grafana 并连接真实 Prometheus,从零创建一个回答单一问题的 dashboard。完成后将其固化为 provisioning 文件,使这个 Grafana 即使被删除,也能重新构建相同画面。
这是第一个带界面的实验。Grafana 在 http://127.0.0.1:3000 启动后,可通过 terminal 上方的网页预览按钮打开真实 Grafana 画面。panel 既可以在界面中点击创建,也可以通过 API 上传;评分器不关心创建方式,只检查 Grafana 中最终呈现的结果。
为什么重要
dashboard 不是图片,而是回答问题的工具。因此,本实验不按绘制 panel 的顺序进行,而遵循问题 → query → panel → alert → 文件的顺序。
最后一步尤其重要。点击创建的 dashboard 只存在于 Grafana database 中;database 一旦消失,dashboard 也随之消失,并且不会留下谁在何时修改了什么。实际工作中找不到“当时那个 dashboard”,几乎总是因为这个原因。
此 Pod 中包含虚拟服务 shop-api 的 12 小时指标。http_requests_total 有四个 handler label(/api/orders、/api/search、/api/users、/healthz),响应时间记录在 http_request_duration_seconds_bucket histogram 中。
步骤
- 在
/root/graf/provisioning/datasources/prometheus.yml中声明 data source,并在http://127.0.0.1:3000启动 Grafana。data source 的 uid 为labprom,地址为http://127.0.0.1:9090。 - 在
/root/graf/02-question.md中用一句话写出此 dashboard 要回答的问题,并在/root/graf/02-question.promql中用一行写出回答该问题的 PromQL。 - 创建并上传一个仅含该 query 的单 panel dashboard,uid 为
shop-api。 - 添加 p95 响应时间和当前请求率 panel。类型应符合问题的形态。
- 添加
handlertemplate variable,让所有 panel 都按该变量筛选。 - 在
/root/graf/runbook.md中编写 runbook:哪里坏了/先看什么/如何回滚。 - 在
/root/graf/provisioning/alerting/shop-api.yml中创建 alert rule,并指向 runbook。 - 将 dashboard 导出为 JSON,放到
/root/graf/dashboards/shop-api.json,并通过 provider 文件让 Grafana 读取该目录。
参考
- 可用
lab-status查看当前运行内容,也可用promq "<PromQL>"直接执行 query。 - 本实验不使用
lab-start-grafana。该 helper 已预先连接 data source,会使你无法练习第 1 步。若已启动,请先用pkill -x grafana停止。 - provisioning 配置文件在启动时读取。修改文件后画面无变化,通常是因为没有重新启动。
- Grafana 启动需要 20~40 秒。请等待
curl -s http://127.0.0.1:3000/api/health返回"database": "ok"。 - 再次启动 Grafana 时,请等待旧 process 释放 3000 端口。执行
pkill -x grafana后立即启动新进程,会因无法绑定端口而退出。
启动 Grafana,并通过文件连接 data source
在 /root/graf/provisioning/datasources/prometheus.yml 中声明 uid 为 labprom 的 prometheus data source,将该目录指定为 provisioning 路径,并在 http://127.0.0.1:3000 启动 Grafana。
如果在 UI 中点击添加 data source,它只会保留在 Grafana DB 中。评分器会检查 API 响应的 readOnly 是否为 true,这表示“来自文件”。
mkdir -p /root/graf/provisioning/datasources /root/graf/provisioning/dashboards \
/root/graf/provisioning/alerting /root/graf/dashboards \
/tmp/gf/data /tmp/gf/logs /tmp/gf/plugins
GF_PATHS_DATA=/tmp/gf/data GF_PATHS_LOGS=/tmp/gf/logs GF_PATHS_PLUGINS=/tmp/gf/plugins \
GF_PATHS_PROVISIONING=/root/graf/provisioning \
GF_SERVER_HTTP_PORT=3000 \
GF_AUTH_ANONYMOUS_ENABLED=true GF_AUTH_ANONYMOUS_ORG_ROLE=Admin \
GF_PLUGINS_PREINSTALL_DISABLED=true \
setsid nohup grafana server --homepath /opt/grafana >/var/log/grafana.log 2>&1 </dev/null &
curl -s http://127.0.0.1:3000/api/health # "database": "ok" 가 나올 때까지 20~40초
curl -s http://127.0.0.1:3000/api/datasources/uid/labprom/health
这组环境变量各有原因:把 data、log、plugin 路径转到 /tmp,是因为此 Pod 没有 capability,可能无法写入默认路径;启用 anonymous access,是为了通过网页预览打开时不先遇到登录页;禁用 plugin 预安装,是因为 Grafana 11.4 每次启动都会尝试从互联网下载 grafana-lokiexplore-app,而此 Pod 无法访问外网。
先写下 dashboard 要回答的问题
在 /root/graf/02-question.md 中写一句以问号结尾、由此 dashboard 回答的问题,并在 /root/graf/02-question.promql 中写出回答它的 PromQL。问题是“shop-api 响应中有多少百分比是 5xx?”。
要计算的不是数量,而是比例。流量翻倍时 5xx 数量也会翻倍,但用户遭遇失败的概率不变。
用 5xx 请求的每秒速率除以全部请求的每秒速率。使用 status label 和 rate(),窗口设为 [5m]。
promq 'sum(rate(http_requests_total[5m]))'
promq 'sum by (status) (rate(http_requests_total[5m]))'
promq "$(cat /root/graf/02-question.promql)"
评分器会把你写的 query 通过 Grafana data source 实际执行并检查结果。写法不同也没关系,只要数值正确即可。
创建回答问题的 panel
创建 uid 为 shop-api 的 dashboard,并加入一个绘制第 2 步 query 的 timeseries panel。dashboard 标题应能看出它回答的问题。
可以通过网页预览打开 Grafana,新建 dashboard 并添加 panel(此时在 dashboard 设置中将 uid 指定为 shop-api),也可以像下面这样通过 API 上传。
curl -s -XPOST -H 'Content-Type: application/json' \
-d @/tmp/dash.json http://127.0.0.1:3000/api/dashboards/db
/tmp/dash.json 的结构是 {"overwrite": true, "dashboard": { ... }}。
类型必须为 timeseries。incident 中需要的答案不是“现在是百分之几”,而是“从何时开始上升”;没有时间轴的类型无法回答。
可按如下方式检查上传结果。
curl -s http://127.0.0.1:3000/api/dashboards/uid/shop-api | jq '.dashboard.panels'
让 panel 类型符合问题形态
在同一 dashboard 中添加 p95 响应时间 panel 和当前请求率 panel,使 panel 总数变为三个。p95 使用 timeseries,当前请求率使用 stat。
p95 从 histogram 计算。le 是 bucket 边界,应只保留它并聚合其余 label。
promq 'histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m])))'
promq 'sum(rate(http_requests_total[5m]))'
当前请求率不要使用 gauge。gauge 用来显示数值在 0 与最大值之间的位置,但每秒请求数没有最大值。当前时刻的单个数字应使用 stat。
反之,若 p95 使用 stat 或 gauge,就无法看到“从何时开始变慢”。quantile 随时间如何变化才是关键。
让一个 dashboard 查看四个 handler
添加名为 handler 的 query 类型 template variable,并让三个 panel 的 query 都按该变量筛选。
手工列值的 custom variable 会在新增 handler 的那天过时。请从数据中读取。
label_values(http_requests_total, handler)
而且只声明 variable 不会产生任何效果。panel query 必须像这样筛选。
sum(rate(http_requests_total{handler="$handler"}[5m]))
评分器会把 variable 替换为真实值后执行 query。label 名称或引号错误会返回空结果并立即失败。
先写 runbook,再创建 alert
在 /root/graf/runbook.md 中编写 runbook。必须包含 ## 무엇이 깨졌나、## 먼저 볼 것、## 되돌리는 법 三节,并且“先看什么”中要有可实际执行的 http_requests_total query。
顺序如此安排是有原因的。若先创建 alert,就会变成“响了再想”,文档最终不会被写。先写 runbook,便能阻止那些无法回答“它响起时人现在该做什么”的 alert 被创建。
“先看什么”必须是命令,而不是句子。“检查状态”在凌晨 3 点毫无帮助。请直接写下统计哪个 handler 正在失败的 query。
创建 alert 并指向 runbook
在 /root/graf/provisioning/alerting/shop-api.yml 中创建 5xx 比例 alert rule。必须有 threshold 条件,for 至少 5 分钟,annotations.runbook_url 必须指向第 6 步的 runbook。写完文件后重新启动 Grafana。
for 是本步骤的核心。只要一个请求失败,5xx 就可能短暂升高;若每次都叫醒人,对方以后会忽略 alert。alert 不是因为关闭而死,而是因为被忽略而死。
alert query 不能使用 $handler 等 dashboard variable。alert 在没有画面的情况下求值,没有 dropdown 来展开 variable。
provisioning 配置在启动时读取。只写文件不会发生任何变化。
pkill -x grafana; sleep 3
# (1단계와 같은 환경변수로 다시 띄운다)
curl -s http://127.0.0.1:3000/api/v1/provisioning/alert-rules | jq '.[].title'
也可使用管理员账户重新加载(anonymous access 会返回 403)。
curl -XPOST -u admin:admin http://127.0.0.1:3000/api/admin/provisioning/alerting/reload
将 dashboard 固化为文件
把当前画面中的 dashboard 导出为 JSON,保存到 /root/graf/dashboards/shop-api.json;通过 /root/graf/provisioning/dashboards/lab.yml 让 Grafana 读取该目录,然后重新启动。评分器会检查 meta.provisioned 是否为 true。
dashboard JSON 与 provider 文件并不相同。provider 文件不包含 dashboard,而是指示应该读取哪个目录。
curl -s http://127.0.0.1:3000/api/dashboards/uid/shop-api \
| jq '.dashboard' > /root/graf/dashboards/shop-api.json
pkill -x grafana; sleep 3
# (1단계와 같은 환경변수로 다시 띄운다)
curl -s http://127.0.0.1:3000/api/dashboards/uid/shop-api | jq '.meta.provisioned'
若 meta.provisioned 为 true,表示当前画面来自文件。此后即使尝试用 API 覆盖,Grafana 也会以 Cannot save provisioned dashboard 拒绝,从而阻止画面与文件发生偏差。