TT Lab
开始
学习 学习路径 课程

Grafana — 仪表盘是一个问题

把四份仪表板折叠进一个下拉框

在 TT Lab 中继续学习

目标

亲手做出为每个对象复制的四份仪表板,数一数需要修改的地方有几处,然后用查询变量、多选、全选、面板重复和依赖变量,把它折叠成一个仪表板。最后数出展开后的面板数量并确定上限,合并旧的副本,并证明得到相同的答案。

为什么重要

仪表板最常见的毁坏途径是复制。原件被修改时,副本不会随之修改,而界面完好无损,没有人知道这件事。变量把这种复制变成一个下拉框。不过,加入变量并不是一行声明就能结束的事。让它可以选择多个值的那一刻,值会展开成正则表达式,所以查询的匹配器也必须一起改变;而一旦打开面板重复,界面上画出的面板数量就会按选项数相乘。便利和成本挂在同一个旋钮上,所以使用重复的仪表板,必须同时确定展开后面板数量的上限。本实验会把这个旋钮各转一遍,并用数字确认。

步骤

  1. 用 lab-start-grafana 启动 Grafana,把 /opt/lab/gfd/gfd-variables/copy-template.json 中的 __UID__ 和 __ROUTE__ 逐个替换,上传四份。uid 从 gfd-vars-c1 到 gfd-vars-c4,__ROUTE__ 的位置依次放入 /api/orders、/api/search、/api/users、/healthz。然后在 /root/gfd-variables/01-copies.txt 中写三行——dashboards= 是标签为 gfd-vars-copy 的仪表板数量,panels_per_dashboard= 是其中一份的面板数量,edit_sites= 是把查询改一次时需要动手的地方的数量(两者相乘)。
  2. 新建 uid 为 gfd-vars 的仪表板。必须有一个名为 route 的 query 类型模板变量,该变量用 label_values(http_requests_total, handler) 读取值。面板只有一个,查询是用 handler="$route" 收窄的 5xx 比率。然后把该变量实际取得的值,每行一个地写入 /root/gfd-variables/02-values.txt。
  3. 打开 route 变量的多选(multi)和全选(includeAll),并把面板查询中选择处理器的匹配器从 handler="$route" 改成 handler=~"$route"。然后在 /root/gfd-variables/03-interp.txt 中写以 route_all= 开头的一行——四个值全部选中时,Prometheus 查询里放在 $route 位置上的字符串原样(包含括号和竖线)。
  4. 给 5xx 比率面板设置 repeat,让 route 每个选中的值各生成一个面板。repeatDirection 设为横向(h),maxPerRow 设为 2。面板标题中必须有 $route,这样才能知道是哪个路径的图。
  5. 再添加一个名为 code 的第二个 query 变量。查询是 label_values(http_requests_total{handler=~"$route"}, status),refresh 设为时间范围改变时也重新读取的值(2)。templating.list 中 route 必须在 code 之前。然后再添加一个同时使用 $route 和 $code 的面板,以及一个完全不使用变量的总请求率面板,使面板成为三个。最后在 /root/gfd-variables/05-order.txt 中写以 order= 开头的一行(把变量名称按 templating.list 的顺序用逗号连接起来),以及以 reason= 开头的一行(必须这样安排顺序的理由,不少于 40 个字符)。
  6. 也给 code 变量打开多选(multi),并给同时使用 $route 和 $code 的面板设置 repeat: code,使重复面板成为两个。然后在 /root/gfd-variables/06-cost.tsv 中,每个变量写一行,按 templating.list 的顺序,用制表符分成四列 <변수이름> <옵션수> <그 변수로 반복되는 패널 수> <둘의 곱>(占位符依次为变量名称、选项数、按该变量重复的面板数、两者的乘积)。接着在 /root/gfd-variables/06-cap.txt 中写三行——static_panels= 是没有设置重复的面板数,expanded_total= 是乘积之和再加上该数的值,max_expanded= 是为这个仪表板确定的上限(不小于当前值的整数)。
  7. /opt/lab/gfd/gfd-variables/legacy.json 是一个旧仪表板,同一个面板只改了处理器的值,重复粘贴了四次。把它合并成 uid 为 gfd-vars-new 的一个仪表板并上传。条件有三个——面板恰好一个,该面板按多值查询变量重复,并且查询用该变量收窄。变量用 label_values(http_requests_total, handler) 读取值,并且必须开启多选和全选。
  8. 在 /root/gfd-variables/08-proof.tsv 中写四行。每行用制表符分成两列 <핸들러 값> <합친 패널의 쿼리에서 변수를 그 값으로 바꾼 PromQL>(占位符依次为处理器的值、把合并后面板的查询中的变量换成该值后的 PromQL)。四行的第一列是 http_requests_total 的 handler 标签的四个值,第二列中不能残留变量符号($)。评分器会把每一行的查询与 legacy.json 中同一个处理器的面板查询在同一时刻发出,看值是否相同。

参考

先数一数有几份副本

用 lab-start-grafana 启动 Grafana,把 /opt/lab/gfd/gfd-variables/copy-template.json 中的 __UID__ 和 __ROUTE__ 逐个替换,上传四份。uid 从 gfd-vars-c1 到 gfd-vars-c4,__ROUTE__ 的位置依次放入 /api/orders、/api/search、/api/users、/healthz。然后在 /root/gfd-variables/01-copies.txt 中写三行——dashboards= 是标签为 gfd-vars-copy 的仪表板数量,panels_per_dashboard= 是其中一份的面板数量,edit_sites= 是把查询改一次时需要动手的地方的数量(两者相乘)。

启动 Grafana 需要 20–40 秒。请等到 curl -s http://127.0.0.1:3000/api/health 返回 "database": "ok" 为止。

把模板变成一份,只需字符串替换。路径中带有斜杠,所以把 sed 的分隔符换成 # 之类的其他字符会更方便。

sed 's#__ROUTE__#/api/orders#g; s#__UID__#gfd-vars-c1#g' /opt/lab/gfd/gfd-variables/copy-template.json > /tmp/c1.json
jq -n --slurpfile d /tmp/c1.json '{dashboard: $d[0], overwrite: true}' \
  | curl -s -X POST -H 'Content-Type: application/json' -d @- http://127.0.0.1:3000/api/dashboards/db
curl -sG http://127.0.0.1:3000/api/search --data-urlencode 'tag=gfd-vars-copy' | jq 'length'

第三个数字就是本实验想要消除的东西。现在是八处,但如果对象有四十个,就是八十处。

从数据中读取值列表的变量

新建 uid 为 gfd-vars 的仪表板。必须有一个名为 route 的 query 类型模板变量,该变量用 label_values(http_requests_total, handler) 读取值。面板只有一个,查询是用 handler="$route" 收窄的 5xx 比率。然后把该变量实际取得的值,每行一个地写入 /root/gfd-variables/02-values.txt。

手工列出值的 custom 类型,会在出现第五个路径的那天变旧。必须是向数据源询问的 query 类型。

变量实际取得了什么,直接向数据源询问就可以。值列表不会保存在仪表板 JSON 中。

DS=$(curl -s http://127.0.0.1:3000/api/datasources | jq -r 'map(select(.type=="prometheus")) | .[0].uid')
curl -sG "http://127.0.0.1:3000/api/datasources/proxy/uid/$DS/api/v1/label/handler/values" \
  --data-urlencode 'match[]=http_requests_total' | jq -r '.data[]'

仪表板可以在网页预览中通过点击创建,也可以通过 /api/dashboards/db 上传。评分器只看已经上传到 Grafana 的结果。

一选多个值,匹配器就要改变

打开 route 变量的多选(multi)和全选(includeAll),并把面板查询中选择处理器的匹配器从 handler="$route" 改成 handler=~"$route"。然后在 /root/gfd-variables/03-interp.txt 中写以 route_all= 开头的一行——四个值全部选中时,Prometheus 查询里放在 $route 位置上的字符串原样(包含括号和竖线)。

把多个值变成一个字符串的方式由数据源决定。Prometheus 是使用正则表达式的数据源,所以值会用竖线连接,并用括号括起来。

因此匹配器也必须一起改变。等号匹配器只有在字符串完全相等时才匹配,所以一旦放入展开后的正则,就不会与任何时间序列匹配,成为空图表。只选一个值时正常、选两个值的那一刻就变空的图表,几乎总是因为这个原因。

本实验中的值没有正则特殊字符,所以不会产生转义。顺序遵循值列表的顺序。

面板重复——面板数量与对象数量相同

给 5xx 比率面板设置 repeat,让 route 每个选中的值各生成一个面板。repeatDirection 设为横向(h),maxPerRow 设为 2。面板标题中必须有 $route,这样才能知道是哪个路径的图。

重复只对多值变量起作用。如果前一步没有开启 multi,要重复的值只有一个,面板也就只有一个。

在仪表板 JSON 中,把 "repeat": "<변수이름>"(占位符为变量名称)放入面板对象。纵向展开时,repeatDirection 是 v,此时不使用 maxPerRow。

在重复出来的面板里,变量会被解析为该面板的那一个值。所以如果在标题中放入变量,四个面板的标题就会各不相同。实际画出了几个,请通过网页预览用眼睛看——服务器返回的 JSON 中只有重复之前的一个面板。

变量依赖变量时——顺序与刷新

再添加一个名为 code 的第二个 query 变量。查询是 label_values(http_requests_total{handler=~"$route"}, status),refresh 设为时间范围改变时也重新读取的值(2)。templating.list 中 route 必须在 code 之前。然后再添加一个同时使用 $route 和 $code 的面板,以及一个完全不使用变量的总请求率面板,使面板成为三个。最后在 /root/gfd-variables/05-order.txt 中写以 order= 开头的一行(把变量名称按 templating.list 的顺序用逗号连接起来),以及以 reason= 开头的一行(必须这样安排顺序的理由,不少于 40 个字符)。

在第二个变量的查询中使用第一个变量,Grafana 就会察觉这种关联,并在前一个值改变时重新读取后一个值。要做到这一点,前一个必须先被解析——数组的顺序就是解析的顺序。

refresh 是数字。只在打开仪表板时重新读取的值,和时间范围改变时也重新读取的值是不同的。如果把候选项会随时间范围变化的变量设成前一种,去看昨天的人就会看到今天的列表。

总请求率面板在下一步中会被算作“不重复的面板”。不要用处理器来收窄它。

数出展开后的面板数量并确定上限

也给 code 变量打开多选(multi),并给同时使用 $route 和 $code 的面板设置 repeat: code,使重复面板成为两个。然后在 /root/gfd-variables/06-cost.tsv 中,每个变量写一行,按 templating.list 的顺序,用制表符分成四列 <변수이름> <옵션수> <그 변수로 반복되는 패널 수> <둘의 곱>(占位符依次为变量名称、选项数、按该变量重复的面板数、两者的乘积)。接着在 /root/gfd-variables/06-cap.txt 中写三行——static_panels= 是没有设置重复的面板数,expanded_total= 是乘积之和再加上该数的值,max_expanded= 是为这个仪表板确定的上限(不小于当前值的整数)。

选项数不在仪表板 JSON 中。服务器会把变量的 options 清空后返回,所以必须直接向数据源询问来数。route 是 handler 标签的值的数量,code 是 status 标签的值的数量。

curl -sG "http://127.0.0.1:3000/api/datasources/proxy/uid/$DS/api/v1/label/status/values" \
  --data-urlencode 'match[]=http_requests_total' | jq '.data | length'

制表符必须是真正的制表符字符。请使用 printf 的 \t,或者 jq -r 的 @tsv。确定上限就是这一步的要点——重复不是免费的,而是按选项数相乘的成本。

把副本的四个面板用一个变量合并

/opt/lab/gfd/gfd-variables/legacy.json 是一个旧仪表板,同一个面板只改了处理器的值,重复粘贴了四次。把它合并成 uid 为 gfd-vars-new 的一个仪表板并上传。条件有三个——面板恰好一个,该面板按多值查询变量重复,并且查询用该变量收窄。变量用 label_values(http_requests_total, handler) 读取值,并且必须开启多选和全选。

把四个面板的查询并排放在一起看,不同的地方只有一处。把这一处换成变量,就成了一个面板。

jq -r '.panels[] | .targets[0].expr' /opt/lab/gfd/gfd-variables/legacy.json

变量名称可以随意起。评分器会在该仪表板的 templating.list 中查找面板的 repeat 所指向的名称并确认。匹配器必须能接收多个值,所以是正则那一边。

合并后面板的查询中带着变量时,无法用 promq 发出。把变量换成一个值再发出,就可以确认是否得到与原面板相同的数字。

合并后的面板是否给出与原来四个面板相同的答案

在 /root/gfd-variables/08-proof.tsv 中写四行。每行用制表符分成两列 <핸들러 값> <합친 패널의 쿼리에서 변수를 그 값으로 바꾼 PromQL>(占位符依次为处理器的值、把合并后面板的查询中的变量换成该值后的 PromQL)。四行的第一列是 http_requests_total 的 handler 标签的四个值,第二列中不能残留变量符号($)。评分器会把每一行的查询与 legacy.json 中同一个处理器的面板查询在同一时刻发出,看值是否相同。

合并后面板的查询,从 Grafana 中取出即可。

curl -s http://127.0.0.1:3000/api/dashboards/uid/gfd-vars-new \
  | jq -r '.dashboard.panels[0].targets[0].expr'

把变量符号换成值,是字符串替换。如果写成了 ${target} 这样带花括号的形式,那种形式也要一起替换。

这一步所证明的是“合并之后得到相同的答案”。如果合并改变了值,那么这个仪表板就不是合并,而是成了另一个仪表板。