Featured image of post Kubernetesクラスタにkube-state-metricsとcAdvisorをぶちこんでPrometheusで監視する

Kubernetesクラスタにkube-state-metricsとcAdvisorをぶちこんでPrometheusで監視する

PrometheusでKubernetesとコンテナのメトリクスを取るところまで

やったことのまとめ

自分は普段すでに監視用アドオンが入った状態のマネージドKubernetesクラスタを使うのが当たり前になっている。
それがどんなにありがたいことか分かってない気がしたのと、
単純にそれらの構成がどうなっているのか興味があったので実際に手を動かして設定してみた。

  • お試し用にkindでKubernetesクラスタ作成
  • kube-state-metrics(Deployment)でKubernetesメトリクスを収集
  • cAdvisor(DaemonSet)でコンテナメトリクスを収集
  • Prometheus(Deployment)にService Discovery設定をして上記2種のメトリクスをモニタリング

構成

つかうもの

やったこと

Kubernetesクラスタの作成

今回は特に構成にこだわりもないので適当にkindでシングルノードのKubernetesクラスタをつくる。

$ kind create cluster

Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.25.3) 🖼
 ✓ Preparing nodes 📦
 ✓ Writing configuration 📜
 ✓ Starting control-plane 🕹️
 ✓ Installing CNI 🔌
 ✓ Installing StorageClass 💾
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂

kube-state-metricsのインストール

Kubernetesリソース(Podとか)のメトリクスといったらkube-state-metricsって感じなのでまずはこいつを入れていく。

サンプルマニフェストが公開されているのでそれを使ってみる。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/service-account.yaml && \
  kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/cluster-role.yaml && \
  kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/cluster-role-binding.yaml && \
  kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/deployment.yaml && \
  kubectl apply -f https://raw.githubusercontent.com/kubernetes/kube-state-metrics/master/examples/standard/service.yaml

serviceaccount/kube-state-metrics created
clusterrole.rbac.authorization.k8s.io/kube-state-metrics created
clusterrolebinding.rbac.authorization.k8s.io/kube-state-metrics created
deployment.apps/kube-state-metrics created
service/kube-state-metrics created

この手順でマニフェストをapplyするとkube-state-metricsがDeploymentとして動作する。
Serviceも一緒に作られているのでport-forwardしてメトリクスを見てみる。

$ kubectl -n kube-system get deploy,service -l app.kubernetes.io/name=kube-state-metrics

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kube-state-metrics   1/1     1            1           115s

NAME                         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE
service/kube-state-metrics   ClusterIP   None         <none>        8080/TCP,8081/TCP   115s

$ kubectl -n kube-system port-forward service/kube-state-metrics 8080:8080 8081:8081
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Forwarding from 127.0.0.1:8081 -> 8081
Forwarding from [::1]:8081 -> 8081
...

# from different terminal
$ curl localhost:8080/metrics
...
# HELP kube_configmap_labels [STABLE] Kubernetes labels converted to Prometheus labels.
# TYPE kube_configmap_labels gauge
kube_configmap_labels{namespace="local-path-storage",configmap="local-path-config"} 1
kube_configmap_labels{namespace="default",configmap="kube-root-ca.crt"} 1
...

こんな感じでkube-state-metricsはKubernetes APIをポーリングして得られたPodのステータスとかの情報をメトリクス化している。らしい。

ちなみにメトリクスの詳細な説明はdocsで公開されている。

実はUIも用意されているが、今回の目的ではないので割愛。

http://localhost:8080/

/metricsはメトリクスを表示する /healthzはヘルスチェック用

http://localhost:8081/

こちらの/metricsはkube-state-metrics自体のメトリクス(Go関連とか)を表示する

cAdvisorのインストール

cAdvisorもコンテナ単位のメトリクスが見られて便利なのでこれも入れていく。

こちらはサンプルマニフェストがkustomize化されているのでさらに簡単に入れられる。

$ kubectl apply -k https://github.com/google/cadvisor//deploy/kubernetes/base

namespace/cadvisor created
serviceaccount/cadvisor created
daemonset.apps/cadvisor created

cAdvisorは各Node上のコンテナ情報を取得するためDaemonSetとして動作している。
(昔はkubeletに勝手に入ってた気がするが、最近はDaemonSetで動かすのが普通っぽい?)

kube-state-metricsと同様にport-forwardしてメトリクスを確認する。

$ kubectl -n cadvisor get daemonset -l app=cadvisor

NAME       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
cadvisor   1         1         1       1            1           <none>          6m54s

$ pod=`kubectl -n cadvisor get pod -l app=cadvisor -o jsonpath="{.items[0].metadata.name}"`
$ kubectl -n cadvisor port-forward pod/"${pod}" 8080:8080

Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080

# from different terminal
$ curl localhost:8080/metrics

# HELP cadvisor_version_info A metric with a constant '1' value labeled by kernel version, OS version, docker version, cadvisor version & cadvisor revision.
# TYPE cadvisor_version_info gauge
cadvisor_version_info{cadvisorRevision="86b11c65",cadvisorVersion="v0.45.0",dockerVersion="",kernelVersion="5.10.124-linuxkit",osVersion="Alpine Linux v3.16"} 1
# HELP container_blkio_device_usage_total Blkio Device bytes usage
# TYPE container_blkio_device_usage_total counter
container_blkio_device_usage_total{container_label_app="",container_label_app_kubernetes_io_component="",container_label_app_kubernetes_io_name="",container_label_app_kubernetes_io_version="",container_label_component="",container_label_controller_revision_hash="",container_label_description="",container_label_io_cri_containerd_kind="",container_label_io_kubernetes_container_name="",container_label_io_kubernetes_pod_name="",container_label_io_kubernetes_pod_namespace="",container_label_io_kubernetes_pod_uid="",container_label_k8s_app="",container_label_maintainers="",container_label_name="",container_label_pod_template_generation="",container_label_pod_template_hash="",container_label_tier="",device="/dev/vda",id="/",image="",major="254",minor="0",name="",operation="Read"} 0 1669642901054
...

container_**みたいな名前のメトリクスがとれていることがわかる。

こちらも実はUIが用意されているが割愛。

http://localhost:8080/

ふくろうかわいい

Prometheusのインストール

Kubernetesリソースとコンテナのメトリクスが用意できたので、
次にこれをモニタリングするPrometheusを用意する。

ちょっと探したけどPrometheusについては公式っぽいKubernetesマニフェスト例が見つからなかったので自分でちょろっと書いて立ててみる。

$ kubectl create namespace prometheus

namespace/prometheus created

$ kubectl -n prometheus apply -f deployment.yaml

deployment.apps/prometheus created

$ kubectl -n prometheus expose deployment prometheus --port=9090 --target-port=9090

service/prometheus exposed

今回は特にメトリクスを永続化したいわけではなかったのでPrometheusをDeploymentとして立てた。
(メトリクスを永続化したいときはStatefulSetで立てたり、クラスタ外にFederateしたりいろいろやり方はあるとおもう)

これも同様にport-forwardしてUIを開いて動作確認する。

$ kubectl -n prometheus port-forward service/prometheus 9090:9090

Forwarding from 127.0.0.1:9090 -> 9090
Forwarding from [::1]:9090 -> 9090

http://localhost:9090/

いつもの

いい感じ。

Prometheusの監視設定(kubernetes_sd_config)

最後にこのPrometheusでkube-state-metricsとcAdvisorのメトリクスが取れるように設定していく。

Kubernetesクラスタ内のメトリクスをPrometheusで監視する場合static_configにいちいちServiceやPodのIPを羅列していては日が暮れてしまうし、
それらが変化してしまったときに動的に追従させるのが大変というかたぶん無理。

このため通常はkubernetes_sd_configを使ってクラスタ内のServiceやPodの宛先をPrometheusが勝手に探すようにする(Service Discovery)。

これについては公式の例があるのでこちらを参考にさせてもらう。

今回はrelabel_configsの設定でモニタリングの対象をkube-state-metricsのServiceとcAdvisorのDaemonSet(Pod)に絞っている。
(role: podのsd設定を入れると全Podのメトリクスが対象となりrole: serviceのsd設定で対象となっているメトリクスと重複するため)

$ kubectl -n cadvisor get pods --show-labels

NAME             READY   STATUS    RESTARTS   AGE   LABELS
cadvisor-fngj6   1/1     Running   0          23h   app=cadvisor,controller-revision-hash=df8bf66b4,name=cadvisor,pod-template-generation=1

$ kubectl -n kube-system get service kube-state-metrics --show-labels

NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)             AGE   LABELS
kube-state-metrics   ClusterIP   None         <none>        8080/TCP,8081/TCP   24h   app.kubernetes.io/component=exporter,app.kubernetes.io/name=kube-state-metrics,app.kubernetes.io/version=2.7.0

$ kubectl apply -f configmap.yaml

configmap/prometheus-config created

ついでにこのConfigMapを読み込むようにDeploymentの設定も修正する。

$ kubectl apply -f deployment.yaml

deployment.apps/prometheus configured

この状態でPrometheusのService Discoveryの状態を確認する。

http://localhost:9090/service-discovery

とれてない

…あれ?
なんかできてなさそう。

Podのログを確認してみる。

$ pod=`kubectl -n prometheus get pods -l app=prometheus -o jsonpath="{.items[0].metadata.name}"`
$ kubectl -n prometheus logs $pod

...
ts=2022-11-28T14:26:56.150Z caller=kubernetes.go:326 level=info component="discovery manager scrape" discovery=kubernetes msg="Using pod service account via in-cluster config"
ts=2022-11-28T14:26:56.150Z caller=main.go:1234 level=info msg="Completed loading of configuration file" filename=/config/prometheus.yml totalDuration=2.798834ms db_storage=1.208µs remote_storage=1.417µs web_handler=375ns query_engine=1.042µs scrape=250.917µs scrape_sd=2.159042ms notify=1.083µs notify_sd=5.709µs rules=1.584µs tracing=12.958µs
ts=2022-11-28T14:26:56.150Z caller=main.go:978 level=info msg="Server is ready to receive web requests."
ts=2022-11-28T14:26:56.150Z caller=manager.go:944 level=info component="rule manager" msg="Starting rule manager..."
ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg="pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Service: services is forbidden: User \"system:serviceaccount:prometheus:default\" cannot list resource \"services\" in API group \"\" at the cluster scope"
ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg="pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Pod: pods is forbidden: User \"system:serviceaccount:prometheus:default\" cannot list resource \"pods\" in API group \"\" at the cluster scope"
ts=2022-11-28T14:26:56.156Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg="pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: Failed to watch *v1.Service: failed to list *v1.Service: services is forbidden: User \"system:serviceaccount:prometheus:default\" cannot list resource \"services\" in API group \"\" at the cluster scope"
ts=2022-11-28T14:26:56.156Z caller=klog.go:108 level=warn component=k8s_client_runtime func=Warningf msg="pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: failed to list *v1.Endpoints: endpoints is forbidden: User \"system:serviceaccount:prometheus:default\" cannot list resource \"endpoints\" in API group \"\" at the cluster scope"
ts=2022-11-28T14:26:56.156Z caller=klog.go:116 level=error component=k8s_client_runtime func=ErrorDepth msg="pkg/mod/k8s.io/client-go@v0.25.3/tools/cache/reflector.go:169: Failed to watch *v1.Endpoints: failed to list *v1.Endpoints: endpoints is forbidden: User \"system:serviceaccount:prometheus:default\" cannot list resource \"endpoints\" in API group \"\" at the cluster scope"
...

エラーメッセージ的に"デフォルトで設定されたServiceAccount(prometheus:default)にPodとかServiceを見る権限が無いよ!“って感じなので、
ServiceAccount, ClusterRole, ClusterRoleBindingを作ってやる。

$ kubectl -n prometheus create serviceaccount prometheus

serviceaccount/prometheus created

$ kubectl create clusterrole prometheus --verb=get,list,watch --resource=pods,services,endpoints

clusterrole.rbac.authorization.k8s.io/prometheus created

$ kubectl create clusterrolebinding prometheus --clusterrole=prometheus --serviceaccount=prometheus:prometheus

clusterrolebinding.rbac.authorization.k8s.io/prometheus created

またまたDeploymentをいじって↑で権限を付与したServiceAccountを使ってPodを立てるようにする。

$ kubectl apply -f deployment.yaml

deployment.apps/prometheus configured

再度立ち上がったPrometheusを確認すると今度はService Discoveryに成功している。

とれてる

試しにPrometheus自身のPod, コンテナのメトリクスをクエリで探すと確かに値が返ってくる。

kube-state-metricsとcAdvisorのメトリクスが取れている

ええやん。

おわり

Kubernetesクラスタにkube-state-metricsとcAdvisorを導入し、
クラスタ内のPrometheusでそれらのメトリクスをモニタリングするところまでやってみた。

今回はprometheus-operatorとか使わずに全部手作業で立てたので結構面倒だったが、
たまにこうやって遊ぶと楽しいし、こういうやつを勝手に作成してくれる仕組みのありがたさがよくわかる気がする。

おまけ

!って感じのねこ