↓メインコンテンツへスキップ
  1. Blogs/

Kubernetes監視入門 第4回|kubernetes_sd_configsとkube-state-metricsでPod・Nodeの状態を追う

5 分
Kubernetes Prometheus
0222-nnn
著者
0222-nnn
猫が好き
目次

概要
#

第1回はstatic_configsでNode Exporterを1台だけ固定して監視した。そこで、Kubernetes上ではPodが増減し、Service Discoveryで検出対象自体が変わるケースには対応できないことを予告していた。第3回でも、up{job=...}==0はPrometheusが既に知っているtargetへのscrape失敗しか検知できず、対象がService Discoveryで消えるケースは別問題だと明記した。

この記事では、その課題に対応するkubernetes_sd_configsを扱う。Node Exporterを2台に増やし、static_configsでServiceを指定した構成ではtargetが1件のままで、背後の2台を個別に区別できないことを実際に確認したうえで、Podを1台ずつ個別のtargetとして検出する設定に切り替える。もう1つ、Kubernetesオブジェクト自体の状態(PodのphaseやNodeのReady状態など)をメトリクス化するkube-state-metricsも導入する。

対象読者は、第1回〜第3回でPrometheus・Node Exporter・Alertmanagerの基本操作を経験した人。Prometheus OperatorやServiceMonitor、kube-prometheus-stackは扱わない(次回で扱う)。

今回使う環境
#

第1回〜第3回で使ったminikube(--driver=docker、container runtime は containerd)をそのまま使う。minikube・Kubernetes・kubectlのバージョンは変えていない。

minikube version: v1.39.0
Kubernetes (Server Version): v1.37.0
kubectl (Client Version): v1.34.5-dispatcher

今回はNode Exporterの複数pod化を確認するため、minikubeのマルチノードクラスタとしてworker nodeを1台追加する。

minikube node add -p prometheus-grafana-intro --worker
kubectl get nodes -o wide
NAME                           STATUS   ROLES           VERSION
prometheus-grafana-intro       Ready    control-plane   v1.37.0
prometheus-grafana-intro-m02   Ready    <none>          v1.37.0

Node ExporterはDaemonSetで動かしているため、新しいNodeを追加すると、そのNodeにもPodが自動で作成される。

NAME                  READY   STATUS    NODE
node-exporter-76vlm   1/1     Running   prometheus-grafana-intro
node-exporter-llsft   1/1     Running   prometheus-grafana-intro-m02

もう1つ、Kubernetesオブジェクトの状態をメトリクス化するkube-state-metricsを追加する。GitHubのReleasesで確認した最新stable版 v2.20.0(2026-08-16公開)を使う。公式のstandard manifest例のClusterRoleはそのまま使い、配置先をmonitoring Namespaceへ変更したうえで、securityContextやliveness/readinessProbeなど本番向けの項目を省略した。権限(ClusterRole)は公式standardと同じ範囲のままにしている。本番導入時は公式manifestをそのまま使うことを勧める。

kubectl apply -f kube-state-metrics/serviceaccount.yaml -f kube-state-metrics/clusterrole.yaml -f kube-state-metrics/clusterrolebinding.yaml -f kube-state-metrics/deployment.yaml -f kube-state-metrics/service.yaml
kubectl rollout status deployment/kube-state-metrics -n monitoring --timeout=60s

すべてローカルのminikube上で完結し、クラウドの課金は発生しない。

Prometheus自身にも変更を加える。kubernetes_sd_configsでKubernetes APIへpod・service・endpointsliceの一覧を問い合わせるため、専用のServiceAccountと、権限を絞ったRole・RoleBindingを追加した。

flowchart LR
    subgraph monitoring["monitoring Namespace"]
        NE1["node-exporter
(pod on node1)"] NE2["node-exporter
(pod on node2)"] KSM["kube-state-metrics"] PR["Prometheus"] end API["Kubernetes API server"] PR -- "list/watch pods, services,
endpointslices" --> API API -- "target一覧を返す" --> PR PR -- "scrape" --> NE1 PR -- "scrape" --> NE2 PR -- "scrape" --> KSM

static_configsが固定リストをそのままscrapeするのに対し、kubernetes_sd_configsはKubernetes APIをlist/watchして対象を取得し、変更へ追従する。このようにKubernetes APIを使ってtargetを検出するため、Prometheus自身にKubernetesの権限が必要になる。

static_configsで確認すると、複数podを個別に区別できない
#

第1回のNode Exporter設定は、Service名を固定targetにしていた。

scrape_configs:
  - job_name: node-exporter
    static_configs:
      - targets:
          - node-exporter:9100

Node Exporterが1台のときはこれで問題なかった。ここでNode Exporterを2台にした状態のまま、このジョブを維持してupを確認する。

curl -s --data-urlencode 'query=up{job="node-exporter"}' http://localhost:9090/api/v1/query | jq '.data.result[] | {instance: .metric.instance, value: .value[1]}'
{
  "instance": "node-exporter:9100",
  "value": "1"
}

targetは相変わらず1件のままである。node-exporterはKubernetesの通常のService(ClusterIP)である。DNSはnode-exporterという名前をServiceのClusterIPへ解決するだけで、その先でServiceの転送処理(kube-proxy等のService proxy)が、背後のpodのどれか1つへ接続を振り分ける。どちらのpodが実際にscrapeされているかを、node名を含むnode_uname_infoで確認する。

curl -s --data-urlencode 'query=node_uname_info{job="node-exporter"}' http://localhost:9090/api/v1/query | jq '.data.result[] | {nodename: .metric.nodename}'
{
  "nodename": "prometheus-grafana-intro"
}

今回確認した時点では、応答したのはprometheus-grafana-intro側のNode Exporterだけで、prometheus-grafana-intro-m02側は見えなかった。static_configsは「targetの数」をprometheus.ymlの記述で固定するため、Serviceの背後にいくつpodがあっても、targetとしては常に1件である。DaemonSetでpodを増やしても、Prometheus側の設定を書き換えない限りtargetは増えない。

targetが1件しかないため、instance="node-exporter:9100"というtarget側のラベルだけでは、背後のどちらのpodが応答したかを区別できない。Serviceは接続ごとに背後のpodへ振り分けるため、応答するpodはscrapeのたびに変わる可能性もある。今回のnode_uname_infoのように、Exporter自身がNodeを識別するラベルを公開しているメトリクスであれば、その結果から応答元を確認できる場合はあるが、これはtarget自体が持つ情報ではない。

kubernetes_sd_configsを設定する
#

RBAC: Prometheus専用のServiceAccountとNamespaceを限定したRole
#

Kubernetes APIへ問い合わせるには、Prometheusのpodに対応する権限が要る。今回はkubernetes_sd_configsのnamespaces.namesで検出範囲をmonitoring Namespaceに絞るため、クラスタ全体の権限を持つClusterRoleではなく、monitoring Namespace内だけのRoleで足りる。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: prometheus
  namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: prometheus
  namespace: monitoring
rules:
  - apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["discovery.k8s.io"]
    resources: ["endpointslices"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: prometheus
  namespace: monitoring
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: prometheus
subjects:
  - kind: ServiceAccount
    name: prometheus
    namespace: monitoring

podsだけでなくservicesも必要になる。endpointslice roleを設定した直後はpodsとendpointslicesだけを許可していたが、Prometheusのログに次のエラーが出た。

level=ERROR msg="Failed to watch" err="failed to list *v1.Service: services is forbidden: User \"system:serviceaccount:monitoring:prometheus\" cannot list resource \"services\" in API group \"\" in the namespace \"monitoring\""

kubernetes_sd_configの仕様にある通り、endpoints・endpointslice roleは対象のServiceにも紐づく情報(role: serviceのラベル)を付与するため、Service自体の一覧取得権限も要る。ここではpods・services・endpointslicesにget・list・watchを付与した構成を示しており、verb単位でこれ以上削れるかどうかまでは検証していない。このRoleを、Prometheus自身のServiceAccountにRoleBindingで結び付け、DeploymentのserviceAccountNameに指定した。

このRoleBindingが付与する権限はmonitoring Namespace内に限られる。次のcan-iでは、Podのlistがmonitoringで許可され、kube-systemでは拒否されることを確認した(RBACは加算方式であり、別のRoleBinding・ClusterRoleBindingが存在すれば追加で権限が付与される。ここではこのServiceAccountに他の紐付けを作っていないことを前提にしている)。

kubectl auth can-i list pods --as=system:serviceaccount:monitoring:prometheus -n monitoring
kubectl auth can-i list pods --as=system:serviceaccount:monitoring:prometheus -n kube-system
yes
no

role: podでNode Exporterを検出する
#

kubernetes_sd_configsにはroleがあり、何を検出対象にするかを決める。今回はpod単位で検出するrole: podを使う。

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
        namespaces:
          names:
            - monitoring
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: "true"
      - source_labels: [__meta_kubernetes_pod_node_name]
        target_label: node
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: pod

role: podは、Namespace内の全podをcontainerのポート単位で検出する。今回はmonitoring内にPrometheus自身やGrafana、kube-state-metricsのpodも同居しているため、Node Exporterだけを対象にする絞り込みが要る。

ここで使ったprometheus.io/scrapeというannotation名は、Prometheusのkubernetes_sd_config設定例にコメントアウトされた形で載っている、コミュニティの慣習であって、Prometheus自体の仕様ではない。kubernetes_sd_configのドキュメントにはannotationベースのフィルタリングを自動で行う機能の記載はなく、relabel_configsで自分で書く必要がある。今回はNode ExporterのDaemonSetにprometheus.io/scrape: "true"というannotationを追加し、それをkeepするrelabel_configsで絞り込んだ。

ここまでの設定を適用する。prometheus-configmap.yamlには、後述するkube-state-metrics用のkubernetes-endpointslicesジョブも合わせて含めてある(内容は「kube-state-metricsを使う」で説明する)。以降のコマンドは、この記事のsample/ディレクトリを作業ディレクトリとして実行する。

kubectl apply -f prometheus-rbac/serviceaccount.yaml -f prometheus-rbac/role.yaml -f prometheus-rbac/rolebinding.yaml
kubectl apply -f node-exporter/daemonset.yaml
kubectl apply -f prometheus-configmap.yaml -f prometheus-deployment.yaml
kubectl rollout status deployment/prometheus -n monitoring --timeout=60s
kubectl rollout status daemonset/node-exporter -n monitoring --timeout=60s

serviceAccountNameの追加はPodテンプレートを変えるため、Prometheusのpodが置き換わる。それまで使っていたkubectl port-forwardは接続が切れるので、確認を続ける場合は port-forward をやり直す。

__address__(scrape先のIP:port)は、containerが宣言しているポート番号からPrometheusが自動的に組み立てる。今回はrelabel_configsで__address__を書き換えていないが、実際にどう組み立てられたかを確認する。

curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job=="kubernetes-pods") | {address: .discoveredLabels.__address__, pod: .labels.pod, node: .labels.node}'
{
  "address": "192.168.157.2:9100",
  "pod": "node-exporter-76vlm",
  "node": "prometheus-grafana-intro"
}
{
  "address": "192.168.157.3:9100",
  "pod": "node-exporter-llsft",
  "node": "prometheus-grafana-intro-m02"
}

__address__にはpodのIPと、Node Exporterのcontainerが宣言している9100が自動的に入っている。Node ExporterはhostNetwork: trueで動くため、pod自身がnodeのネットワークを共有しており、このpod IPはnode自体のIPと一致する。

upを確認すると、2台とも個別のtargetとしてUPになっている。

curl -s --data-urlencode 'query=up{job="kubernetes-pods"}' http://localhost:9090/api/v1/query | jq '.data.result[] | {node: .metric.node, value: .value[1]}'
{
  "node": "prometheus-grafana-intro",
  "value": "1"
}
{
  "node": "prometheus-grafana-intro-m02",
  "value": "1"
}

node_uname_infoも、static_configsのときは1件だけだったが、今回は2台とも見える。

curl -s --data-urlencode 'query=node_uname_info{job="kubernetes-pods"}' http://localhost:9090/api/v1/query | jq '.data.result[] | {nodename: .metric.nodename}'
{
  "nodename": "prometheus-grafana-intro"
}
{
  "nodename": "prometheus-grafana-intro-m02"
}

static_configsのServiceでは1台分しか見えなかった2台目のNode Exporterが、kubernetes_sd_configsでは個別のtargetとして見えている。

Podが再作成されても、設定を変えずに追従する
#

Service Discoveryのもう1つの利点は、pod自体が再作成されて変わっても、Prometheusの設定を書き換えずに追従することである。これをkube-state-metricsのpodで確認する(Node ExporterはhostNetwork: trueでnodeのネットワークを共有するため、今回の環境ではpod再作成の前後でアドレスが変わらない。kube-state-metricsは通常のpodなので、今回の再作成ではIPが変わった。ただしIPが必ず変わるとは限らず、CNIの実装によってはIPが再利用されることもある)。

「Prometheusの設定を書き換えずに」という主張を確かめるため、Prometheus自身のpod名・UID・コンテナの再起動回数を、kube-state-metricsのpod再作成の前後で記録する。これが変わっていなければ、Prometheusは再起動もPod置き換えも起きていない。

kubectl get pod -n monitoring -l app=prometheus -o jsonpath='{.items[0].metadata.name}{"\t"}{.items[0].metadata.uid}{"\t"}{.items[0].status.containerStatuses[0].restartCount}{"\n"}'
kubectl get pod -n monitoring -l app=kube-state-metrics -o jsonpath='{.items[0].metadata.name}{"\t"}{.items[0].status.podIP}{"\n"}'
prometheus-5b5dcb7bb6-kmwxj	8ba98d10-bc11-403c-9471-6f4c4b09e16c	0
kube-state-metrics-85c5756d4c-csbqk	10.244.0.25

kube-state-metricsのpodだけを削除する。対象は検証用のkube-state-metrics podのみで、Deploymentが自動的に新しいpodを作るため、影響は一時的なメトリクス欠測にとどまる。削除直後は新しいpodがまだPendingだったりpodIPが空だったりするため、Readyになるまで待ってから確認する。

kubectl delete pod -n monitoring -l app=kube-state-metrics
kubectl wait --for=condition=Ready pod -l app=kube-state-metrics -n monitoring --timeout=60s

再作成後のpodと、Prometheus側の状態を同じ形式で確認する。

kubectl get pod -n monitoring -l app=kube-state-metrics -o jsonpath='{.items[0].metadata.name}{"\t"}{.items[0].status.podIP}{"\n"}'
kubectl get pod -n monitoring -l app=prometheus -o jsonpath='{.items[0].metadata.name}{"\t"}{.items[0].metadata.uid}{"\t"}{.items[0].status.containerStatuses[0].restartCount}{"\n"}'
kube-state-metrics-85c5756d4c-xlcjf	10.244.0.26
prometheus-5b5dcb7bb6-kmwxj	8ba98d10-bc11-403c-9471-6f4c4b09e16c	0

kube-state-metricsのpod名とIPは変わった(10.244.0.25 → 10.244.0.26)が、Prometheus側のpod名・UID・再起動回数はすべて一致しており、Podの置き換えやコンテナの再起動は起きていない。この間にPrometheusの設定変更や再適用の操作も行っていない。この状態でtargetを確認する。

curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job=="kubernetes-endpointslices") | {scrapeUrl, health, lastScrape}'
{
  "scrapeUrl": "http://10.244.0.26:8080/metrics",
  "health": "up",
  "lastScrape": "2026-10-01T21:50:42.579859Z"
}

target側のIPが自動的に新しいIP(10.244.0.26)へ変わっている。Prometheus自身は変化していないことを前段で確認したので、この追従はPrometheus内部でKubernetes APIをwatchし続けているkubernetes_sd_configsの動作によるものである。

kube-state-metricsを使う
#

Node Exporterが取得するのは、OS層のCPU・メモリ・ディスクといった値である。一方で、「Deploymentのreplicaが何台起動しているか」「PodがPendingのままか」といったKubernetesオブジェクト自体の状態は、Node Exporterの対象範囲に入らない。これを補うのがkube-state-metricsで、公式の説明では「Kubernetes APIのオブジェクトの状態を監視し、それをメトリクスへ変換するサービス」とされている。

Prometheusからは、kube-state-metricsも1つのExporterとして/metricsをscrapeする対象である。今回はkube-state-metricsのServiceをendpointslice roleで検出する。

      - job_name: kubernetes-endpointslices
        kubernetes_sd_configs:
          - role: endpointslice
            namespaces:
              names:
                - monitoring
        relabel_configs:
          - source_labels:
              [
                __meta_kubernetes_endpointslice_label_kubernetes_io_service_name,
                __meta_kubernetes_endpointslice_port_name,
              ]
            action: keep
            regex: kube-state-metrics;http-metrics

endpointsではなくendpointsliceを使ったのは、Kubernetesの発表でEndpoints APIがv1.33以降非推奨とされ、kubernetes_sd_configのドキュメントも「EndpointSlicesの使用とendpointslice roleへの切り替えを推奨する」としているためである。実際、今回の環境(v1.37.0)でkubectl get endpointsを実行すると、次の警告が出る。

Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice

kubernetes-endpointslicesというjob名では、monitoring Namespace内の全endpointsliceが対象になるため、Service名とport名の組み合わせでkube-state-metricsのhttp-metricsポートだけに絞り込んでいる。これはPrometheus公式の設定例で、API serverのHTTPSポートだけをkeepする形と同じ書き方である。

kube-state-metrics自身のRBACは、Prometheus用のRoleとは別物である。kube-state-metricsは「クラスタ全体のPod・Deployment・Node等の状態」を集計する役割を持つ。そのため公式のClusterRoleは、NamespaceをまたいだClusterRoleになっている。

一方、今回Prometheus用に作ったRoleは、「Prometheusがmonitoring Namespace内のpod・service・endpointsliceだけをSDで検出する」ための権限にすぎない。目的も対象範囲も、kube-state-metrics自身のRBACとは異なる。

kube-state-metricsが公開するメトリクスを確認する。

curl -s --data-urlencode 'query=kube_pod_status_phase{namespace="monitoring",phase="Running"} == 1' http://localhost:9090/api/v1/query | jq '.data.result[] | {pod: .metric.pod, value: .value[1]}'

phase="Running"というラベルだけで絞り込むと、値が0(今はRunningではない)ものも混ざる。== 1を付けて、実際にその状態にある結果だけに絞り込む。

{
  "pod": "node-exporter-s866q",
  "value": "1"
}
{
  "pod": "grafana-7d9d4457b8-lrjhj",
  "value": "1"
}
{
  "pod": "prometheus-5968cc5b5d-dnzxg",
  "value": "1"
}
{
  "pod": "kube-state-metrics-545c58f4d8-jq89m",
  "value": "1"
}

kube_pod_status_phaseは、monitoring Namespace内の各podがどのphase(Pending・Running・Succeeded・Failed・Unknown)にあるかを示す。値はそのphaseなら1、それ以外は0になる(kube-state-metricsのdocs)。Node Exporterの/metricsにはこの情報がない。

kube-state-metricsはPodだけでなく、Nodeオブジェクト自体の状態も取得する。Node ExporterはNodeのOS層のメトリクス(CPU・メモリ・ディスク)を見ているが、「NodeオブジェクトがReadyかどうか」というKubernetes APIレベルの状態は見ていない。

curl -s --data-urlencode 'query=kube_node_status_condition{condition="Ready",status="true"} == 1' http://localhost:9090/api/v1/query | jq '.data.result[] | {node: .metric.node, value: .value[1]}'
{
  "node": "prometheus-grafana-intro",
  "value": "1"
}
{
  "node": "prometheus-grafana-intro-m02",
  "value": "1"
}

この時点でもworker nodeは削除していないため、2台ともReadyとして返る。kube_node_status_conditionは、Nodeごとにcondition(Ready・MemoryPressure等)とstatus(true・false・unknown)の組み合わせごとに値を持つ(kube-state-metricsのdocs)。これもNode Exporterの対象範囲には無い、Kubernetesオブジェクトとしてのnodeの状態である。

static_configsとkubernetes_sd_configsの違い
#

static_configs kubernetes_sd_configs
targetの決め方 prometheus.ymlに固定で書く Kubernetes APIをlist/watchして取得する
Serviceの背後に複数pod targetは常に1件で、どのpodが応答したか区別できない pod単位で個別のtargetになる
pod再作成でIPが変わった場合 手動でprometheus.ymlを書き換える必要がある(Service経由なら追従する) 自動で追従する
Prometheus自身に必要な権限 不要 Kubernetes APIへのget/list/watch権限が要る
対象の絞り込み targetのリストそのものが絞り込み relabel_configsで明示的に書く

今回は扱わなかったこと
#

relabel_configsをscrape jobごとに手で書く、annotationをDaemonSetへ手で追加する、というやり方は、対象の種類が増えるほど繰り返しが多くなる。Prometheus Operatorは、この繰り返しをServiceMonitor・PodMonitorといったCRDに置き換える仕組みを提供する。この記事で作った手動構成とOperatorベースの構成がどう対応するかは、次回の記事で扱う。

元に戻す
#

前提として、kube-state-metricsの検証環境は学習用の非永続構成であり、Prometheusの時系列データはPersistentVolumeを使っていない。第2回はPromQLの検証だけで新規リソースを追加していない。第3回で追加したAlertmanager・webhook-receiverは、第3回自身の「元に戻す」で既に削除済みという前提とする。ここでの手順は、そこからさらに今回追加した分だけを戻し、第1回相当の構成(single-node、Node Exporter 1台、Service名を指定したstatic_configs)に戻すためのものである。

PrometheusのserviceAccountNameを追加・削除する変更は、Podテンプレートを変えるためDeploymentのrolloutが発生し、Prometheusのpodが置き換わる。保存先がemptyDirであるため、これによって蓄積していた時系列データは失われる。以下の手順で戻せるのはリソースの構成であり、収集済みのデータは戻せない。比較に使いたい出力は、Pod置き換えの前に控えておく。

まず、kube-state-metrics関連のリソースだけを削除する。Prometheus用のRBACはまだ消さない。PrometheusのserviceAccountNameがこの時点ではまだprometheusを参照しており、先にServiceAccountを消すと、その間にPrometheusのpodが再作成される状況が起きた場合に、存在しないServiceAccountを参照してpod作成に失敗しうるためである。

# 対象はmonitoring Namespace内のkube-state-metrics関連リソースのみで、影響は検証環境に留まる。削除され消えたリソースも、必要ならこの記事のManifestを再適用して作り直せる
kubectl delete deployment kube-state-metrics -n monitoring
kubectl delete service kube-state-metrics -n monitoring
kubectl delete clusterrolebinding kube-state-metrics
kubectl delete clusterrole kube-state-metrics
kubectl delete serviceaccount kube-state-metrics -n monitoring
# ここまでで影響は検証環境のkube-state-metricsだけに留まる

次に、Node ExporterのDaemonSetと、Prometheusのconfigmap・deploymentを第1回のManifestへ再適用する。Kubernetesの3-way mergeはlast-applied-configurationとの差分をもとに削除対象を決める差分チェックであり、kubectl patchなどapplyを経由しない変更を保証して消す仕組みではない。

kubectl apply -f ../../2026_09_25_prometheus_grafana_minikube_basics/sample/node-exporter/daemonset.yaml
kubectl apply -f ../../2026_09_25_prometheus_grafana_minikube_basics/sample/prometheus/configmap.yaml
kubectl apply -f ../../2026_09_25_prometheus_grafana_minikube_basics/sample/prometheus/deployment.yaml

このあとserviceAccountNameを確認すると、prometheusのまま残っている。

kubectl get deployment prometheus -n monitoring -o jsonpath='{.spec.template.spec.serviceAccountName}{" / "}{.spec.template.spec.serviceAccount}{"\n"}'
prometheus / prometheus

serviceAccountNameを今回のManifestに書いていないのに消えていない。原因は非推奨のalias フィールドserviceAccountにある。Kubernetesのデフォルト値設定処理はserviceAccountNameとserviceAccountを互いに同期するため、最初にserviceAccountName: prometheusをapplyした時点でserviceAccount側にも値が複製されている。serviceAccountはどのManifestにも書いていないためlast-applied-configurationに残らず、3-way mergeでは消す対象として認識されない。確実に消すには、両方のフィールドを明示的に指定する。

kubectl patch deployment prometheus -n monitoring --type=json \
  -p='[{"op":"remove","path":"/spec/template/spec/serviceAccount"},{"op":"remove","path":"/spec/template/spec/serviceAccountName"}]'
kubectl rollout status deployment/prometheus -n monitoring --timeout=60s
deployment.apps/prometheus patched
deployment "prometheus" successfully rolled out

PrometheusがserviceAccountNameを参照しなくなったことを確認できたので、ここでPrometheus用のRBACを削除しても影響はない。

# 上の出力が空であることを確認してから削除する。空ならPrometheusはもうこのRBACを参照しておらず、削除しても影響はない
kubectl get deployment prometheus -n monitoring -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}'
kubectl delete rolebinding prometheus -n monitoring
kubectl delete role prometheus -n monitoring
kubectl delete serviceaccount prometheus -n monitoring

最後に、追加したworker nodeを削除する。

minikube node delete -p prometheus-grafana-intro prometheus-grafana-intro-m02
🔥  Deleting node prometheus-grafana-intro-m02 from cluster prometheus-grafana-intro
✋  Stopping node "prometheus-grafana-intro-m02"  ...
🛑  Powering off "prometheus-grafana-intro-m02" via SSH ...
🔥  Deleting "prometheus-grafana-intro-m02" in docker ...
💀  Node prometheus-grafana-intro-m02 was successfully deleted.

まとめ
#

static_configsは、対象が固定されている環境では単純で分かりやすい。一方、Kubernetes上でPodが増減する環境では、Serviceの背後に複数のpodがいてもtargetとしては1件のままであり、どのpodが実際に応答したかを区別できない。kubernetes_sd_configsはKubernetes APIをlist/watchして対象を取得し、pod単位のtargetを自動的に作る。今回確認したケースでは、pod再作成でIPが変わっても、Prometheus自身を再起動・再設定せずにtargetが追従した。

この仕組みを使うには、Prometheus自身にKubernetes APIへの権限(RBAC)が要る。今回は検出範囲をmonitoring Namespaceに絞ったため、ClusterRoleではなくRoleで済んだ。一方、クラスタ全体のオブジェクトを見るkube-state-metrics自身はClusterRoleが要る。両者は目的も対象も別物である。

次回は、この手動構成をPrometheus OperatorのServiceMonitor・PodMonitorに置き換えると何が変わるかを扱う。

参考資料
#

関連記事

Alerting入門 第3回|Prometheus Alerting rulesとAlertmanagerで通知を送る
4 分
Kubernetes Prometheus Alertmanager
PromQL入門 第2回|チートシート形式で早引きするrate・increase・topk・histogram_quantile
6 分
Kubernetes Prometheus PromQL
Prometheus・Grafana入門 第1回|minikubeでメトリクス収集からダッシュボード作成まで
8 分
Kubernetes Prometheus Grafana Minikube
GKEでPodの退避を起こしたら、監視に必要なメトリクスがCloud Monitoringに来なかった
8 分
Terraform GoogleCloud GKE Kubernetes CloudLogging CloudMonitoring
TerraformでGKE Standard ClusterとSpot Node Poolを作成してみた
3 分
Terraform GoogleCloud Kubernetes
nginx WebサーバーでのDocker Live Restore検証
8 分
Docker Kubernetes