4枚のダッシュボードをドロップダウン1つに畳む
目標
対象ごとにコピーされたダッシュボード4セットを自分で作って、直す箇所がいくつあるかを数えたあと、クエリ変数、複数値の選択、全選択、パネルのリピート、依存する変数で、それを1つのダッシュボードにまとめます。最後に、展開されるパネルの数を数えて上限を決め、古いコピー版をまとめたうえで、同じ答えが出ることを証明します。
なぜ重要なのか
ダッシュボードが壊れるいちばん一般的な道は、コピーです。コピーは、元のダッシュボードが直されても一緒には直されず、画面は正常に見えるので、誰もその事実に気づきません。変数は、そのコピーをドロップダウン1つに置き換えます。ただし、変数を入れる作業は、宣言1行では終わりません。複数の値を選べるようにした瞬間に、値が正規表現に展開されるので、クエリのマッチャーも一緒に変える必要があり、パネルのリピートをオンにすると、画面に描かれるパネルの数がオプションの数だけ掛け算されます。便利さとコストが同じつまみに付いているようなものなので、リピートを使うダッシュボードには、展開されるパネル数の上限も一緒に決めておく必要があります。このラボは、そのつまみを1つずつ回してみて、数字で確認します。
ステップ
lab-start-grafanaでGrafanaを起動し、/opt/lab/gfd/gfd-variables/copy-template.jsonの__UID__と__ROUTE__を書き換えながら、4セットをアップロードしてください。uidはgfd-vars-c1からgfd-vars-c4までで、__ROUTE__の部分には/api/orders、/api/search、/api/users、/healthzを1つずつ入れます。そして/root/gfd-variables/01-copies.txtに3行書いてください。dashboards=はタグがgfd-vars-copyのダッシュボードの数、panels_per_dashboard=はそのうち1セットのパネルの数、edit_sites=はクエリを1回直すときに手を入れる必要のある箇所の数(両者の積)です。- uidが
gfd-varsのダッシュボードを新しく作成してください。routeという名前のqueryタイプのテンプレート変数があり、その変数はlabel_values(http_requests_total, handler)で値を読み込みます。パネルは1つで、クエリはhandler="$route"で絞り込んだ5xx割合です。そして、その変数が実際に取得する値を、/root/gfd-variables/02-values.txtに1行に1つずつ書いてください。 route変数の複数値の選択(multi)と全選択(includeAll)をオンにして、パネルのクエリでハンドラーを選ぶマッチャーをhandler="$route"からhandler=~"$route"に変えてください。そして/root/gfd-variables/03-interp.txtに、route_all=で始まる1行を書いてください。4つの値がすべて選択されたときに、Prometheusのクエリの中で$routeの位置に入る文字列そのままです(括弧と縦棒を含みます)。- 5xx割合のパネルに
repeatを設定して、routeの選択された値ごとにパネルが1枚ずつできるようにしてください。repeatDirectionは横(h)、maxPerRowは2にします。パネルのタイトルには$routeを入れて、どのパスのグラフなのかがわかるようにします。 codeという2つ目のquery変数を追加してください。クエリはlabel_values(http_requests_total{handler=~"$route"}, status)で、refreshは、時間範囲が変わるときにも再読み込みする値(2)にします。templating.listで、routeがcodeより前にある必要があります。そして、$routeと$codeを両方とも使うパネルを1つと、変数を1つも使わない全体のリクエストレートのパネルを1つ追加して、パネルを3つにしてください。最後に/root/gfd-variables/05-order.txtに、order=で始まる行(変数名をtemplating.listの順にカンマでつないだもの)と、reason=で始まる行(順序をそうしなければならない理由、40文字以上)を書いてください。code変数でも複数値の選択(multi)をオンにして、$routeと$codeを一緒に使うパネルにrepeat: codeを設定し、リピートパネルを2つにしてください。そして/root/gfd-variables/06-cost.tsvに、変数ごとに1行ずつ、templating.listの順に、タブで区切った4列<변수이름> <옵션수> <그 변수로 반복되는 패널 수> <둘의 곱>を書いてください(プレースホルダーは順に、変数名、オプションの数、その変数でリピートされるパネルの数、その2つの積です)。続けて/root/gfd-variables/06-cap.txtに3行書きます。static_panels=はリピートが設定されていないパネルの数、expanded_total=は積の合計にその数を足した値、max_expanded=はこのダッシュボードに置くと決めた上限(今の値以上の整数)です。/opt/lab/gfd/gfd-variables/legacy.jsonは、同じパネルがハンドラーの値だけを変えて4回並んでいる古いダッシュボードです。これをuidがgfd-vars-newのダッシュボード1つにまとめて、アップロードしてください。条件は3つです。パネルはちょうど1つで、そのパネルは複数値のクエリ変数で繰り返され、クエリはその変数で絞り込まれます。変数はlabel_values(http_requests_total, handler)で値を読み込み、複数値の選択と全選択がオンになっている必要があります。/root/gfd-variables/08-proof.tsvに4行書いてください。各行は、タブで区切った2列<핸들러 값> <합친 패널의 쿼리에서 변수를 그 값으로 바꾼 PromQL>です(プレースホルダーは順に、ハンドラーの値、まとめたパネルのクエリで変数をその値に置き換えたPromQLです)。4行の1列目は、http_requests_totalのhandlerラベルの値4つで、2列目には変数記号($)が残っていてはいけません。採点ツールは、各行のクエリと、legacy.jsonの同じハンドラーのパネルのクエリを同じ瞬間に投げて、値が同じかどうかを見ます。
参考
- 作業ディレクトリは
/root/gfd-variablesです。材料は/opt/lab/gfd/gfd-variables/copy-template.json(コピーの元)と/opt/lab/gfd/gfd-variables/legacy.json(まとめるべき古いダッシュボード)で、2つのファイルを作ったスクリプトは/opt/lab/gfd/gfd-variables/make.shです。 - Grafanaは
lab-start-grafanaで起動します(20–40秒)。匿名のAdminなので、トークンなしでAPIを使え、ターミナルの上にあるWebプレビューの3000番ポートで画面を開けます。 - パネルの
datasourceを空にしておくと、既定のデータソース(Prometheus)を使います。uidが必要なら、curl -s http://127.0.0.1:3000/api/datasources | jq -r '.[0].uid'で得てください。Podごとに異なります。 - この環境で判定できないもの: パネルが画面に何枚描かれるか。リピートはブラウザーが描くときに起き、画像レンダラープラグインはありません。サーバーのダッシュボードJSONには、リピートされる前のパネル1枚しか入っておらず、変数の
optionsも空で返ってきます。オプションの数は、データソースに直接問い合わせて数えてください。 - よくある間違いは、
multiをオンにしたのに、マッチャーを=のままにすることです。値が1つのときはうまくいき、2つからは空のグラフになります。 - Variables · Add and manage variables · Variable syntax · Configure panel options (repeat) · Prometheus template variables
コピーがいくつあるかを数えるところから始める
lab-start-grafanaでGrafanaを起動し、/opt/lab/gfd/gfd-variables/copy-template.jsonの__UID__と__ROUTE__を書き換えながら、4セットをアップロードしてください。uidはgfd-vars-c1からgfd-vars-c4までで、__ROUTE__の部分には/api/orders、/api/search、/api/users、/healthzを1つずつ入れます。そして/root/gfd-variables/01-copies.txtに3行書いてください。dashboards=はタグがgfd-vars-copyのダッシュボードの数、panels_per_dashboard=はそのうち1セットのパネルの数、edit_sites=はクエリを1回直すときに手を入れる必要のある箇所の数(両者の積)です。
Grafanaの起動には、20–40秒かかります。curl -s http://127.0.0.1:3000/api/healthが"database": "ok"を返すまで待ってください。
元のファイルを1セット分に変えるには、文字列の置換で十分です。パスにスラッシュが入っているので、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'
3つ目の数字が、このラボがなくそうとしているものです。今は8か所ですが、対象が40個なら80か所です。
値の一覧をデータから読み込む変数
uidがgfd-varsのダッシュボードを新しく作成してください。routeという名前のqueryタイプのテンプレート変数があり、その変数はlabel_values(http_requests_total, handler)で値を読み込みます。パネルは1つで、クエリはhandler="$route"で絞り込んだ5xx割合です。そして、その変数が実際に取得する値を、/root/gfd-variables/02-values.txtに1行に1つずつ書いてください。
値を手で並べるcustomタイプは、5つ目のパスができる日に古くなります。データソースに問い合わせる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[]'
ダッシュボードは、Webプレビューでクリックして作ってもかまいませんし、/api/dashboards/dbでアップロードしてもかまいません。採点ツールが見るのは、Grafanaに上がった結果だけです。
複数の値を選んだ瞬間に、マッチャーが変わる
route変数の複数値の選択(multi)と全選択(includeAll)をオンにして、パネルのクエリでハンドラーを選ぶマッチャーをhandler="$route"からhandler=~"$route"に変えてください。そして/root/gfd-variables/03-interp.txtに、route_all=で始まる1行を書いてください。4つの値がすべて選択されたときに、Prometheusのクエリの中で$routeの位置に入る文字列そのままです(括弧と縦棒を含みます)。
複数の値を1つの文字列にする方法は、データソースが決めます。Prometheusは正規表現を使うデータソースなので、値が縦棒でつながれ、括弧で囲まれます。
そのため、マッチャーも一緒に変える必要があります。等号のマッチャーは、文字列がちょうど同じときにだけ合うので、展開された正規表現が入った瞬間に、どの時系列とも合わなくなり、空のグラフになります。値を1つだけ選ぶとうまくいくのに、2つ選んだ瞬間に空になってしまうグラフは、ほとんどいつもこれが理由です。
このラボの値には、正規表現の特殊文字がないので、エスケープは生じません。順序は、値の一覧の順序に従います。
パネルのリピート: 対象の数だけパネルができる
5xx割合のパネルにrepeatを設定して、routeの選択された値ごとにパネルが1枚ずつできるようにしてください。repeatDirectionは横(h)、maxPerRowは2にします。パネルのタイトルには$routeを入れて、どのパスのグラフなのかがわかるようにします。
リピートは、複数値の変数にだけ設定できます。前のステップでmultiをオンにしていなかった場合は、リピートする値が1つだけなので、パネルも1枚です。
ダッシュボードのJSONでは、パネルのオブジェクトに"repeat": "<변수이름>"を入れます(プレースホルダーは変数名です)。縦に展開するときはrepeatDirectionがvで、そのときはmaxPerRowが使われません。
リピートされたパネルの中では、変数がそのパネルの値1つに解決されます。そのため、タイトルに変数を入れておくと、4枚のタイトルがそれぞれ異なります。実際に何枚描かれるかは、Webプレビューで目で見てください。サーバーが返すJSONには、リピートされる前のパネル1枚しか入っていません。
変数が変数に依存するとき: 順序と更新
codeという2つ目のquery変数を追加してください。クエリはlabel_values(http_requests_total{handler=~"$route"}, status)で、refreshは、時間範囲が変わるときにも再読み込みする値(2)にします。templating.listで、routeがcodeより前にある必要があります。そして、$routeと$codeを両方とも使うパネルを1つと、変数を1つも使わない全体のリクエストレートのパネルを1つ追加して、パネルを3つにしてください。最後に/root/gfd-variables/05-order.txtに、order=で始まる行(変数名をtemplating.listの順にカンマでつないだもの)と、reason=で始まる行(順序をそうしなければならない理由、40文字以上)を書いてください。
2つ目の変数のクエリの中で1つ目の変数を使うと、Grafanaがその関係に気づき、前の値が変わったときに後ろの値を再読み込みします。そのためには、前のものが先に解決されている必要があります。配列の順序が、そのまま解決する順序です。
refreshは数字です。ダッシュボードを開くときだけ再読み込みする値と、時間範囲が変わるときにも再読み込みする値は、異なります。時間範囲によって候補が変わる変数を前者の値にしておくと、昨日を見に行った人が今日の一覧を見ることになります。
全体のリクエストレートのパネルは、次のステップで「繰り返されないパネル」として数えます。ハンドラーで絞り込まないでください。
展開されるパネルの数を数えて、上限を決める
code変数でも複数値の選択(multi)をオンにして、$routeと$codeを一緒に使うパネルにrepeat: codeを設定し、リピートパネルを2つにしてください。そして/root/gfd-variables/06-cost.tsvに、変数ごとに1行ずつ、templating.listの順に、タブで区切った4列<변수이름> <옵션수> <그 변수로 반복되는 패널 수> <둘의 곱>を書いてください(プレースホルダーは順に、変数名、オプションの数、その変数でリピートされるパネルの数、その2つの積です)。続けて/root/gfd-variables/06-cap.txtに3行書きます。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を使ってください。上限を決めることが、このステップの要点です。リピートは無料ではなく、オプションの数だけ掛け算されるコストです。
コピー版の4つのパネルを、変数1つにまとめる
/opt/lab/gfd/gfd-variables/legacy.jsonは、同じパネルがハンドラーの値だけを変えて4回並んでいる古いダッシュボードです。これをuidがgfd-vars-newのダッシュボード1つにまとめて、アップロードしてください。条件は3つです。パネルはちょうど1つで、そのパネルは複数値のクエリ変数で繰り返され、クエリはその変数で絞り込まれます。変数はlabel_values(http_requests_total, handler)で値を読み込み、複数値の選択と全選択がオンになっている必要があります。
4つのパネルのクエリを並べて見ると、違うところは1か所だけです。その1か所を変数に変えれば、パネル1つになります。
jq -r '.panels[] | .targets[0].expr' /opt/lab/gfd/gfd-variables/legacy.json
変数名は自由に付けてかまいません。採点ツールは、パネルのrepeatが指す名前を、そのダッシュボードのtemplating.listから探して確認します。マッチャーは複数の値を受け取れる必要があるので、正規表現のほうです。
まとめたパネルのクエリに変数を入れたままでは、promqで投げてみることはできません。変数を1つの値に置き換えてから投げてみると、元のパネルと同じ数字が出るかどうかを確認できます。
まとめたパネルが元の4つのパネルと同じ答えを出すかどうか
/root/gfd-variables/08-proof.tsvに4行書いてください。各行は、タブで区切った2列<핸들러 값> <합친 패널의 쿼리에서 변수를 그 값으로 바꾼 PromQL>です(プレースホルダーは順に、ハンドラーの値、まとめたパネルのクエリで変数をその値に置き換えたPromQLです)。4行の1列目は、http_requests_totalのhandlerラベルの値4つで、2列目には変数記号($)が残っていてはいけません。採点ツールは、各行のクエリと、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}のように波括弧を使った形で書いていたなら、その形も一緒に置き換える必要があります。
このステップが証明するのは、「まとめたのに同じ答えが出る」ということです。まとめる作業が値を変えてしまったなら、そのダッシュボードはまとめたものではなく、別のダッシュボードになったということです。