Jsonnet/Monitoring MixinsでPrometheus Rule, Grafana Dashboardを管理する

  #monitoring #prometheus #grafana #jsonnet

概要

Prometheus, Grafanaの監視基盤で遭遇する、Rule, Dashboardをどうやって管理しようかという話。
Raw Yaml/Json, Jsonnet, k8s CRDなど各社方法は様々だが、今回はJsonnetでの管理をより拡張したMonitoring Mixinsに焦点を当てる。

Monitoring Mixins

Monitoring MixinsはPrometheus rule, Grafana dashboardといった監視設定のPackagingを目指すプロジェクト。
ExporterのMetricsだけではなく、それを使って何を観測し可視化するかという知見を共有するのが目的。Out-of-the-boxでいい感じのアラート設定とダッシュボードがほしい、そんな需要に答える。コードはJsonnetとパッケージングツールのjsonnet-bundlerで管理される。
Mixins自体はyaml, jsonの管理までがスコープで、デプロイ方法はスコープ外としている。

有名所としてはKubernetes-mixinがある。kube-prometheusを使っていると入ってくるアレの本家。
またExporter repositoryにしれっと存在していたりもする。

Mixinsの課題

Mixinsの課題は、自分でちょっと凝ったことをしようとしたときにworkflowが整備されていないこと。
凝ったことというのは、既存のMixinsの値を上書きしたり、自前でMixinsを書いたりすること。
kube-promtheusを利用する上で間接的にお世話になってるユーザは多いかもしれないが、そこまで直接的なユーザがいないんじゃないかという懸念がちょっとだけある。

というのも本家の"Generate config files"では、mixinからyaml, jsonを生成する方法として、

$ make prometheus_alerts.yaml
$ make prometheus_rules.yaml
$ make dashboards_out

と記述してるが、そのMakefileについては特に言及されていなかったりする。
このMakefile自体は Kubernetes-mixin のものだが、初見ではなかなか気づかない。

MakefileではなくCLIとして mixtool というのもあるので、これを使うのも良い。
lintは存在しない変数まで調べてくれるので便利。
serverなど一部道半ばぽいサブコマンドもあるので、lintとgenerateだけ使うくらいの気持ちで良いと思う。

生成方法はmixtoolを使えば良いとして、複数のMixinsを組み合わせるにはどうしたらいいのか、式や値の一部をOverrideしたいがどうすればいいのか。というところで結局はworkflowが分かりづらかったりする。

そもそもJsonnet自体高度なことやりだすと難しい、継承を考え始めたあたりで難易度が急上昇する。

Project templateを作った

先に課題として挙げた

を解決するためのProject templateを作った。

https://github.com/kobtea/jsonnet-libs

詳細はREADMEに書いたつもりなので、ブログでは先の課題に対する解決に焦点を当てる。

Workflowを分かりやすくする

Project templateなのでforkするなり好きにしてもらって、管理するためのリポジトリを準備する。

$ git clone https://github.com/kobtea/jsonnet-libs.git
$ cd jsonnet-libs
$ jb install

例として project-sample という名前でk8s stackの監視を設定してみる。
Environmentというのはprod, dev, service1など任意の論理的なデプロイの単位を表す。
エントリポイントとなる mixin.libsonnet を作成し、利用するmixinをimportする。

$ mkdir env/project-sample

# install package
$ jb install https://github.com/kubernetes-monitoring/kubernetes-mixin

$ vim env/project-sample/mixin.libsonnet
$ cat env/project-sample/mixin.libsonnet
local sampleOne = import '../../mixin/sample-one-mixin/mixin.libsonnet';
local k8s = import 'kubernetes-mixin/mixin.libsonnet';

sampleOne + k8s

各mixinで定義されている configの値を上書きする場合は以下のように書き換える。
config自体はmixinsの config.libsonnet に書くのが慣例ぽい。

https://github.com/kubernetes-monitoring/kubernetes-mixin/blob/master/config.libsonnet

$ cat env/project-sample/mixin.libsonnet
local sampleOne = import '../../mixin/sample-one-mixin/mixin.libsonnet';
local k8s = import 'kubernetes-mixin/mixin.libsonnet';

sampleOne {
  _config+:: {
    sampleOneSelector: 'job="sample-one-mod"',
  },
} +
k8s {
  _config+:: {
    nodeExporterSelector: 'job="node"',
  },
}.

envの作成ができたらsyntax checkをする。

$ make fmt
$ make lint

問題なければいよいよyaml, jsonを生成する。
成果物はdist配下に作成される。

$ make generate ARG=project-sample
$ tree dist/project-sample
dist/project-sample
├── alerts.yml
├── dashboards
│   ├── apiserver.json
│   ├── cluster-total.json
│   ├── controller-manager.json
│   ├── k8s-resources-cluster.json
│   ├── k8s-resources-namespace.json
│   ├── k8s-resources-node.json
│   ├── k8s-resources-pod.json
│   ├── k8s-resources-workload.json
│   ├── k8s-resources-workloads-namespace.json
│   ├── kubelet.json
│   ├── namespace-by-pod.json
│   ├── namespace-by-workload.json
│   ├── persistentvolumesusage.json
│   ├── pod-total.json
│   ├── proxy.json
│   ├── sample-one
│   │   ├── sample-one-one.json
│   │   └── sample-one-two.json
│   ├── scheduler.json
│   └── workload-total.json
└── rules.yml

2 directories, 21 files

成果物はおなじみのPrometheus ruleのyaml, Grafana dashboardのjsonなのでYour Favorite Deploymentでデプロイする。

Rule, Dashboardの上書きを容易にする

Alert Ruleのintervalを上書きしたい、Dashboardの式をRecording ruleのMetricsに差し替えたい。configには出されていないが上書きしたい需要は常にある。

式の上書きを補助する関数をライブラリとして準備している。

// include utils
local utils = import '../../lib/utils.libsonnet';

// override alert
utils.overrideAlerts([
  {
    alert: 'SampleTwoUp',
    expr: |||
      up{%(sampleTwoSelector)s} == 0
    ||| % $._config,
  },
]),

// override rule
utils.overrideRules([
  {
    record: 'instance_path:two_requests:rate5m',
    expr: |||
      rate(two_requests_total{%(sampleTwoSelector)s}[10m])
    ||| % $._config,
  },
]),

// override dashboard
local dashboards = super.grafanaDashboards;
local d1 = '%(sampleTwoGrafanaFolder)s/sample-two-one.json' % $._config;
{
  [d1]:
    std.foldl(
      function(acc, elm) acc + utils.overrideDashboardPanelTarget(elm.target, elm.title, acc),
      [
        {
          title: 'Requests',
          target: {
            refId: 'A',
            expr: 'avg(instance_path:one_requests:rate5m)',
          },
        },
        {
          title: 'Requests2',
          target: {
            refId: 'A',
            expr: 'max(instance_path:one_requests:rate5m)',
          },
        },
      ],
      dashboards[d1],
    )
},

具体的なサンプルはこちら
Dashboardに関してはRuleよりも複雑なのでまだ改善点が多い。けど、基本的なユースケースはカバーできている気がする。

便利。

Jsonnet vs CUE

Jsonnetに言及しておいて何だけど、Monitoring Mixinsは終わりが近いかもしれない。
MixinsについてGrafana Labsの人が年始にブログで言及していた。

How Prometheus monitoring mixins can make effective observability strategies accessible to all | Grafana Labs

かいつまんで要約すると

Observabilityの文脈ではMetricsだけではなく、それをつかって何を観測するかという知見も提供する必要がある。Mixinsはこのo11yの思想にマッチし、Kubernetes-mixinは象徴的な存在である。

しかし、長期的な目で見るとMixinsは限界があるかもしれない。理由はメンテナ不足、Jsonnetがtext baseのため妥当性の検証にコストがかかること。そして、一般的なDashboards,Alerts,Rulesは"一般的"でしかないこと。これは各社様々な事情で存在するローカルルールや文化に対応しきれないということ。

Mixinsの課題を踏まえ、新しいプロトタイピングを始めている。プロトタイピングではMixinsの思想はそのままに先の課題を改善する。そしてそれはJsonnetではなくなるかも。

ということらしい。

その移行先の言語として有力なのがCUE。いまは過渡期でCUEベースでGrafanaを管理できるような実装が進められている
過渡期なのでどっち使えばいいんじゃいと悩むけど、Issueにもある通りCUEベースのMixinsはまだまだエコシステムが成熟していないので今使うならJsonnetで良いと思う。
終りが近いと言いつつも明確に移行が宣言されたわけではないし、何年先の話になるかもわからない。既存のMixinを使いたいならJsonnetベースを使えばいいし、まっさらな状態で社内の設定を管理するぜ!というならCUEベースで始めても良いかもね。

自分は主にKubernetes-mixinsに乗っかりたい動機でまだまだJsonnetベースで頑張るぞい。