Help us understand the problem. What is going on with this article?

Dashboards-as-Code: Grafanaダッシュボードをコードから生成する

Grafanaのダッシュボードをコードから生成するDashboards-as-Codeを試してみたので紹介します。

背景

Grafanaはとても便利な可視化ツールですが、ダッシュボードが増えてくるといくつか使いづらい点が出てきます。
最近は特に以下の2点を課題に感じています。

課題1: ダッシュボードのJSONのレビューがつらい...

GrafanaのダッシュボードはJSON形式でエクスポート/インポートできるので、ダッシュボードをJSONファイルでgit管理する場合が多いと思います。
この場合、GrafanaのUIで作成したダッシュボードをJSON形式でエクスポートしてプルリクエストするのですが、GrafanaのJSONはメタデータが多くて人間が読むのは正直厳しいです。
そのためJSONではなく実際のダッシュボードの画面だけみてレビューする場合がほとんどではないかと思います。

課題2: 似たようなグラフを複数作成するのが面倒...

Grafanaのダッシュボードは基本的にUI上で編集していくことになります。
HTTPリクエストのRPSやキューの長さなど、メトリクス名が少し違うだけで同じようなグラフを作成する時に一つずつタイトルやメトリクス名をUI上で編集していくことになるので非常に面倒です。
GrafanaのVariables機能で値を切り替えて何とかなる場合もありますが、一覧して見たい時などはやはり複製して編集していくしかありません。

これらの課題を解決できそうなのがDashboards-as-Codeです。
Dashboards-as-Codeは、ダッシュボードのJSONをプログラムで生成するというアプローチで、これを実現するためのOSSが複数公開されています。
今回はGrafanaCon EU 2018で紹介されていた以下の2つ1を検証してみました。

作成するダッシュボード

ダッシュボードのサンプルとして、Kubernetes & Prometheusの環境で、kube-state-metricskube_{k8sリソース名}_createdというメトリクスをnamespaceラベルごとにカウントしたグラフを各k8sリソースごとに作ってみます。
{k8sリソース名}の部分には、pod、service等のリソース名が入ります。
UIで作成する場合は、リソースごとに複製して{k8sリソース名}部分を修正していく感じになります。

grafanalib

https://github.com/weaveworks/grafanalib

grafanalibはWeaveworksがOSSとして公開しているPython製のライブラリです。

インストール

pipでインストールすると、generate-dashboardコマンドが使えるようになります。

$ pip install grafanalib

コーディング

Pythonで記述します。
今回はKubernetesのリソース名を与えるとそのグラフを返すようなfunctionを用意しました。
APIが直感的なのでPythonを普段使わない人でもすぐに書けると思います。

kube-state-metrics.dashboard.py
from grafanalib.core import *


def resource_row(resource):
    return Row(panels=[
        Graph(
            title='Number of %s by namespace' % resource,
            dataSource='prometheus',
            targets=[
                Target(
                    expr='count(kube_%s_created) by (namespace)' % resource,
                    legendFormat='{{namespace}}',
                ),
            ],
            yAxes=[
                YAxis(format=NO_FORMAT),
                YAxis(format=SHORT_FORMAT),
            ]
        )
    ])


dashboard = Dashboard(
    title="grafanalib: Kubernetes resource count",
    rows=[
        resource_row('deployment'),
        resource_row('daemonset'),
        resource_row('job'),
        resource_row('cronjob'),
        resource_row('pod'),
        resource_row('configmap'),
        resource_row('secret'),
        resource_row('service'),
        resource_row('endpoint'),
    ],
).auto_panel_ids()

これをUIで作ろうとすると、リソースの種類の数だけグラフを複製してメトリクス名とタイトルを修正するという地味な作業繰り返すことになるので、かなり楽になっているのがわかると思います。

JSON生成

generate-dashboardコマンドで、PythonコードからJSONを生成します。

$ generate-dashboard -o kube-state-metrics.json kube-state-metrics.dashboard.py

36行のコードから1008行のJSONが生成されました。

$ wc -l kube-state-metrics.dashboard.py
      36 kube-state-metrics.dashboard.py
$ wc -l kube-state-metrics.json
    1008 kube-state-metrics.json

生成されたJSONをインポートするとこんな感じのグラフになります。

スクリーンショット 2018-12-17 18.28.36.png

感想

実際の運用では、*.pyをgitでバージョン管理して、CI/CDでJSONを生成、生成したJSONをインポート or プロビジョニングしてコンテナイメージ作成、という流れになると思います。
レビュー対象が1008行のJSONから36行のPythonコードになるので、レビューのしやすさという観点では劇的に改善されそうです。

気になったのは、生成されたJSONが"schemaVersion": 12となっていて少し古い点です。
今後のGrafanaのアップデートがどのくらいのペースでサポートされていくのかが懸念です。

grafonnet

https://github.com/grafana/grafonnet-lib

grafonnetは、JSONを生成するための言語であるJsonnetのGrafana用ライブラリです。
生成コードはJsonnetで書きます。

インストール

まずJsonnetをインストールします。

$ brew install jsonnet

次にgrafonnetのライブラリをcloneしておきます。

$ git clone git@github.com:grafana/grafonnet-lib.git

Jsonnetコードの先頭でimportし、

local grafana = import 'grafonnet/grafana.libsonnet';

jsonnetコマンド実行時にcloneしたgrafonnetのパスを指定すると利用できます。

jsonnet -J /path/to/grafonnet dashboard.jsonnet

コーディング

grafanalibの時とほぼ同じコードを書きました。

kube-state-metrics.jsonnet
local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local graphPanel = grafana.graphPanel;
local prometheus = grafana.prometheus;

local resourcePanel(resource) =
  graphPanel.new(
    title='Number of %s count by namespace' % resource,
    datasource='prometheus',
  ).addTarget(
    prometheus.target(
      expr='count(kube_%s_created) by (namespace)' % resource,
      legendFormat='{{namespace}}',
    )
  );

local gridPos = {
  x: 0,
  y: 0,
  w: 24,
  h: 8,
};

dashboard.new(
  'grafonnet: Kubernetes resource count',
)
.addPanel(resourcePanel('deployment'), gridPos)
.addPanel(resourcePanel('daemonset'), gridPos)
.addPanel(resourcePanel('job'), gridPos)
.addPanel(resourcePanel('cronjob'), gridPos)
.addPanel(resourcePanel('pod'), gridPos)
.addPanel(resourcePanel('configmap'), gridPos)
.addPanel(resourcePanel('secret'), gridPos)
.addPanel(resourcePanel('service'), gridPos)
.addPanel(resourcePanel('endpoint'), gridPos)

Jsonnetを初めて使用したのとエラーメッセージが結構わかりづらいので、grafanalibよりは導入ハードルが高いと感じました。
https://jsonnet.org/ を読んで最低限のシンタックスを覚えてから書くのがよいと思います。
Vimのプラグインがあったので、シンタックスハイライトや保存時の自動フォーマットができて助かりました。
https://github.com/google/vim-jsonnet

JSON生成

jsonnetコマンドをgrafonnetのパスを指定して実行します。

$ jsonnet -J /path/to/grafonnet kube-state-metrics.jsonnet -o grafonnet-kube-state-metrics.json

35行のコードから800行のJSONが生成されました。

% wc -l kube-state-metrics.jsonnet
      35 kube-state-metrics.jsonnet
$ wc -l grafonnet-kube-state-metrics.json
     800 grafonnet-kube-state-metrics.json

書き方にもよりますが、コード量はgrafanalibと同じくらいになると思います。
生成されたJSONをインポートするとこんな感じのグラフになります。

スクリーンショット 2018-12-17 18.29.04.png

感想

Jsonnetを使っている方は少ないと思うのでPythonで書くgrafanalibに比べると導入ハードルが高いと思います。最低限のシンタックスを調べれば、ダッシュボードのJSON生成程度であれば何とかなりそうです。
あとはJsonnetを調べるうちに他の言語にはない特徴があることに気づきました。実はJsonnetはJSONのスーパーセットです。つまり既存のダッシュボードのJSONは全てJsonnetとしても解釈可能ということです。
この特徴は、既存ダッシュボードの移行という観点では大きなメリットになると思います。

# JSONをそのままJsonnetとして利用可能
$ cp a.json a.jsonnet
$ jsonnet -J /path/to/grafonnet a.jsonnet -o a-generated.json
# 生成されるJSONは同じ
$ diff a-generated.json a.json
$ diff a-generated.json a.jsonnet
# 拡張子は.jsonnetじゃなくてもOK
$ jsonnet -J path/to/grafonnet a.json -o a-generated.json

上記の例からもわかるように、既存のJSONファイルをそのまま流用してJsonnetを使用したCI/CDにすぐに移行できるということになります。
まずはJsonnetからJSONを生成するCI/CDパイプラインを整備して、その後にリファクタリングしてダッシュボードの似たような部分を共通化する、というアプローチができそうです。

...
local resourcePanel(resource) = graphPanel.new(                                                   
...
{                                                                                  
...
  panels: [                                                                        
    {                                                                              
      aliasColors: {},                                                             
      bars: false,                                                                 
      dashLength: 10,                                                              
      ...
    },
    resourcePanel('cronjob'),      # 元のJSONの一部分だけ共通化した生成コードに置き換え
    resourcePanel('statefulset'),
    ...
    {                                                                              
      aliasColors: {},                                                             
      bars: false,
      ...
    },
...
}        

実際にダッシュボードのJSONの一部分だけをgrafonnetで生成したJSONに置き換えるという使い方ができました。

まとめ

Dashboards-as-Codeの2つのライブラリを検証してみました。
最初に触った感じはPythonで書けるgrafanalibの方が導入しやすいと思ったのですが、grafonnet(というかJsonnet)にはJSONをそのまま使用できるというメリットがあるので、grafonnetの方が既存プロジェクトには導入しやすいのではないかと思います。
今後もGrafanaのJSONスキーマが変更されることはもちろんあると思うので、その時にgrafonnetであれば最新のJSONをとりあえずそのまま使用できます。
Python等の他の言語で生成する場合は、ライブラリが最新のGrafanaのJSONスキーマに対応しない限り、最新の機能を使えないということになるので、Jsonnetを利用しているgrafonnetはこの点でかなり有利だと思いました。


  1. 2セッションともYouTubeで動画が公開されています。grafanalibの動画grafonnetの動画 

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away