kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

kube-prometheusでKubernetesの監視環境をマルっと用意する②

f:id:kun432:20210124210855p:plain

前回はQuickstartに従ってkube-prometheusを動かすところまでやってみました。今回はjsonnetを使ったカスタマイズをやってみたいと思います。

前回はこちら。

目次

jsonnet環境の構築

jsonnetは、JSONを出力するためのテンプレート言語およびツールです。

そしてjsonnetを使うためのgo製のパッケージマネージャーとしてjsonnet-bundlerがあります。

kube-prometheusでmanifestのカスタマイズを行うにはGo+Jsonnet+jsonnet-bundlerが必要になりますので、まずはこれをインストールしましょう。

まずGo。

$ wget https://golang.org/dl/go1.15.7.linux-amd64.tar.gz
$ sudo tar -C /usr/local -zxf go1.15.7.linux-amd64.tar.gz
$ cat <<'EOF' >> ~/.bashrc
export GOPATH=$HOME/go
PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
EOF
$ source .bashrc

次にJsonnet+jsonnet-bundler。ついでにjsonをyamlに変換するgojsontoyamlも入れておきましょう。

$ go get github.com/google/go-jsonnet/cmd/jsonnet
$ GO111MODULE="on" go get github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb
$ go get github.com/brancz/gojsontoyaml

jsonnetことはじめ

少しだけjsonnetに慣れておきましょう。jsonnetが動作することを確認しておいてください。

$ jsonnet --version
Jsonnet commandline interpreter (Go implementation) v0.17.0

先ほども言いましたが、jsonnetは、JSONを出力するためのテンプレート言語およびツールであり、JSONでめんどくさいところを改善しつつ、より柔軟な記述が可能となっています。

以下のようなファイルを"fabfour.jsonnet"として作成します。

// description of fabfour
{
  fabfour: [
    {
      id: 1,
      name: "John",
      role: "Backing Guitar",
    },
    {
      id: 2,
      name: "Paul",
      role: "Bass Guitar",
    },
    {
      id: 3,
      name: "George",
      role: "Lead Guitar",
    },
    {
      id: 4,
      name: "Ringo",
      role: "Drums",
    },
  ]
}

jsonnetコマンドで実行します。

$ jsonnet fabfour.jsonnet
{
   "fabfour": [
      {
         "id": 1,
         "name": "John",
         "role": "Backing Guitar"
      },
      {
         "id": 2,
         "name": "Paul",
         "role": "Bass Guitar"
      },
      {
         "id": 3,
         "name": "George",
         "role": "Lead Guitar"
      },
      {
         "id": 4,
         "name": "Ringo",
         "role": "Drums"
      }
   ]
}

JSONで怒られるコメントやケツカンマも気にすることなく、またキーをクォートせずに書けるなど、便利になっているのがわかるでしょうか。

さらに以下のように書くこともできます。

// description of fabfour
local member = ["John", "Paul", "George", "Ringo"];
local role = ["Backing Guitar", "Bass Guitar", "Lead Guitar", "Drums"];
local concat(m,r) = m + " is " + r;
{
  fabfour: [
    {
      id: i + 1,
      name: member[i],
      role: role[i],
      comment: concat(member[i], role[i])
    } for i in std.range(0, std.length(member)-1)
  ]
}

実行するとこういう感じになります。

$ jsonnet fabfour.jsonnet
{
   "fabfour": [
      {
         "comment": "John is Backing Guitar",
         "id": 1,
         "name": "John",
         "role": "Backing Guitar"
      },
      {
         "comment": "Paul is Bass Guitar",
         "id": 2,
         "name": "Paul",
         "role": "Bass Guitar"
      },
      {
         "comment": "George is Lead Guitar",
         "id": 3,
         "name": "George",
         "role": "Lead Guitar"
      },
      {
         "comment": "Ringo is Drums",
         "id": 4,
         "name": "Ringo",
         "role": "Drums"
      }
   ]
}

変数や関数を定義したり、数値や文字列の操作、制御構文も可能ということですね!

Jsonnet+jsonnet-bundlerを使ったkube-prometheusプロジェクトのカスタマイズ

ではJsonnet+jsonnet-bundlerを使って、カスタマイズされたkube-prometheusプロジェクトを作成していきましょう。

ディレクトリを作成します。

$ mkdir my-kube-prometheus; cd my-kube-prometheus

jsonnet-bundlerのjbコマンドでプロジェクトを初期化します。

$ jb init

これにより、jsonnetfile.jsonが作成されます。中身はこんな感じです。

{
  "version": 1,
  "dependencies": [],
  "legacyImports": true
}

jb installコマンドでkube-prometheusパッケージを追加します。ここでもリリース番号を指定している点に注意してください。以降の説明の中でレポジトリを参照する箇所が複数回出てきますが、全てrelease-0.6のレポジトリを参照しています。適宜読み替えてください。

$ jb install github.com/coreos/kube-prometheus/jsonnet/kube-prometheus@release-0.6
GET https://github.com/coreos/kube-prometheus/archive/f69ff3d63de17f3f52b955c3b7e0d7aff0372873.tar.gz 200
GET https://github.com/coreos/prometheus-operator/archive/cd331ce9bb58bb926e391c6ae807621cb12cc29e.tar.gz 200
GET https://github.com/ksonnet/ksonnet-lib/archive/0d2f82676817bbf9e4acf6495b2090205f323b9f.tar.gz 200
GET https://github.com/kubernetes-monitoring/kubernetes-mixin/archive/a161500608ac2ca8908f2c318bd929ecd5e20415.tar.gz 200
GET https://github.com/kubernetes/kube-state-metrics/archive/89aaf6c524ee891140c4c8f2a05b1b16f5847309.tar.gz 200
GET https://github.com/prometheus/prometheus/archive/983ebb4a513302315a8117932ab832815f85e3d2.tar.gz 200
GET https://github.com/brancz/kubernetes-grafana/archive/57b4365eacda291b82e0d55ba7eec573a8198dda.tar.gz 200
GET https://github.com/coreos/etcd/archive/92458228e1268e9b78e11abeeb255824b44d0b2f.tar.gz 200
GET https://github.com/kubernetes/kube-state-metrics/archive/89aaf6c524ee891140c4c8f2a05b1b16f5847309.tar.gz 200
GET https://github.com/prometheus/node_exporter/archive/88ee42742e1a947c91a328dfe47e6ea3c3fbd5da.tar.gz 200
GET https://github.com/grafana/grafonnet-lib/archive/b0d72d6ed0e9fcab83fc2dd954b3bd57113e768c.tar.gz 200
GET https://github.com/grafana/jsonnet-libs/archive/e03ce6c81f2dc4b147d138737e7cced476c966cd.tar.gz 200
GET https://github.com/kubernetes-monitoring/kubernetes-mixin/archive/3d4f68cead695d2717d606f80246ffcfd7b1f08b.tar.gz 200

これにより、プロジェクトフォルダ内にパッケージが追加され、jsonnet.lock.jsonおよびjsonnet.lock.jsonが更新されます。npmっぽい感じですね。

$ ls
jsonnetfile.json  jsonnetfile.lock.json  vendor

$ ls vendor
etcd-mixin       ksonnet                   node-mixin
github.com       kube-prometheus           prometheus
grafana          kube-state-metrics        prometheus-operator
grafana-builder  kube-state-metrics-mixin  promgrafonnet
grafonnet        kubernetes-mixin

$ cat jsonnetfile.json
{
  "version": 1,
  "dependencies": [
    {
      "source": {
        "git": {
          "remote": "https://github.com/coreos/kube-prometheus.git",
          "subdir": "jsonnet/kube-prometheus"
        }
      },
      "version": "release-0.6"
    }
  ],
  "legacyImports": true
}

$ cat jsonnetfile.lock.json
{
  "version": 1,
  "dependencies": [
    {
      "source": {
        "git": {
          "remote": "https://github.com/brancz/kubernetes-grafana.git",
          "subdir": "grafana"
        }
      },
      "version": "57b4365eacda291b82e0d55ba7eec573a8198dda",
      "sum": "92DWADwGjnCfpZaL7Q07C0GZayxBziGla/O03qWea34="
    },
(snip)

基本となるbase.jsonnetファイルを作成します。サンプルのjsonnetファイルから作成するのがよいようです。https://github.com/prometheus-operator/kube-prometheus/blob/release-0.6/example.jsonnet

local kp =
  (import 'kube-prometheus/kube-prometheus.libsonnet') +
  // Uncomment the following imports to enable its patches
  // (import 'kube-prometheus/kube-prometheus-anti-affinity.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-managed-cluster.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-node-ports.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-static-etcd.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-thanos-sidecar.libsonnet') +
  // (import 'kube-prometheus/kube-prometheus-custom-metrics.libsonnet') +
  {
    _config+:: {
      namespace: 'monitoring',
    },
  };

{ ['setup/0namespace-' + name]: kp.kubePrometheus[name] for name in std.objectFields(kp.kubePrometheus) } +
{
  ['setup/prometheus-operator-' + name]: kp.prometheusOperator[name]
  for name in std.filter((function(name) name != 'serviceMonitor'), std.objectFields(kp.prometheusOperator))
} +
// serviceMonitor is separated so that it can be created after the CRDs are ready
{ 'prometheus-operator-serviceMonitor': kp.prometheusOperator.serviceMonitor } +
{ ['node-exporter-' + name]: kp.nodeExporter[name] for name in std.objectFields(kp.nodeExporter) } +
{ ['kube-state-metrics-' + name]: kp.kubeStateMetrics[name] for name in std.objectFields(kp.kubeStateMetrics) } +
{ ['alertmanager-' + name]: kp.alertmanager[name] for name in std.objectFields(kp.alertmanager) } +
{ ['prometheus-' + name]: kp.prometheus[name] for name in std.objectFields(kp.prometheus) } +
{ ['prometheus-adapter-' + name]: kp.prometheusAdapter[name] for name in std.objectFields(kp.prometheusAdapter) } +
{ ['grafana-' + name]: kp.grafana[name] for name in std.objectFields(kp.grafana) }

これを環境に合わせてカスタマイズしていきます。カスタマイズの内容については以下を参考に。

https://github.com/prometheus-operator/kube-prometheus/tree/release-0.6#configuration

https://github.com/prometheus-operator/kube-prometheus/tree/release-0.6#customization-examples

とりあえず冒頭の箇所だけ軽く修正して試してみましょう。kubeadm+各ダッシュボードをnodeportに変更+prometheusとalertmanagerのpodのnode分離を有効にしました。

local kp =
  (import 'kube-prometheus/kube-prometheus.libsonnet') +
  (import 'kube-prometheus/kube-prometheus-kubeadm.libsonnet') + 
  (import 'kube-prometheus/kube-prometheus-node-ports.libsonnet') +
  (import 'kube-prometheus/kube-prometheus-anti-affinity.libsonnet') +
(snip)

ビルド用のスクリプト(https://github.com/prometheus-operator/kube-prometheus/blob/release-0.6/build.sh)を使ってビルドします。

#!/usr/bin/env bash

# This script uses arg $1 (name of *.jsonnet file to use) to generate the manifests/*.yaml files.

set -e
set -x
# only exit with zero if all commands of the pipeline exit successfully
set -o pipefail

# Make sure to use project tooling
PATH="$(pwd)/tmp/bin:${PATH}"

# Make sure to start with a clean 'manifests' dir
rm -rf manifests
mkdir -p manifests/setup

# Calling gojsontoyaml is optional, but we would like to generate yaml, not json
jsonnet -J vendor -m manifests "${1-example.jsonnet}" | xargs -I{} sh -c 'cat {} | gojsontoyaml > {}.yaml' -- {}

# Make sure to remove json files
find manifests -type f ! -name '*.yaml' -delete
rm -f kustomization
$ ./build.sh base.libsonnet

ビルドするとmanifestディレクト以下にマニフェストが出力されます。

$ tree manifests
manifests
├── alertmanager-alertmanager.yaml
├── alertmanager-secret.yaml
├── alertmanager-service.yaml
├── alertmanager-serviceAccount.yaml
├── alertmanager-serviceMonitor.yaml
├── grafana-dashboardDatasources.yaml
├── grafana-dashboardDefinitions.yaml
├── grafana-dashboardSources.yaml
├── grafana-deployment.yaml
├── grafana-service.yaml
├── grafana-serviceAccount.yaml
├── grafana-serviceMonitor.yaml
├── kube-state-metrics-clusterRole.yaml
├── kube-state-metrics-clusterRoleBinding.yaml
├── kube-state-metrics-deployment.yaml
├── kube-state-metrics-service.yaml
├── kube-state-metrics-serviceAccount.yaml
├── kube-state-metrics-serviceMonitor.yaml
├── node-exporter-clusterRole.yaml
├── node-exporter-clusterRoleBinding.yaml
├── node-exporter-daemonset.yaml
├── node-exporter-service.yaml
├── node-exporter-serviceAccount.yaml
├── node-exporter-serviceMonitor.yaml
├── prometheus-adapter-apiService.yaml
├── prometheus-adapter-clusterRole.yaml
├── prometheus-adapter-clusterRoleAggregatedMetricsReader.yaml
├── prometheus-adapter-clusterRoleBinding.yaml
├── prometheus-adapter-clusterRoleBindingDelegator.yaml
├── prometheus-adapter-clusterRoleServerResources.yaml
├── prometheus-adapter-configMap.yaml
├── prometheus-adapter-deployment.yaml
├── prometheus-adapter-roleBindingAuthReader.yaml
├── prometheus-adapter-service.yaml
├── prometheus-adapter-serviceAccount.yaml
├── prometheus-adapter-serviceMonitor.yaml
├── prometheus-clusterRole.yaml
├── prometheus-clusterRoleBinding.yaml
├── prometheus-kubeControllerManagerPrometheusDiscoveryService.yaml
├── prometheus-kubeSchedulerPrometheusDiscoveryService.yaml
├── prometheus-operator-serviceMonitor.yaml
├── prometheus-prometheus.yaml
├── prometheus-roleBindingConfig.yaml
├── prometheus-roleBindingSpecificNamespaces.yaml
├── prometheus-roleConfig.yaml
├── prometheus-roleSpecificNamespaces.yaml
├── prometheus-rules.yaml
├── prometheus-service.yaml
├── prometheus-serviceAccount.yaml
├── prometheus-serviceMonitor.yaml
├── prometheus-serviceMonitorApiserver.yaml
├── prometheus-serviceMonitorCoreDNS.yaml
├── prometheus-serviceMonitorKubeControllerManager.yaml
├── prometheus-serviceMonitorKubeScheduler.yaml
├── prometheus-serviceMonitorKubelet.yaml
└── setup
    ├── 0namespace-namespace.yaml
    ├── prometheus-operator-0alertmanagerCustomResourceDefinition.yaml
    ├── prometheus-operator-0podmonitorCustomResourceDefinition.yaml
    ├── prometheus-operator-0probeCustomResourceDefinition.yaml
    ├── prometheus-operator-0prometheusCustomResourceDefinition.yaml
    ├── prometheus-operator-0prometheusruleCustomResourceDefinition.yaml
    ├── prometheus-operator-0servicemonitorCustomResourceDefinition.yaml
    ├── prometheus-operator-0thanosrulerCustomResourceDefinition.yaml
    ├── prometheus-operator-clusterRole.yaml
    ├── prometheus-operator-clusterRoleBinding.yaml
    ├── prometheus-operator-deployment.yaml
    ├── prometheus-operator-service.yaml
    └── prometheus-operator-serviceAccount.yaml

1 directory, 68 files

あとはこれを適用するだけです。

$ kubectl apply -f manifests/setup
$ kubectl apply -f manifests/

確認してみましょう。

$ kubectl get all -n monitoring
NAME                                       READY   STATUS    RESTARTS   AGE
pod/alertmanager-main-0                    2/2     Running   0          4m37s
pod/alertmanager-main-1                    2/2     Running   0          4m37s
pod/alertmanager-main-2                    2/2     Running   0          4m37s
pod/grafana-67dfc5f687-qsqlq               1/1     Running   0          4m37s
pod/kube-state-metrics-69d4c7c69d-gf945    3/3     Running   0          4m37s
pod/node-exporter-j7r9d                    2/2     Running   0          4m37s
pod/node-exporter-jcbqv                    2/2     Running   0          4m37s
pod/node-exporter-kxzgq                    2/2     Running   0          4m37s
pod/node-exporter-qjhs8                    2/2     Running   0          4m37s
pod/prometheus-adapter-66b855f564-ss446    1/1     Running   0          4m37s
pod/prometheus-k8s-0                       3/3     Running   1          4m36s
pod/prometheus-k8s-1                       3/3     Running   1          4m36s
pod/prometheus-operator-75c98bcfd7-d242v   2/2     Running   0          4m47s

NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/alertmanager-main       NodePort    10.106.156.38    <none>        9093:30903/TCP               4m40s
service/alertmanager-operated   ClusterIP   None             <none>        9093/TCP,9094/TCP,9094/UDP   4m37s
service/grafana                 NodePort    10.103.252.223   <none>        3000:30902/TCP               4m39s
service/kube-state-metrics      ClusterIP   None             <none>        8443/TCP,9443/TCP            4m38s
service/node-exporter           ClusterIP   None             <none>        9100/TCP                     4m38s
service/prometheus-adapter      ClusterIP   10.111.27.139    <none>        443/TCP                      4m38s
service/prometheus-k8s          NodePort    10.105.218.239   <none>        9090:30900/TCP               4m36s
service/prometheus-operated     ClusterIP   None             <none>        9090/TCP                     4m36s
service/prometheus-operator     ClusterIP   None             <none>        8443/TCP                     4m48s

NAME                           DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/node-exporter   4         4         4       4            4           kubernetes.io/os=linux   4m38s

NAME                                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/grafana               1/1     1            1           4m40s
deployment.apps/kube-state-metrics    1/1     1            1           4m39s
deployment.apps/prometheus-adapter    1/1     1            1           4m39s
deployment.apps/prometheus-operator   1/1     1            1           4m49s

NAME                                             DESIRED   CURRENT   READY   AGE
replicaset.apps/grafana-67dfc5f687               1         1         1       4m40s
replicaset.apps/kube-state-metrics-69d4c7c69d    1         1         1       4m39s
replicaset.apps/prometheus-adapter-66b855f564    1         1         1       4m39s
replicaset.apps/prometheus-operator-75c98bcfd7   1         1         1       4m49s

NAME                                 READY   AGE
statefulset.apps/alertmanager-main   3/3     4m38s
statefulset.apps/prometheus-k8s      2/2     4m37s

nodeportで上がっているので、それぞれアクセスしてみます。

f:id:kun432:20210127022557p:plain

f:id:kun432:20210127022602p:plain

f:id:kun432:20210127022609p:plain

とりあえず動いているようですね。

まとめ

Jsonnetの基本的な使い方と、jsonnet-bundlerを使ったkube-prometheusのカスタマイズの流れを一通り抑えました。kube-prometheusのjsonnetファイル、見てるとちょっと避けたい感があったのですがw、ちょっと和らいできたかな?という感じです。

とはいえ、ほとんどカスタマイズできてないし、また環境ごとに出力を分けたい、とかもあるので、次回以降はそのあたりをやっていきたいと思います。