はじめに
kube-prometheusは、k8sクラスターでprometheus, grafana, alertmanagerを動作させるための便利なOperatorとして配布されています。Prometheus Operatorを単独で利用するにはGrafanaなどの周辺のアプリケーションも動作させなければいけないため、それらを一括でデプロイしてくれるのは確かに便利です。
しかしNetworkPolicyがかなり厳格に設定されていて、基本的にはデプロイしたPod間の疎通のみが許可されています。
外部からのWebブラウザーでのアクセスは想定されておらず、ガイドされているWebブラウザーからの通信もport-fowardのみが記載されています。
Ingressを経由して、Grafanaにアクセスする方法を検討した際のメモを中心に、jsonnetで様々なカスタマイズをした顛末について記載しておきます。
この記事ではまずGrafanaにアクセスするためにdefault構成をdeploy後にランタイムを変更したad-hocな方法について記載しています。次にGrafanaを含む他の変更についてjsonnet-builderを使ってカスタマイズした構成情報をdeployした方法を載せています。
環境
本番系クラスター
- Kubernetes v1.25.6 (Kubespray v2.21.0)
- kube-prometheus v0.12.0
検証系クラスター
- Kubernetes v1.27.5 (Kubespray v2.23.0)
- kube-prometheus v0.13.0
事前準備
jsonnet, gojson2yaml, jbコマンドは、手順に従って最新版を ~/go/bin/ に配置しています。このディレクトリはPATH環境変数に登録しています。
導入方法
README.mdのQuick Startセクションにあるとおりに実施します。
特別な手順は含まれていませんが、確認のために残します。
$ git clone https://github.com/prometheus-operator/kube-prometheus.git
$ cd kube-prometheus
## Compatibility Matrixを参照し、適切なtagをcheckoutする
$ git checkout refs/tags/v0.12.0 -b my_v0.12.0
Releaseブランチを使うように指示されているので、こちらをマージします。
$ git pull origin release-0.12
$ sudo kubectl apply --server-side -f manifests/setup
$ sudo kubectl wait \
--for condition=Established \
--all CustomResourceDefinition \
--namespace=monitoring
$ sudo kubectl apply -f manifests/
フロントエンド Reverse Proxy Server
K8sクラスターはバックエンドのPrivate Network内部にあるため、組織内からアクセスできるようにネットワーク境界でnginxを稼動しています。
最新の設定例については公式ガイドを参照してください。
Reverse ProxyとしてK8sのIngressに転送していますが、Grafana関係の設定は次のようになっています。
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location /grafana/ {
proxy_pass https://192.168.200.99;
allow 10.0.0.0/8;
deny all;
## auth proxy用の設定
proxy_set_header X-WEBAUTH-USER reader;
proxy_set_header Authorization "";
## /grafana/api/live/ 用のWS設定
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
}
以前はHost
フィールドに$http_host
を指定していましたが、これを$host
に変更しています。また、$connection_upgrade
が不定な値になっていたようだったので、明示的にmapを加えました。
Grafana AuthProxyの初期設定
情報公開用にGrafana画面を公開するためViewer権限を持つユーザーでアクセスするよう強制します。
設定時にはX-WEBAUTH-USERとAuthorizationヘッダーは付けずにコメントアウトした状態で、allow行で接続できる端末を限定しておきます。
次に初期状態のGrafanaにadminユーザーでログインし、reader
ユーザーをViewer権限で作成しておきます。
最後に掲載した設定に戻すことで、Webブラウザからはreader
ユーザーでのアクセスが強制されます。
課題とGrafana側の対応を探る
デフォルト状態のGrafanaはトップレベル('/')にデプロイされてしまうため、/grafana/のようなsubpathでアクセスしたいcontext-rootの変更方法が分かりませんでした。
Grafanaのドキュメントを確認するとcontext-rootを変更することは可能ですが、あまり情報はなさそうです。Grafanaのガイドを元に、ConfigMap経由で変更可能か試してみます。
まずは手掛かりを探すために、Podの中から設定ファイルの配置状況などを確認していきます。
幸いGrafanaのPodは、bashやlsなどのユーティリティコマンドが普通に使えそうです。
いろいろ探すと /etc/grafana/grafana.ini があって、定義から secret/grafana-config がマップされていることが分かります。
## Podの中に移動 + 探索
$ sudo kubectl -n monitoring exec -it grafana-9f58f8675-4n957 -- bash
bash-5.1$ ls /etc/grafana/
grafana.ini provisioning
## /etc/grafana がどうやって設定されているかDeploymentの定義を確認
$ sudo kubectl -n monitoring get deploy grafana -o yaml
...
- mountPath: /etc/grafana
name: grafana-config
...
- name: grafana-config
secret:
defaultMode: 420
secretName: grafana-config
...
## Secretオブジェクトから grafana-config の内容を確認
$ sudo kubectl -n monitoring get secret grafana-config -o yaml
apiVersion: v1
data:
grafana.ini: W2RhdGVfZm9ybWF0c10KZGVmYXVsdF90aW1lem9uZSA9IFVUQwo=
kind: Secret
...
Base64になっているようなので、デコードして内容を確認します。
$ echo "W2RhdGVfZm9ybWF0c10KZGVmYXVsdF90aW1lem9uZSA9IFVUQwo=" | base64 -d
[date_formats]
default_timezone = UTC
既存のgrafana.iniファイルの内容に[server]スタンザを付け加えた設定ファイルを加えます。
$ cat <<EOF | base64
[date_formats]
default_timezone = UTC
[server]
domain = grafana.example.com
root_url = %(protocol)s://%(domain)s:%(http_port)s/grafana/
serve_from_sub_path = true
EOF
W2RhdGVfZm9ybWF0c10KZGVmYXVsdF90aW1lem9uZSA9IFVUQwpbc2VydmVyXQpkb21haW4gPSBn
cmFmYW5hLmV4YW1wbGUuY29tCnJvb3RfdXJsID0gJShwcm90b2NvbClzOi8vJShkb21haW4pczol
KGh0dHBfcG9ydClzL2dyYWZhbmEvCnNlcnZlX2Zyb21fc3ViX3BhdGggPSB0cnVlCg==
このdomainにはTLS, HTTP/2を有効にしたNginxが稼動するProxyサーバー名を指定しています。
特にそのようなProxyサーバーを使わない場合には、Webブラウザに指定するホスト名(Ingressに割り当てるIPアドレスなど)を指定します。
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
grafana.ini: |
W2RhdGVfZm9ybWF0c10KZGVmYXVsdF90aW1lem9uZSA9IFVUQwpbc2VydmVyXQpkb21haW4gPSBn
cmFmYW5hLmV4YW1wbGUuY29tCnJvb3RfdXJsID0gJShwcm90b2NvbClzOi8vJShkb21haW4pczol
KGh0dHBfcG9ydClzL2dyYWZhbmEvCnNlcnZlX2Zyb21fc3ViX3BhdGggPSB0cnVlCg==
kind: Secret
...
平文で編集してもBase64にエンコードされるはずですが、ともかくcontext-rootを変更したため、grafanaのPodを再起動しただけでは、ReadinessProbeに失敗します。再起動はせずにDeploymentオブジェクトを変更して、/api/health の部分を編集します。
$ sudo kubectl -n monitoring edit deploy grafana
APIにアクセスしているパスを変更します。
...
readinessProbe:
failureThreshold: 3
httpGet:
path: /grafana/api/health
port: http
scheme: HTTP
...
保存して抜けると自動的にPodは再起動されます。Reconsileの機構は本当に便利ですよね。
IngressからのGrafanaへの接続
実際のWebブラウザからのアクセスは、Global IPを持っているdomain=行に指定したサーバー名のnginxにHTTPSで接続し、そこから http://192.168.110.79/ にプロキシーしています。
ここではIngressについての初期設定を含めた変更点についてまとめておきます。
Ingressの初期設定
既にIngressを利用していれば後述するIngressオブジェクト等を追加するだけで良いと思いますので、ここは飛ばしてください。参考までにkubesprayなどでIngress Controllerを有効にしただけの状態を想定して、Ingressの初期設定ファイルを掲載しておきます。
IngressClassが一つもないので、後でIngressオブジェクトの定義で使うためあらかじめingressclass/nginxを定義しておきます。
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: nginx
spec:
controller: k8s.io/ingress-nginx
Webブラウザからアクセスするために静的なアドレスをアサインします。
k8sが稼動している192.168.110.0/24 は内側のネットワークで、Webブラウザが配置されているネットワークです。
---
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
type: LoadBalancer
loadBalancerIP: "192.168.110.79"
ports:
- name: http
port: 80
targetPort: http
selector:
app.kubernetes.io/name: ingress-nginx
これらの設定によってWebブラウザから192.168.110.79を通して、Ingressに接続できるようになりました。
Ingressオブジェクトの作成
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
labels:
group: ingress-nginx
namespace: ingress-nginx
spec:
ingressClassName: nginx
rules:
- http:
paths:
- backend:
service:
name: monitoring-grafana-svc
port:
number: 3000
path: /grafana
pathType: Prefix
name:に指定したmonitoring-grafana-svcの名前で、Serviceオブジェクトを定義します。
---
apiVersion: v1
kind: Service
metadata:
name: monitoring-grafana-svc
labels:
group: ingress-nginx
namespace: ingress-nginx
spec:
type: ExternalName
externalName: grafana.monitoring.svc.cluster.local
NetworkPolicyの変更
IngressからGrafanaに接続しようとしても、503エラーが出力されます。
冒頭に書いたとおり、NetworkPolicyが厳格なため、Ingressからのアクセス許可を追加で設定する必要があります。
現状のNetworkPolicyを確認します。
$ sudo kubectl -n monitoring get networkpolicy grafana -o yaml
の出力は以下のとおりです。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
egress:
- {}
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
ports:
- port: 3000
protocol: TCP
podSelector:
matchLabels:
app.kubernetes.io/component: grafana
app.kubernetes.io/name: grafana
app.kubernetes.io/part-of: kube-prometheus
policyTypes:
- Egress
- Ingress
Prometheus PodからGrafana Podに向いた接続は許可されていますが、その他のアクセスは許可されていません。
ここにIngressからの接続を、namespaceSelectorを使って許可します。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
egress:
- {}
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 3000
protocol: TCP
podSelector:
matchLabels:
app.kubernetes.io/component: grafana
app.kubernetes.io/name: grafana
app.kubernetes.io/part-of: kube-prometheus
policyTypes:
- Egress
- Ingress
このpodSelectorとnamespaceSelectorの両方に'-'を付けて並列に配置している場合には、OR条件となります。この例で2番目の要素に"-"を付けないと、[{ podSelector: { matchLabels: ... }, namespaceSelector: { matchLabels: ... } }]とまとめられ、結果、AND条件として処理されます。
ここまでで、https://grafana.example.com/grafana/ を通してコンソールにアクセスできるようになっています。
NetworkPolicyのもう一つ別の解放
次のようにform:ブロックを増やすことでも同様の結果が得られます。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
egress:
- {}
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
ports:
- port: 3000
protocol: TCP
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 3000
protocol: TCP
podSelector:
matchLabels:
app.kubernetes.io/component: grafana
app.kubernetes.io/name: grafana
app.kubernetes.io/part-of: kube-prometheus
policyTypes:
- Egress
- Ingress
Grafana UIのデフォルトパスワード
この環境で動作しているGrafanaのデフォルトの(ID, Password)は(admin, admin)
です。
これはGrafanaの公式ドキュメントに記載されているとおりです。
ここまでで、GrafanaのUIに接続することはできました。
【準備作業】jsonnet-builderを使ったkube-prometheusのカスタマイズ
デプロイ後のSecretやDeploymentオブジェクトを編集する方法は手軽で、最初の一歩としては問題ありませんが、kube-prometheusはそう思っていないようです。
kube-prometheusで想定されているjsonnet-builderを利用してカスタマイズした定義ファイルを生成する場合には、あらかじめ次のような準備作業が必要です。
あらかじめjsonnetコマンドなどをgoコマンドを使って導入しておきます。
$ go install -a github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest
$ go install github.com/brancz/gojsontoyaml@latest
$ go install github.com/google/go-jsonnet/cmd/jsonnet@latest
インストールされたコマンドは、~/go/bin/に配置されます。
ここをPATH環境変数に追加する方法はいくつもありますが、Ubuntuであれば ~/.bash_aliases というデフォルトで存在すれば読み込まれるalias設定用のファイルを利用するのがお勧めです。
~/.bashrcを直接編集してしまうと/etc/skel/.bashrc がアップグレードなどで変更されれば、変更点を統合(merge)する手間がかかります。~/.bash_aliasesを利用すると~/.bashrcは/etc/skel/からコピーするだけで済むためです。
公式ドキュメントのcustomizing.mdでは、別のディレクトリでファイルを生成する手順が紹介されていますが、v0.12.0ブランチで作業を進めます。
【カスタマイズ例1】IngressからGrafanaへの通信を許可する
先ほどまで直接ランタイムを変更してきた内容を宣言的に実施するため、examples/kustomize.jsonnet ファイルを変更し、変更済みのYAMLファイルを生成します。
namespaceSelectorの追加
次のような変更を加えます。
values+::{}
の中ではなく、並列に記載されている点に注意してください。
local kp =
(import 'kube-prometheus/main.libsonnet') +
(import 'kube-prometheus/addons/managed-cluster.libsonnet') +
{
values+:: {
...
},
// 以下のブロックを追加
grafana+: {
networkPolicy+: {
spec+: {
ingress+: [{
from: [{
namespaceSelector: {
matchLabels: {
'name': 'ingress-nginx',
},
},
}],
ports: [{
port: 3000,
protocol: "TCP",
}],
}],
},
},
},
};
grafana.iniの編集
context_rootを変更し、サブパスを利用するため grafana.ini への変更内容を次のように加えます。
local kp =
(import 'kube-prometheus/main.libsonnet') +
(import 'kube-prometheus/addons/managed-cluster.libsonnet') +
{
values+:: {
common+: {
namespace: 'monitoring',
},
grafana+:: {
config+: {
sections+: {
server: { domain: 'grafana.examples.com', root_url: '%(protocol)s://%(domain)s:%(http_port)s/grafana/', serve_from_sub_pa
th: true },
},
},
},
},
};
ReadinessProbeのpathの変更
以下のように編集します。
local kp =
(import 'kube-prometheus/main.libsonnet') +
(import 'kube-prometheus/addons/managed-cluster.libsonnet') +
{
values+:: {
...
},
grafana+: {
networkPolicy+: {
...
},
// 以下のブロックを追加
deployment+: {
spec+: {
template+: {
spec+: {
containers: std.map(
function(container)
if container.name == "grafana" then
container {
readinessProbe+: {
httpGet+: {
path: "/grafana/api/health"
},
},
}
else
container,
super.containers
),
},
},
},
},
},
};
あらかじめ vendor/grafana/grafana.libsonnet で準備されている local defaults = の containers変数は新しいサイドカーなどの別イメージのコンテナ一式を追加するには利用できますが、既存のGrafanaのコンテナに関連する設定(env等)を変更することができない点に注意してください。
面倒だったのはcontainers:は配列になっているため、containers+:
のようにオブジェクトの要素を変更する表記は使えないため std.map()
と super.containers
を利用して各要素に関数を適用する必要がありました。
配列の要素が1つだという前提でよければ、if〜else文は不要になるため、もう少し短くなりますが将来の変更時に予想しない結果になる可能性があるためこのような表記が安全だろうと思います。
if〜else文を削除する場合、super.containers
の直前にある},
が必要です。ここに掲載しているelse
文の直前の**{**にはカンマはありませんでした。単純にif文の行を削除したり追加するだけではエラーになります。jsonnetは使いこなすにはなかなか難しい言語です。
ChatGPTの活用
jsonnetは始めてだったので、ここは方法を試行錯誤して悩んだ部分ではあって、最終的に次のようなやり取りを経て、前述の方法に辿り付きました。
最初の試行錯誤がなければ到達できなかったかもしれないとは思いつつ、指示が適切であれば便利な場面があるのは確かです。
NetworkPolicy/prometheus-k8sにadapterからの通信を許可する
v0.12.0ではNetworkPolicyの設定が不十分でadapterからprometheus本体への通信がブロックされています。
以下のように編集します。
local kp =
(import 'kube-prometheus/main.libsonnet') + {
values+:: {
common+: {
...
},
},
prometheus+: {
networkPolicy+: {
spec+: {
ingress+: [{
from: [{
podSelector: {
matchLabels: {
'app.kubernetes.io/name': 'prometheus-adapter',
},
},
}],
ports: [{
port: 9090,
protocol: "TCP",
}],
}],
},
},
},
};
Manifestsの生成とmanifestsの差分
あとは、Makefileを使ってYAMLファイルを生成します。
$ rm -rf manifests
$ make generate
生成された manifests/grafana-networkPolicy.yaml ファイルは手動で変更したものと同様に次のようになります。
diff --git a/manifests/grafana-networkPolicy.yaml b/manifests/grafana-networkPolicy.yaml
index cab676c8..a259b618 100644
--- a/manifests/grafana-networkPolicy.yaml
+++ b/manifests/grafana-networkPolicy.yaml
@@ -16,6 +16,9 @@ spec:
- podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
+ - namespaceSelector:
+ matchLabels:
+ name: ingress-nginx
ports:
- port: 3000
protocol: TCP
grafana.iniは次のようになります。
diff --git a/manifests/grafana-config.yaml b/manifests/grafana-config.yaml
index 10d9c6a9..beab186a 100644
--- a/manifests/grafana-config.yaml
+++ b/manifests/grafana-config.yaml
@@ -12,4 +12,8 @@ stringData:
grafana.ini: |
[date_formats]
default_timezone = UTC
+ [server]
+ domain = grafana.example.com
+ root_url = %(protocol)s://%(domain)s:%(http_port)s/grafana/
+ serve_from_sub_path = true
type: Opaque
grafana-deployment.yamlは次のようになりました。
diff --git a/manifests/grafana-deployment.yaml b/manifests/grafana-deployment.yaml
index eca41b6e..60fa54a6 100644
--- a/manifests/grafana-deployment.yaml
+++ b/manifests/grafana-deployment.yaml
@@ -18,7 +18,7 @@ spec:
template:
metadata:
annotations:
- checksum/grafana-config: adbde4cde1aa3ca57c408943af53e6f7
+ checksum/grafana-config: c9018225d844e770a4c521083fe592b9
checksum/grafana-dashboardproviders: d8fb24844314114bed088b83042b1bdb
checksum/grafana-datasources: 0800bab7ea1e2d8ad5c09586d089e033
labels:
@@ -37,7 +37,7 @@ spec:
name: http
readinessProbe:
httpGet:
- path: /api/health
+ path: /grafana/api/health
port: http
resources:
limits:
ここまでの変更を反映して、manifests/ を更新すると、$ git status
の出力は次のようになります。
On branch my_v0.12.0
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: examples/kustomize.jsonnet
modified: jsonnet/kube-prometheus/components/grafana.libsonnet
modified: manifests/grafana-config.yaml
modified: manifests/grafana-deployment.yaml
modified: manifests/grafana-networkPolicy.yaml
no changes added to commit (use "git add" and/or "git commit -a")
アップグレード方法は、通常のインストール方法と同じです。
【カスタマイズ例2】 NetworkPolicyを全て削除する
examples/networkpolicies-disabled.jsonnet が準備されているので、examples/kustomize.jsonnet の代わりに指定すればNetworkPolicy関連のファイルがmanifests/に出力されなくなります。
diff --git a/Makefile b/Makefile
index 8a438b21..6cf226a0 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ check-docs: $(MDOX_BIN) $(shell find examples) build.sh example.jsonnet
.PHONY: generate
generate: manifests
-manifests: examples/kustomize.jsonnet $(GOJSONTOYAML_BIN) vendor
+manifests: examples/networkpolicies-disabled.jsonnet $(GOJSONTOYAML_BIN) vendor
./build.sh $<
vendor: $(JB_BIN) jsonnetfile.json jsonnetfile.lock.json
この方法では先ほどのgrafana.iniの変更分も含めてファイルを編集する必要があるので少し面倒です。
やはり examples/kustomize.jsonnet を活かして、networkpolicies-disabled の機能を取り込むため次のように編集します。
diff --git a/examples/kustomize.jsonnet b/examples/kustomize.jsonnet
index c12f98b3..5186d9b6 100644
--- a/examples/kustomize.jsonnet
+++ b/examples/kustomize.jsonnet
@@ -1,9 +1,17 @@
local kp =
- (import 'kube-prometheus/main.libsonnet') + {
+ (import 'kube-prometheus/main.libsonnet') +
+ (import 'kube-prometheus/addons/networkpolicies-disabled.libsonnet') + {
values+:: {
common+: {
namespace: 'monitoring',
},
+ grafana+:: {
+ config+: {
+ sections+: {
+ "server": {domain: "grafana.example.com", root_url: "%(protocol)s://%(domain)s:%(http_port)s/grafana/", serve_from_sub_path:
true},
+ },
+ },
+ },
},
};
方法2でこれまでの変更を反映すると$ git status
の出力は次のようになります。
$ git status
On branch my_v0.12.0
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: examples/kustomize.jsonnet
modified: jsonnet/kube-prometheus/components/grafana.libsonnet
modified: kustomization.yaml
deleted: manifests/alertmanager-networkPolicy.yaml
deleted: manifests/blackboxExporter-networkPolicy.yaml
deleted: manifests/grafana-networkPolicy.yaml
deleted: manifests/kubeStateMetrics-networkPolicy.yaml
deleted: manifests/nodeExporter-networkPolicy.yaml
deleted: manifests/prometheus-networkPolicy.yaml
deleted: manifests/prometheusAdapter-networkPolicy.yaml
deleted: manifests/prometheusOperator-networkPolicy.yaml
no changes added to commit (use "git add" and/or "git commit -a")
この方法は必須ではないので、これは行わずに個別にポートを有効化しても問題ありません。
【カスタマイズ例3】アラートメールの送信先を指定する
テスト環境ではメールを送信させる必要はありませんが、本番環境であれば異常はできるだけ早く把握したいものです。
今回はSeverityに応じてメールを送信させます。
このニーズは当然想定されているので、変更は簡単です。
local kp =
(import 'kube-prometheus/main.libsonnet') + {
values+:: {
common+: {
namespace: 'monitoring',
},
alertmanager+:: {
config+: {
global+: {
resolve_timeout: '5m',
smtp_smarthost: 'smtp.example.com:25',
smtp_from: 'grafana@example.com',
},
receivers: [
{
name: 'Critical',
email_configs: [{
to: 'admin@example.com',
}],
},
{ name: 'Default', },
{ name: 'Watchdog', },
{ name: 'null', },
],
},
},
},
},
};
これだけで Severity が Critical に分類されるアラートが発生した場合にメールが送信されます。
【カスタマイズ例4】KubeSchedulerDown, KubeControllerManagerDown のアラートを止める
環境によっては kube-scheduler や kube-controller-manager に対する監視が適当でない場合があります。
これらは ciritcal に分類されているため、メールを送信するようにした途端に定期的に警告メールを受信することになるかもしれません。
これを停止するために現在では便利な定義が jsonnet/kube-prometheus/addons にあるので、次のようなimport文を examples/kustomize.jsonnet に追加しましょう。
local kp =
(import 'kube-prometheus/main.libsonnet') +
(import 'kube-prometheus/addons/managed-cluster.libsonnet') +
{
1行加えるだけですが、{ と + に注意してください。
diffでみると次のようになっています。
diff --git a/examples/kustomize.jsonnet b/examples/kustomize.jsonnet
index 04f151ad..ee33e27e 100644
--- a/examples/kustomize.jsonnet
+++ b/examples/kustomize.jsonnet
@@ -1,5 +1,7 @@
local kp =
- (import 'kube-prometheus/main.libsonnet') + {
+ (import 'kube-prometheus/main.libsonnet') +
+ (import 'kube-prometheus/addons/managed-cluster.libsonnet') +
+ {
values+:: {
common+: {
namespace: 'monitoring',
この結果出力されるYAMLファイルを $ kubectl apply -f manifests
で適用すればアラートは消えますが、気になる場合には不要になった ServiceMonitor オブジェクトを削除しても良いかもしれません。
$ kubectl -n monitoring delete servicemonitor kube-controller-manager
$ kubectl -n monitoring delete servicemonitor kube-scheduler
【カスタマイズ例5】node-exporterのCPUThrottlingHighアラートを止めたい
Severity が info なのでメールは出ませんが、気にある場合には次のようなカスタマイズが可能です。
どれくらいのリソースを指定するのが適当かは環境によって変化するため、元の値の2倍、3倍などと変更して様子をみてください。
diff --git a/examples/kustomize.jsonnet b/examples/kustomize.jsonnet
index 0cfc8d3a..687c4d15 100644
--- a/examples/kustomize.jsonnet
+++ b/examples/kustomize.jsonnet
@@ -12,6 +14,32 @@ local kp =
},
},
},
+ nodeExporter+:: {
+ resources+: {
+ requests+: { cpu: "306m", },
+ limits+: { cpu: "750m", },
+ },
+ },
+ blackboxExporter+:: {
+ resources+: {
+ requests+: { cpu: "80m", },
+ limits+: { cpu: "160m", },
+ },
+ },
+ kubeStateMetrics+:: {
+ kubeRbacProxyMain+:: {
+ resources+: {
+ requests+: { cpu: "80m", },
+ limits+: { cpu: "160m", },
+ },
+ },
+ kubeRbacProxySelf+:: {
+ resources+: {
+ requests+: { cpu: "40m", },
+ limits+: { cpu: "80m", },
+ },
+ },
+ },
},
};
【カスタマイズ例6】 Rook/CephのFileSystemのinodesアラートを消したい
Rook/Cephが提供するstorageclass/rook-cephfsはReadWriteManyでアクセスできるStorage機能ですが、inodesが動的に割り当てられるにも関わらず、ある程度のファイル数が書き込まれると上限に到達してしまいます。
これが原因で見掛け上はdf -i
コマンドの出力でinodeの使用率が100%になってしまいアラートが発砲されます。
このため KubePersistentVolumeInodesFillingUp ルールに、次のようなルールを加えれば解決できることはad-hocに定義を編集して突き止めています。
unless on(namespace, persistentvolumeclaim)
kube_persistentvolumeclaim_info{storageclass="rook-cephfs"} == 1
このルールは上限が1m毎と、1h毎の2つがあるので、これらを識別して個別にルールをアップデートしなければいけません。
このための変更を加えた examples/kustomize.jsonnet は次のようになりました。
diff --git a/examples/kustomize.jsonnet b/examples/kustomize.jsonnet
index c12f98b3..09141738 100644
--- a/examples/kustomize.jsonnet
+++ b/examples/kustomize.jsonnet
@@ -5,6 +5,34 @@ local kp =
namespace: 'monitoring',
},
},
+ kubernetesControlPlane+: {
+ prometheusRule+: {
+ spec+: {
+ groups: std.map(
+ function(group)
+ if group.name == 'kubernetes-storage' then
+ group {
+ rules: std.map(
+ function(rule)
+ if rule.alert == 'KubePersistentVolumeInodesFillingUp' then
+ rule {
+ expr+: |||
+ unless on(namespace, persistentvolumeclaim)
+ kube_persistentvolumeclaim_info{storageclass="rook-cephfs"} == 1
+ |||
+ }
+ else
+ rule,
+ super.rules
+ ),
+ }
+ else
+ group,
+ super.groups
+ ),
+ },
+ },
+ },
};
local manifests =
make generate
で生成されたYAMLファイルでも差分を確認しておきます。
diff --git a/manifests/kubernetesControlPlane-prometheusRule.yaml b/manifests/kubernetesControlPlane-prometheusRule.yaml
index ce0fefd6..0dd03e65 100644
--- a/manifests/kubernetesControlPlane-prometheusRule.yaml
+++ b/manifests/kubernetesControlPlane-prometheusRule.yaml
@@ -440,6 +440,8 @@ spec:
kube_persistentvolumeclaim_access_mode{ access_mode="ReadOnlyMany"} == 1
unless on(namespace, persistentvolumeclaim)
kube_persistentvolumeclaim_labels{label_excluded_from_alerts="true"} == 1
+ unless on(namespace, persistentvolumeclaim)
+ kube_persistentvolumeclaim_info{storageclass="rook-cephfs"} == 1
for: 1m
labels:
severity: critical
@@ -465,6 +467,8 @@ spec:
kube_persistentvolumeclaim_access_mode{ access_mode="ReadOnlyMany"} == 1
unless on(namespace, persistentvolumeclaim)
kube_persistentvolumeclaim_labels{label_excluded_from_alerts="true"} == 1
+ unless on(namespace, persistentvolumeclaim)
+ kube_persistentvolumeclaim_info{storageclass="rook-cephfs"} == 1
for: 1h
labels:
severity: warning
参考資料
kube-prometheusに含まれている変更例は新しいルールを加えるもので、デフォルトルールの一部だけを変更するというトリッキーな作業については下記の資料が参考になりました。
【カスタマイズ例7】Rook/CephのmetricsとDashBoardを追加したい
次のような環境で検証しています。
- Rook/Ceph v1.11.10 (Ceph v17.2.6、namespace: rook-ceph)
Rook/Ceph側での設定
rookのGitリポジトリにはmonitroingのためのYAMLファイル等が含まれています。
- rook/deploy/examples/monitoring/service-monitor.yaml
- rook/deploy/examples/monitoring/localrules.yaml
この2つのYAMLファイルは、namespace: rook-ceph 側に設定します。
YAMLファイル中にnamespace: rook-cephは指定されているので、編集は不要です。
$ sudo kubectl -n rook-ceph apply -f rook/deploy/examples/monitoring/service-monitor.yaml
$ sudo kubectl -n rook-ceph apply -f rook/deploy/examples/monitoring/localrules.yaml
これでPrometheus上でmetricsが収集されるようになります。
$ sudo kubectl -n rook-ceph get ServiceMonitor rook-ceph-mgr
$ sudo kubectl -n rook-ceph get PrometheusRule prometheus-ceph-rules
Prometheus側での設定
Grafana Dashboardの定義は次の公式サイトにリンクが掲載されています。
ここからダウンロードできる次のファイルを利用します。
- 2842_rev17.json (Ceph - Cluster)
- 5336_rev9.json (Ceph - OSD (Single))
- 5342_rev9.json (Ceph - Pools)
これらのファイルは namespace: monitoring 側でConfigMapオブジェクトとして登録します。
$ sudo kubectl -n monitoring create configmap grafana-dashboard-ceph-cluster --from-file=2842_rev17.json
$ sudo kubectl -n monitoring create configmap grafana-dashboard-ceph-osd --from-file=5336_rev9.json
$ sudo kubectl -n monitoring create configmap grafana-dashboard-ceph-pools --from-file=5342_rev9.json
Kube-Prometheus側では examples/kustomize.jsonnet に次のような変更を加えます。
local kp =
(import 'kube-prometheus/main.libsonnet') +
(import 'kube-prometheus/addons/managed-cluster.libsonnet') +
{
values+:: {
common+: {
namespace: 'monitoring',
},
prometheus+: {
namespaces+: ['rook-ceph'],
},
},
grafana+: {
networkPolicy+: {
// 省略
},
deployment+: {
spec+: {
template+: {
spec+: {
containers: std.map(
function(container)
if container.name == "grafana" then
container {
readinessProbe+: {
httpGet+: {
path: "/grafana/api/health"
},
},
volumeMounts+: [
{
mountPath: "/grafana-dashboard-definitions/0/ceph-cluster",
name: "grafana-dashboard-ceph-cluster",
readOnly: false,
},
{
mountPath: "/grafana-dashboard-definitions/0/ceph-osd",
name: "grafana-dashboard-ceph-osd",
readOnly: false,
},
{
mountPath: "/grafana-dashboard-definitions/0/ceph-pool",
name: "grafana-dashboard-ceph-pools",
readOnly: false,
},
],
}
else
container,
super.containers
),
volumes+: [
{
configMap: {
name: "grafana-dashboard-ceph-cluster",
},
name: "grafana-dashboard-ceph-cluster",
},
{
configMap: {
name: "grafana-dashboard-ceph-osd",
},
name: "grafana-dashboard-ceph-osd",
},
{
configMap: {
name: "grafana-dashboard-ceph-pools",
},
name: "grafana-dashboard-ceph-pools",
},
],
},
},
},
},
},
};
examples/kustomize.jsonnet を編集したら、make generate
で manifests/ を更新し、kube apply コマンドで適用します。
Podの再起動などは不要で、しばらく待ってからGrafanaのDashboards画面にアクセスすると追加した3つのボードが表示されます。
localrules.yamlファイルの修正
Rook/Cephに付属しているlocalrules.yamlに含まれる次のアラートはharmlessにしたいので、ファイルを修正してseverityをinfoに修正しています。
- CephNodeNetworkPacketDrops
- CephNodeNetworkPacketErrors
- CephNodeInconsistentMTU
rook/deploy/examples/monitoring/localrules.yaml を修正し、kube applyすると即座に反映されます。
現在利用している examples/kustomize.jsonnet ファイルの全体
参考までに現在利用している kustomize.jsonnet ファイルを掲載しておきます。
カスタマイズ例7のrook.ioに対する変更を含んでいるため、rook側のmonitoringディレクトリにあるYAMLファイルを"warning"→"info"に変更するなどの対応をしてから適応し、Grafana用の追加dashboard定義の追加も必要です。
local kp =
(import 'kube-prometheus/main.libsonnet') +
(import 'kube-prometheus/addons/managed-cluster.libsonnet') + {
values+:: {
common+: {
namespace: 'monitoring',
},
nodeExporter+:: {
resources+: {
requests+: { cpu: "500m", },
limits+: { cpu: "1000m", },
},
},
blackboxExporter+:: {
resources+: {
requests+: { cpu: "80m", },
limits+: { cpu: "160m", },
},
},
kubeStateMetrics+:: {
resources+: {
requests+: { cpu: "50m", },
limits+: { cpu: "300m", },
},
kubeRbacProxyMain+:: {
resources+: {
requests+: { cpu: "120m", },
limits+: { cpu: "320m", },
},
},
kubeRbacProxySelf+:: {
resources+: {
requests+: { cpu: "120m", },
limits+: { cpu: "320m", },
},
},
},
grafana+:: {
resources+: {
requests+: { cpu: "300m", },
limits+: { cpu: "600m", },
},
config+: {
sections+: {
server: { domain: 'admin.example.com', root_url: '%(protocol)s://%(domain)s:%(http_port)s/grafana/', serve_from_sub_path: true }
,
"auth.proxy": { enabled: true, auto_sign_up: true, header_name: "X-WEBAUTH-USER", header_property: "username" },
},
},
},
alertmanager+:: { [121/1857]
config+: {
global+: {
resolve_timeout: '5m',
smtp_smarthost: 'smarthost1.example.com:25',
smtp_from: 'admin-dev@example.com',
smtp_require_tls: false,
},
receivers: [
{
name: 'Critical',
email_configs: [{
to: 'admin-dev@example.com',
require_tls: false,
}],
},
{
name: 'Warning',
email_configs: [{
to: 'admin-dev@example.com',
require_tls: false,
}],
},
{ name: 'Default', },
{ name: 'Watchdog', },
{ name: 'null', },
],
},
},
prometheus+: {
namespaces+: ['rook-ceph'],
},
},
grafana+: {
networkPolicy+: {
spec+: {
ingress+: [{
from: [{
namespaceSelector: {
matchLabels: {
'name': 'ingress-nginx',
},
},
}],
ports: [{
port: 3000,
protocol: "TCP",
}],
}],
},
},
deployment+: { [70/1857]
spec+: {
template+: {
spec+: {
containers: std.map(
function(container)
if container.name == "grafana" then
container {
readinessProbe+: {
httpGet+: {
path: "/grafana/api/health"
},
},
volumeMounts+: [
{
mountPath: "/grafana-dashboard-definitions/0/ceph-cluster",
name: "grafana-dashboard-ceph-cluster",
readOnly: false,
},
{
mountPath: "/grafana-dashboard-definitions/0/ceph-osd",
name: "grafana-dashboard-ceph-osd",
readOnly: false,
},
{
mountPath: "/grafana-dashboard-definitions/0/ceph-pool",
name: "grafana-dashboard-ceph-pools",
readOnly: false,
},
],
}
else
container,
super.containers
),
volumes+: [
{
configMap: {
name: "grafana-dashboard-ceph-cluster",
},
name: "grafana-dashboard-ceph-cluster",
},
{
configMap: {
name: "grafana-dashboard-ceph-osd",
},
name: "grafana-dashboard-ceph-osd",
},
{
configMap: {
name: "grafana-dashboard-ceph-pools",
},
name: "grafana-dashboard-ceph-pools",
},
],
},
},
},
},
},
prometheus+: { [10/1857]
networkPolicy+: {
spec+: {
ingress+: [{
from: [{
podSelector: {
matchLabels: {
'app.kubernetes.io/name': 'prometheus-adapter',
},
},
}],
ports: [{
port: 9090,
protocol: "TCP",
}],
}],
},
},
},
kubernetesControlPlane+: {
prometheusRule+: {
spec+: {
groups: std.map(
function(group)
if group.name == 'kubernetes-storage' then
group {
rules: std.map(
function(rule)
if rule.alert == 'KubePersistentVolumeInodesFillingUp' then
rule {
expr+: |||
unless on(namespace, persistentvolumeclaim)
kube_persistentvolumeclaim_info{storageclass="rook-cephfs"} == 1
|||
}
else
rule,
super.rules
),
}
else
group,
super.groups
),
},
},
},
};
local manifests =
{
['setup/' + resource]: kp[component][resource]
for component in std.objectFields(kp)
for resource in std.filter(
function(resource)
kp[component][resource].kind == 'CustomResourceDefinition' || kp[component][resource].kind == 'Namespace', std.objectFields(kp[compo
nent])
)
} +
{
[component + '-' + resource]: kp[component][resource]
for component in std.objectFields(kp)
for resource in std.filter(
function(resource)
kp[component][resource].kind != 'CustomResourceDefinition' && kp[component][resource].kind != 'Namespace', std.objectFields(kp[compo
nent])
)
};
local kustomizationResourceFile(name) = './manifests/' + name + '.yaml';
local kustomization = {
apiVersion: 'kustomize.config.k8s.io/v1beta1',
kind: 'Kustomization',
resources: std.map(kustomizationResourceFile, std.objectFields(manifests)),
};
manifests {
'../kustomization': kustomization,
}
テストのためまだ説明していないrequire_tls: falseの設定が含まれていますが、これはなくてもsmarthostの25番ポートに接続し、認証無しでメールを送信してくれますので不要です。
また、この構成を利用する際には grafana-dashboard-ceph-cluster などのConfigMapオブジェクトを追加している必要があります。
アップグレード (v0.12.0 → v0.13.0 → v0.14.0)
Kubernetesをv1.25.6からv1.27.7に更新したタイミングで、Prometheusもアップグレードすることにしました。
v0.12.0が動いた状態でk8sをアップグレードしても問題なく動作しているようにみえるので、必ずしも更新する必要はないと思いますが、手順を掲載しておきます。
kube-prometheusの更新
kube-prometheusでv0.13.0ブランチをcheckoutします。
$ cd kube-prometheus
$ git status
## 変更箇所を全てcommitしたら次に進む
$ git checkout main
$ git pull
$ git checkout refs/tags/v0.13.0 -b my_v0.13.0
これでmy_0.13.0ブランチに入っていることを確認してから次に進みます。
$ git branch
main
my_v0.12.0
* my_v0.13.0
examples/kustomize.jsonnet ファイルの更新
v0.12.0で利用していたファイルで上書きします。
$ git checkout my_v0.12.0 examples/kustomize.jsonnet
Rookの更新がないことを確認する
余計なアラートが上がるだけで実害はありませんが、rook.io側ではlocalrules.yamlでseverityを変更しているので、もし変更されてしまっていれば、これを確認して必要であれば適用します。
確認には、次の手順で追加したConfigMapオブジェクトを確認してください。
$ sudo kubectl -n monitoring get cm grafana-dashboard-ceph-pools grafana-dashboard-ceph-osd grafana-dashboard-ceph-cluster
NAME DATA AGE
grafana-dashboard-ceph-pools 1 118d
grafana-dashboard-ceph-osd 1 118d
grafana-dashboard-ceph-cluster 1 118d
このように表示されれば追加の手順は不要です。
Manifetsの生成
examples/kustomize.jsonnetに修正するべき点がなければ、そのままYAMLファイルを生成します。
$ make generate
git diff
などで差分から変更内容を確認します。
$ sudo kubectl apply --server-side -f manifests/setup
$ sudo kubectl wait \
--for condition=Established \
--all CustomResourceDefinition \
--namespace=monitoring
$ sudo kubectl apply -f manifests/
さいごに
jsonnet-builderを使ってkube-prometheusをカスタマイズするのは始めてでしたが、後から変更するのに比べるとかなり大変だったのは間違いありません。いまでも選択した手段が適切だったのか困惑しています。
ただその後のアラートメールの送信や、KubeSchedulerDownアラートの削除をしたことを考えるとjsonnetによるカスタマイズは適切な方法だと思います。
jsonnetは便利なツールだとは思いますが、全ての変更をjsonnetで行うのだとこの技術にこだわるよりも柔軟に変更手段を選択する方が良さそうです。
昔、LinuxでRHEL系のdistributionを選択する時に自作アプリのRPMを作成して登録しようとして大変な思いをしたことを思い出します。技術自体の目的や内容が素晴しくても、それに費す時間とのバランスも考える必要があります。
またjsonnetは学ぶ価値のある技術だと思いますが、m4マクロ並みに大変そうな雰囲気を感じています。
使いこなすには https://jsonnet.org/ のドキュメントを読んで、ある程度は裏側の処理方法を把握する必要がありそうです。
以上