先日、というかもう3ヶ月も経ってしまいましたが Grafana Meetup Japan #1 - connpass にてLTをさせて頂きました。
趣味のダッシュボード開発というテーマで、あわせてGrafanaのダッシュボードをコードで管理するソリューションの1つとして Grizzly GitHub - grafana/grizzly: A utility for managing Jsonnet dashboards against the Grafana API を紹介するお話をさせて頂いたのですが、結構な人数が参加されていたものの知ってる人が一人もいないぞ・・という結果になり、それもなんだかもったいない話だと思いました。
ということで今回はとりあえずGrizzly、Grafonnetがどういったもので、どうGetStartedしていくとよいか、といった話を改めてブログで書いていきます。
なお本記事で利用するツールのバージョンはこんな感じです、jsonnet-bundlerはdevってなんやねんという感じですが、Homebrewで入る最新のものを使ってます(※0.5.1でした)。
$grr --version grr version v0.4.3 $jb --version dev $jsonnet --version Jsonnet commandline interpreter (Go implementation) v0.20.0 リモートのGrafanaバージョン Grafana v10.4.2 (22809dea50)
いちおうサンプルコード一式はここにおいてます
GitHub - egmc/egmc-dashboard-blog: sample code for my blog post
Grafanaのダッシュボード表現とGrafonnet
GrafanaのダッシュボードはJSONで構造化されて表されます。 JSONなので、このままでもバージョン管理できるのですが、これをエディタで編集して変更管理する、というのはなかなかに大変です。 結局ダッシュボードの変更は画面上から行い、結果としてexporterされたJSONをバージョン管理するというのは変更とexport、コミットを繰り返すことになるので作業としてもやや冗長になります。
最終的にJSON表現となればよいのでJSONを生成するテンプレート言語を使ってより簡潔な記述を可能とするのがJsonnet(JSONテンプレート言語)とGrafonnet(JsonnetでGrafanaのダッシュボード表現を扱うためのライブラリ)です。
Jsonnet、Grafonnetのセットアップ
Jsonnet、Grafonnetのセットアップについては以下を参照
起点として以下のページからたどれますが
Jsonnetはc++とgoの実装がありますが公式の推奨どおりgo-jsonnetを使いましょう
Grafonnetを使うためにJsonnetバンドラー(Jsonnetのパッケージマネージャ)を入れます
最後に実際にダッシュボード管理を行うプロジェクトレポジトリでgrafonnetをインストールします
参考までにmacでHomebrewを使う場合は以下のような流れになります(細かい部分はそれぞれのドキュメント参照)
$ brew install go-jsonnet $ brew install jsonnet-bundler #以下プロジェクトフォルダにて $ jb init $ jb install github.com/grafana/grafonnet/gen/grafonnet-latest@main
Grizzlyを使う
GrizzlyはGrafanaダッシュボードなどを管理するためのcliツールです。
野良ではなく、GrafanaのOrg配下で開発されています・・が情報は正直少ないです。
ダッシュボード管理機能からスタートしたものの、現在はGrafanaの周辺ツール(Synthetic MonitoringやPrometheusのアラートルールなど)にも対応を広げているため、observability resourcesをコードで管理するためのcliツールという位置づけになっています。
Grafana Grizzly is a command line tool that allows you to manage your observability resources with code.
本記事ではGrafanaダッシュボードのみを取り扱います。
Grizzlyのセットアップ
プラットフォームごとのバイナリリリースがありますのでこれをそのまま落としてきて使います
Releases · grafana/grizzly · GitHub
grrコマンドが実行可能になったら、まずはリモートのGrafanaを扱うためのクレデンシャルをセットします(環境変数でも設定できますが、本記事では最初から利便性のためにconfig setを利用します)
Setup and Configuration | Grafana Grizzly Docs
を参考にまずはリモート環境に合わせたコンテキスト(環境名)を設定します
grr config create-context xxx
そしてクレデンシャル(GrafanaのURLとトークン)をセットします
grr config set grafana.url {リモートのGrafana URL} grr config set grafana.token {Grafanaのサービスアカウントに紐づいたトークン}
以降特に指定せずに現在のコンテキストに紐づいた設定が利用されます。
複数のGrafana環境を切り替える際は別名でコンテキストを作成して切り替えると良いでしょう。
Grizzlyを使ってJSON/形式でダッシュボードを作成する
サンプルダッシュボードを作っていきます。
本記事ではお題として拙作の GitHub - egmc/systemd_resolved_exporter を使ってsystemd_resolvedのキャッシュヒット率を出すグラフを作ってみます。
PromQL的にはこんな感じです
rate(systemd_resolved_cache_hits_total[$__rate_interval]) / (rate(systemd_resolved_cache_hits_total[$__rate_interval]) + rate(systemd_resolved_cache_misses_total[$__rate_interval]))
(単にこちらはサンプルなので、実際に動かす場合はすでに取得しているexporterの適当なメトリクスで同様のステップで試してみると良いでしょう)
単にGrafanaのUIを使ってダッシュボードを作成し、JSON表現(Grizzlyのデフォルトはyamlですが)を管理するだけであれば grr serve
が簡単です。
この機能は比較的最近追加されたものですが、なかなか動作的におもしろい機能になっています。
例としてこんなyamlファイルを用意し(metadataのnameとspecのuidは合わせる必要があります)
apiVersion: grizzly.grafana.com/v1alpha1 kind: Dashboard metadata: folder: general name: systemd_resolved_sample spec: title: systemd_resolved_sample uid: systemd_resolved_sample
systemd_resolved_sample.yamlとしてプロジェクト直下に保存します。
この時点では単にタイトルとuidだけが指定されているだけの空のダッシュボードです。
$ grr serve ./*.yaml -t dashboard
するとlocalhost:8080でおもむろにサーバが立ち上がり、アクセスするとこんな表示になり
systemd_resolved_sampleにアクセスすると空のダッシュボードが立ち上がっているので、Add visualizationで先ほどのクエリをもとにUI上で作業してグラフを足していきます
単にクエリを追加しただけだと0.xxといった値が折れ線グラフで表示されるだけなので、いくつかグラフをいじって見た目を整えます
- unitをpercent(0.0-1.0)に
- maxを1に(常に100%まで表示されるように)
- グラフタイトルを設定してLegendをsampleに
- 諸事情によりこのサンプルは60s間隔でscrapeしてるのでinteval=60に設定してます
グラフが出来上がったらそのままSave dashboardから保存します。 するとおもむろに手元のyamlが書き換わり、以下のようなものになります。
(データソースのIDなどは環境固有なので、各々環境で適宜読み替えてください)
apiVersion: grizzly.grafana.com/v1alpha1 kind: Dashboard metadata: folder: general name: systemd_resolved_sample spec: annotations: list: - builtIn: 1 datasource: type: grafana uid: -- Grafana -- enable: true hide: true iconColor: rgba(0, 211, 255, 1) name: Annotations & Alerts type: dashboard editable: true fiscalYearStartMonth: 0 graphTooltip: 0 links: [] panels: - datasource: type: prometheus uid: adk2jn9tqqiv4e fieldConfig: defaults: color: mode: palette-classic custom: axisBorderShow: false axisCenteredZero: false axisColorMode: text axisLabel: "" axisPlacement: auto barAlignment: 0 drawStyle: line fillOpacity: 0 gradientMode: none hideFrom: legend: false tooltip: false viz: false insertNulls: false lineInterpolation: linear lineWidth: 1 pointSize: 5 scaleDistribution: type: linear showPoints: auto spanNulls: false stacking: group: A mode: none thresholdsStyle: mode: "off" mappings: [] max: 1 thresholds: mode: absolute steps: - color: green value: null - color: red value: 80 unit: percentunit overrides: [] gridPos: h: 17 w: 24 x: 0 "y": 0 id: 1 options: legend: calcs: [] displayMode: list placement: bottom showLegend: true tooltip: mode: single sort: none targets: - datasource: type: prometheus uid: adk2jn9tqqiv4e editorMode: code expr: rate(systemd_resolved_cache_hits_total[$__rate_interval]) / (rate(systemd_resolved_cache_hits_total[$__rate_interval]) + rate(systemd_resolved_cache_misses_total[$__rate_interval])) instant: false interval: "60" legendFormat: sample range: true refId: A title: systemd resolved chache hit rate type: timeseries schemaVersion: 39 tags: [] templating: list: [] time: from: now-6h to: now timepicker: {} timezone: "" title: systemd_resolved_sample uid: systemd_resolved_sample version: 1 weekStart: ""
これでJSON Model(を内包したYAMLファイル)が残りましたので、このままバージョン管理を行うことができますし、再び grr serve
で編集を再開することができます。
grr serve
の動作としては、ローカルでGrafanaを立ち上げつつ、データソースとしては指定したクレデンシャルを利用してリモートのGrafanaのデータソースへリクエストをプロキシして利用するような形になっています。
リモートのデータソースを利用しつつ手元でトライアンドエラーを行ってダッシュボードをファイルで管理できる、といったあたりが利点になるかと思います。
一方、GrafanaのJSON Modelを生で扱うため抽象度は低く、明示的に指定したいプロパティ以外もすべて特定のスキーマバージョンに基づいて保存されてしまうため、記述内容そのものは冗長になりますし、バージョンをまたいだ環境間の行き来などは難しそうです(実際Grizzlyのpull/pushでGrafanaバージョンまたいでダッシュボード定義を移動しましたが割と動かないものがあったりしました)。
Grizzlyを使ってGrafonnetでダッシュボード管理を作成する
同様のダッシュボードを今度はGrafonnetで作成してみます。
今度は最初からコード管理を行いますので、新規に systemd_resolved_sample.jsonnet
を作成します。
local g = import 'github.com/grafana/grafonnet/gen/grafonnet-latest/main.libsonnet'; # dashboardを生成してpanelを追加する、必要な属性はGrafonnetが提供する関数を利用して生成し+結合で追加していく local dashboard = g.dashboard.new("systemd_resolved_sample_jsonnet") + g.dashboard.withUid('systemd_resolved_sample_jsonnet') + g.dashboard.withPanels([ g.panel.timeSeries.new('systemd-resolved cache hit rate') + g.panel.timeSeries.queryOptions.withTargets([ g.query.prometheus.new( 'prometheus', 'rate(systemd_resolved_cache_hits_total[$__rate_interval]) / (rate(systemd_resolved_cache_hits_total[$__rate_interval]) + rate(systemd_resolved_cache_misses_total[$__rate_interval]))', ) + g.query.prometheus.withInterval("60") + g.query.prometheus.withDatasource('prometheus') + g.query.prometheus.withLegendFormat("sample") ]) + g.panel.timeSeries.queryOptions.withDatasource('prometheus', 'adk2jn9tqqiv4e') + g.panel.timeSeries.standardOptions.withUnit('percentunit') + g.panel.timeSeries.standardOptions.withMax("1") + g.panel.timeSeries.panelOptions.withGridPos(17, 24, 0, 0) ]); # Grizzlyの作法に合わせてmetadataを付与し、specにdashboardのJSONを入れ込む { apiVersion: 'grizzly.grafana.com/v1alpha1', kind: 'Dashboard', metadata: { name: "systemd_resolved_sample_jsonnet", folder: "general" }, spec: dashboard }
$ grr apply ./systemd_resolved_sample.jsonnet -t dashboard
で反映します。
また、 grr watch
を使うことでファイルの変更を検知して(エラーがない限り)リモートのGrafanaへ即座に反映することもできます。
リモートに対してトライアンドエラーができる場合はこのやり方がおすすめです。
$ grr watch . dashboard -t dashboard
grr serve
ポチポチつくたものと同様のダッシュボードができました。
Jsonnetは作法を抑える必要はありますが、記述そのものは生のJSON Model表現より「実際の関心事」フォーカスにした内容になっています。
(本記事では簡単のためにダッシュボードのuidを環境に合わせて指定していますが、このあたりは実際は動的に設定することで環境固有の情報をなくせるでしょう)
スキーマバージョンについてはGrafonnetライブラリに依存しますが、コードそのものにそういった情報は含まれないため、ライブラリが追随してくれる限り将来のバージョンアップも比較的容易になるはずです。
まとめ
- Grizzlyを使うことでお手軽にGrafanaのDashboards as Codeをはじめることができます
grr serve
は手元でGrafanaそのものを使ってDashboardを作成するのに便利です- Grafonnetは作法を覚える必要はありますが、本格的にダッシュボードのコード管理を長期的に行う場合は有用な選択肢になります
・・・と一旦まとめた後の雑多な補足
- Grizzlyは実際便利だと思うんですが、まだまだバギーでコマンド体系は統一されておらず、ツールとしてははまりどころも多いです
- エラーも不親切でまだまだ発展途上な感は否めません
- ということで本記事の一つの目的としてはユーザー増やしてツールの環境がもっとよくなるいいなあ、などという意図もあったりします、バグレポでもPRでも
- Jsonnetでの記述でDashboardをspecに入れてGrizzlyのapiVersion、kind、metadataをいれるやり方ですが実際これ公式自体は公式ドキュメントにないので、これが意図したやり方なのか実は確認してません、しかしかつてのHidden Elementを利用した記述方法ももはやサポートされてなさそうなので、こうするしかないんじゃないかな・・と思ってます
- Grafonnetライブラリはかつて https://github.com/grafana/grafonnet-lib が使われていたのですがGrafana本体のアップデート、API変更に追随が難しくなり自動生成するアプローチになったのが現在の https://github.com/grafana/grafonnet レポジトリです。最新のAPIに追随してくれるのは良いのですが、特定のGrafanaバージョン、スキーマバージョンの指定みたいなのはできなさそうなので古いGrafanaバージョンを使っている場合はやや注意がいりそうです。タグで分けてくれたらいいんじゃないかと思うんですが・・。
- クレデンシャルを含むconfigのパスは
grr config path
で取得できます。手元のMacだと/Users/{ユーザー}/Library/Application Support/grizzly/settings.yaml
に設定されてました。
参考
公式系
- Grafana Grizzly Docs Grizzlyはここ、helpはあまり情報ないのでここを起点にみるといいと思います
- Jsonnet - Jsonnet Configuration Language
- Home - Grafonnet 現在のGrafonnetのサンプルとAPIはここを見る
その他
- Jsonnetの薦め #JSON - Qiita 元記事の作成時期は結構古いですがJsonnetについての概要を日本語で掴むのにおすすめです