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

Kubernetes監視入門 第5回|Prometheus OperatorとServiceMonitor・PodMonitorでkube-prometheus-stackを使う

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

概要
#

第4回では、kubernetes_sd_configsとrelabel_configsを手で書き、Prometheus用のRBACも自分で用意した。対象がNode Exporterとkube-state-metricsの2種類だけでもこれだけの記述が必要だった。監視対象が増えるほどkubernetes_sd_configsやrelabel_configsの管理対象も増え、実運用では保守負荷が高くなる。

Prometheus Operatorは、この繰り返しをCRD(Custom Resource Definition)で定義された型のリソースに置き換える。ServiceMonitor・PodMonitor・PrometheusRuleといったCustom Resourceを作るだけで、Operatorがそれらを読み取り、対応するPrometheus設定を生成する。この記事では、kube-prometheus-stack(Prometheus OperatorとPrometheus本体・Alertmanager・kube-state-metricsなどをまとめたHelm chart)を導入し、第1回〜第4回で手で書いた設定がこれらのCustom Resourceでどう表現されるかを、実際に生成された設定と見比べながら確認する。

対象読者は、第1回〜第4回でPrometheusのkubernetes_sd_configs・alerting rule・RBACを手で書いた経験がある人。Prometheus Operator・kube-prometheus-stackに初めて触れる人を想定している。GKEやGoogle Cloud Managed Service for Prometheusは次回(第6回)で扱う。Alertmanagerの高可用構成、long-term storage、Grafana Operatorなど追加コンポーネントは扱わない。

今回使う環境
#

第1回〜第4回で使っているminikube(--driver=docker、container runtime は containerd)をそのまま使う。minikube・Kubernetes・kubectlのバージョンは変えていない。開始時点は第4回の「元に戻す」を終えた状態、つまりsingle-nodeで、monitoring NamespaceにNode Exporter・Prometheus(static_configs構成)・Grafanaだけが動いている状態を前提とする。第4回で追加したkube-state-metrics・Prometheus用RBAC・kubernetes_sd_configsは、比較のために参照はするが、今回の開始時点では存在しない。

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

kubectlのクライアント版(1.34)とサーバー版(1.37)は、Kubernetesのversion skew policyが定める前後1マイナーバージョンの範囲を超えている。第1回から変えていない既知の状態であり、この記事内のコマンドはいずれも動作したが、再現する場合はサーバーと同じ1.37系のkubectlを使うことを勧める。

Prometheus Operatorの導入にはHelmを使う。

version.BuildInfo{Version:"v3.14.3", ...}

kube-prometheus-stackはprometheus-community/helm-chartsのHelm chartで、chart version 91.8.2(Prometheus Operator v0.94.1)を使う。

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm search repo prometheus-community/kube-prometheus-stack --versions | head -1
prometheus-community/kube-prometheus-stack	91.8.2       	v0.94.1    	kube-prometheus-stack collects Kubernetes manif...

第1回〜第4回の手動構成(monitoring Namespace)はそのまま残し、kube-prometheus-stackは別Namespace(monitoring-operator)へ導入する。chartが作るリソース名にはrelease名の接頭辞が付くため名前自体は衝突しないが、同じNamespaceに手動構成とOperator管理の両方のPrometheus・Alertmanagerが混在すると、どちらの構成を見ているか分かりにくくなる。そのため今回はNamespaceを分けて区別しやすくした(後述する通り、hostNetworkを使うNode ExporterのDaemonSetはNamespaceを分けてもポートの競合を避けられない)。

kubectl create namespace monitoring-operator

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

flowchart LR
    subgraph manual["monitoring Namespace(第1回〜第4回・手動構成)"]
        NESVC["node-exporter Service"]
        NE["node-exporter Pod"]
        GF["grafana Pod"]
        PR1["prometheus
(手動構成)"] end subgraph op["monitoring-operator Namespace(本回)"] OP["kube-prometheus-stack
-operator"] PR2["prometheus
(Operator管理)"] AM["alertmanager"] KSM2["kube-state-metrics"] end SM["ServiceMonitor"] PM["PodMonitor"] RULE["PrometheusRule"] OP -- "watch" --> SM OP -- "watch" --> PM OP -- "watch" --> RULE OP -- "設定を生成" --> PR2 SM -- "selector" --> NESVC NESVC -.->|"Endpoints/EndpointSlice"| NE PM -- "selector" --> GF PR2 -- "scrape" --> NE PR2 -- "scrape" --> GF

Prometheus Operatorとは
#

Prometheus Operatorは、PrometheusやAlertmanagerの構成・運用をKubernetesのCustom Resourceで宣言的に管理する仕組みである。kube-prometheus-stackをインストールすると、次のCRDが追加される。

kubectl get crd | grep monitoring.coreos.com
alertmanagerconfigs.monitoring.coreos.com
alertmanagers.monitoring.coreos.com
podmonitors.monitoring.coreos.com
probes.monitoring.coreos.com
prometheusagents.monitoring.coreos.com
prometheuses.monitoring.coreos.com
prometheusrules.monitoring.coreos.com
scrapeconfigs.monitoring.coreos.com
servicemonitors.monitoring.coreos.com
thanosrulers.monitoring.coreos.com

このうち今回扱うのは3つである。

  • ServiceMonitor: Serviceをラベルで選び、そのServiceのEndpoints / EndpointSliceを通じて監視対象を発見する。第4回でkube-state-metrics向けに使ったrole: endpointsliceのkubernetes_sd_configsに近い
  • PodMonitor: Serviceを介さずPodを直接選ぶ。第4回でNode Exporter向けに使ったrole: podのkubernetes_sd_configsに近い
  • PrometheusRule: 第3回のalerting ruleに相当する。alert / expr / for / labels / annotationsはそのまま使える

Prometheus・Alertmanagerも同様にCRDで定義された型で、そのCustom Resourceは、Prometheus・Alertmanagerそのものの構成(レプリカ数やバージョンなど)を宣言するためのものである。今回はkube-prometheus-stackが作成したものをそのまま使う。

kube-prometheus-stackを導入する
#

helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --version 91.8.2 \
  --namespace monitoring-operator \
  --set grafana.enabled=false

Grafanaは第1回で導入済みのものをそのまま使うため、chartに含まれるGrafanaは無効にした(grafana.enabled=false)。

minikubeは1 Nodeのため、chartが標準で導入するNode ExporterのDaemonSetは、第1回から動いている手動のNode Exporterと同じhostNetwork・ポート9100を使おうとして衝突する。

Warning  FailedScheduling  0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.

今回はNode Exporterの比較が目的ではないため、chart側のNode Exporterを無効にする。最初にprometheus-node-exporter.enabled=falseを試したが、PodはPendingのままで効果がなかった。values.yamlを確認すると、prometheus-node-exporter:はサブchartへ渡す設定のまとまりで、有効・無効の切り替えには使えない。サブchart自体の有効・無効は、Chart.yamlのcondition: nodeExporter.enabledが指すnodeExporter.enabledで決まる。

helm upgrade kube-prometheus-stack prometheus-community/kube-prometheus-stack \
  --version 91.8.2 \
  --namespace monitoring-operator \
  --set grafana.enabled=false \
  --set nodeExporter.enabled=false

導入後のPodを確認する。

kubectl get pods -n monitoring-operator
NAME                                                        READY   STATUS    RESTARTS   AGE
alertmanager-kube-prometheus-stack-alertmanager-0           2/2     Running   0          89s
kube-prometheus-stack-kube-state-metrics-78dc75b868-glhlb   1/1     Running   0          96s
kube-prometheus-stack-operator-8494f9cfd4-66lwj             1/1     Running   0          96s
prometheus-kube-prometheus-stack-prometheus-0               2/2     Running   0          89s

導入しただけで、ServiceMonitorとPrometheusRuleがすでに多数作られている。件数は、このHelm releaseが実際に作成したマニフェストを数える(クラスタ全体の一覧では、他の要因で増減した数と区別できない)。

helm get manifest kube-prometheus-stack -n monitoring-operator | grep -c "^kind: ServiceMonitor$"
helm get manifest kube-prometheus-stack -n monitoring-operator | grep -c "^kind: PrometheusRule$"
11
35

apiserver・coredns・kube-state-metrics・kubeletなど、Kubernetesのコンポーネントを監視するためのServiceMonitorと、ディスク逼迫やメモリ枯渇などを検知するPrometheusRuleが、Helm chartの一部として最初から入っている。この件数は、今回grafana.enabled=false・nodeExporter.enabled=falseにした構成でのものであり、既定値のまま導入した場合はGrafana用・Node Exporter用のServiceMonitorがさらに増える。第1回〜第4回でNode Exporter 1種類のscrape設定とalerting rule 1つを手で書いたのに対し、ここではインストール1回でこれだけの数が揃う。

ServiceMonitorで手動構成を置き換える
#

第4回で、Node Exporterを検出するために次の設定を書いた。

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"

第4回のこの設定はrole: podでNode ExporterのPodを直接発見していた。ServiceMonitorはServiceを介して発見する仕組みのため、これは同じ設定をOperator形式へそのまま書き換えるのではなく、発見方法自体をPod直接検出からServiceベースの検出へ変える、という違いがある(role: podに近い書き方は後述のPodMonitorで扱う)。ServiceMonitorでは、Node ExporterのServiceを指すラベルセレクタを書くだけでよい。ただし、既存のNode ExporterのServiceには、ServiceMonitorで選択できるラベルが付いていないため、まずラベルを1つ追加する(既存のSelector・Portは変更しない、追加のみ)。

kubectl label service node-exporter -n monitoring app=node-exporter
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: node-exporter
  namespace: monitoring
  labels:
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app: node-exporter
  endpoints:
    - targetPort: 9100

endpoints[].portはServiceの名前付きport(.spec.ports[].name)を指す。今回のNode ExporterのServiceには名前付きportが無いため、Pod側のcontainer port番号を直接指すtargetPortを使った。Serviceに名前付きportがある場合はportを使う方が、Service定義の変更に追従しやすい。

labels.release: kube-prometheus-stackが無いと、このServiceMonitorはPrometheusから認識されない。最初はこのラベルを付けずに試し、targetが増えないことを確認した。

kubectl get prometheus kube-prometheus-stack-prometheus -n monitoring-operator \
  -o jsonpath='{.spec.serviceMonitorSelector}{"\n"}'
{"matchLabels":{"release":"kube-prometheus-stack"}}

kube-prometheus-stackのchartにはserviceMonitorSelectorNilUsesHelmValues: trueという設定があり、serviceMonitorSelectorを明示的に指定しない(=値がnilのまま)場合、chartのテンプレートがrelease: <リリース名>というラベル条件を自動で生成する。values.yamlでserviceMonitorSelector: {}(全選択)という例も示されているが、これは明示的に{}を指定した場合の話であり、何も指定しない既定の状態とは異なる。podMonitorSelector・ruleSelectorにも同様の*SelectorNilUsesHelmValuesがあり、Prometheus Operatorの仕様として必須なのではなく、kube-prometheus-stackのこの既定値によって、自作するServiceMonitor・PodMonitor・PrometheusRuleそれぞれにこのリリースのラベルを付ける必要がある。

ここから先のManifestは、この記事のsampleディレクトリに収録している。リポジトリルートからcd content/blog/2026_10_02_prometheus_operator_kube_prometheus_stack/sampleを実行し、以降の相対パスのコマンドはこのディレクトリで実行する。

ラベルを付けたうえでkubectl applyし、targetを確認する。Operator管理のPrometheus(monitoring-operator Namespace)へ、第1回〜第4回の手動Prometheusとは別のポートでport-forwardする。

kubectl port-forward -n monitoring-operator svc/kube-prometheus-stack-prometheus 9091:9090
kubectl apply -f servicemonitor/node-exporter.yaml
curl -s http://localhost:9091/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job=="node-exporter") | {health, scrapeUrl, labels}'
{
  "health": "up",
  "scrapeUrl": "http://192.168.157.2:9100/metrics",
  "labels": {
    "container": "node-exporter",
    "endpoint": "9100",
    "instance": "192.168.157.2:9100",
    "job": "node-exporter",
    "namespace": "monitoring",
    "pod": "node-exporter-5nqhp",
    "service": "node-exporter"
  }
}

namespace ・pod・service・containerのラベルが自動的に付いている。第4回では、このうちnode・podの2つだけをrelabel_configsで手で書いており(namespace・service・containerは付与していなかった)、今回のServiceMonitorではこれらが既定で付く。

ここで押さえておきたいのは、ServiceMonitor(monitoring Namespace)を見つけて設定へ反映しているのはPrometheus自身ではなく、Prometheus Operatorだという点である。ServiceMonitor・PodMonitor・PrometheusRuleをwatchするのはOperatorの役割で、Operator用のClusterRoleにその権限がある。

kubectl get clusterrole kube-prometheus-stack-operator -o json | jq '.rules[] | select(.apiGroups[]? == "monitoring.coreos.com") | select(.verbs | index("watch"))'
{
  "apiGroups": ["monitoring.coreos.com"],
  "resources": [
    "alertmanagers", "alertmanagerconfigs", "podmonitors", "probes",
    "prometheusagents", "prometheuses", "prometheusrules", "servicemonitors",
    "scrapeconfigs", "thanosrulers"
  ],
  "verbs": ["get", "list", "watch"]
}

Operatorは、Prometheus Custom Resourceの2つのフィールドで対象のServiceMonitorを決める。serviceMonitorNamespaceSelectorはどのNamespaceを見るか、serviceMonitorSelectorはその中のどのServiceMonitorを選ぶかで、役割が異なる。今回の値はそれぞれ次の通りである。

kubectl get prometheus kube-prometheus-stack-prometheus -n monitoring-operator \
  -o jsonpath='{.spec.serviceMonitorNamespaceSelector}{"\n"}{.spec.serviceMonitorSelector}{"\n"}'
{}
{"matchLabels":{"release":"kube-prometheus-stack"}}

serviceMonitorNamespaceSelector: {}は「全Namespaceが対象」を意味するため、monitoring Namespaceも対象に含まれる。その中からserviceMonitorSelectorのrelease: kube-prometheus-stackに一致するServiceMonitorだけが選ばれる。Namespaceをまたいで検出できるのはNamespaceの絞り込みが無いためであり、ラベルの条件とは別の設定である。Operatorがこれらを読み取れるのは、Operator用ClusterRoleがクラスタ全体を対象にしているためである。

Prometheus自身にも別のClusterRoleがあるが、これはOperatorが生成した設定に従って実際にscrapeの対象(Service・Pod・Endpoints等)を検出するためのものであり、ServiceMonitor自体を検出する権限ではない。

kubectl get clusterrole kube-prometheus-stack-prometheus -o jsonpath='{.rules}' | jq .
[
  {
    "apiGroups": [""],
    "resources": ["nodes", "nodes/metrics", "services", "endpoints", "pods"],
    "verbs": ["get", "list", "watch"]
  },
  {
    "apiGroups": ["discovery.k8s.io"],
    "resources": ["endpointslices"],
    "verbs": ["get", "list", "watch"]
  },
  {
    "apiGroups": ["networking.k8s.io"],
    "resources": ["ingresses"],
    "verbs": ["get", "list", "watch"]
  },
  {
    "nonResourceURLs": ["/metrics", "/metrics/cadvisor"],
    "verbs": ["get"]
  }
]

第4回のRole(pods・services・endpointslicesの3種類をmonitoring Namespaceのみ)と比べ、Prometheus自身の対象もクラスタ全体に広がっている。nodes/metricsは、kubeletの/metrics/*に対するresource/subresourceの認可である(kubeletの認証・認可)。一方nonResourceURLsは、Kubernetes APIのリソースとして扱われないHTTPパスへのアクセス権を表す仕組みで(RBACの認可)、このClusterRoleでは/metrics・/metrics/cadvisorへのgetをこの形で許可している。両者はRBAC上の別種の権限である。いずれも第4回のRoleには無かった権限で、「RBACを自分で書かなくて済む」ことと「権限が最小限である」ことは別である。

ここまでの「誰が何をwatchし、何を生成し、何をscrapeするか」を時系列で整理する。

sequenceDiagram
    participant User as 利用者
    participant SM as ServiceMonitor(CR)
    participant OP as Prometheus Operator
    participant SEC as Secret(生成設定)
    participant RC as config-reloader
    participant PR as Prometheus
    participant NE as node-exporter

    User->>SM: kubectl apply
    OP->>SM: watch(get/list/watch)
    OP->>SEC: relabel_configs等を生成して書き込み
    SEC-->>RC: マウントされた設定ファイルが更新
    RC->>PR: 設定リロード(POST /-/reload)
    Note over PR: Podは再作成されない
    PR->>NE: scrape(生成された設定に従う)

ServiceMonitorを検出して設定を書くのはOperator、できあがった設定でscrapeするのはPrometheus、設定ファイルの変更を検知してリロードを指示するのはconfig-reloaderと、3つの別々の主体が関わっている。config-reloaderがwatchしているのはSecretというKubernetesオブジェクト自体ではなく、そのSecretがVolumeとしてマウントされたファイルである(本文で後述する通り、実際のログも「ファイルとディレクトリの変更を監視」としている)。この構成では、設定変更の経路にPrometheus Podの再作成を必要としない。後の節では、実際にPod UID・再起動回数が変化しないことも確認する。

生成された設定を確認する
#

ServiceMonitorから実際にどのようなPrometheus設定が生成されたかを確認する。設定はSecretとしてPrometheusにマウントされている。

kubectl get secret prometheus-kube-prometheus-stack-prometheus -n monitoring-operator \
  -o jsonpath='{.data.prometheus\.yaml\.gz}' | base64 -d | gunzip > generated-prometheus.yaml
grep -A 49 "job_name: serviceMonitor/monitoring/node-exporter/0" generated-prometheus.yaml
- job_name: serviceMonitor/monitoring/node-exporter/0
  honor_labels: false
  kubernetes_sd_configs:
  - role: endpoints
    namespaces:
      names:
      - monitoring
  relabel_configs:
  - source_labels:
    - job
    target_label: __tmp_prometheus_job_name
  - action: keep
    source_labels:
    - __meta_kubernetes_service_label_app
    - __meta_kubernetes_service_labelpresent_app
    regex: (node-exporter);true
  - action: keep
    source_labels:
    - __meta_kubernetes_pod_container_port_number
    regex: 9100
  - source_labels:
    - __meta_kubernetes_endpoint_address_target_kind
    - __meta_kubernetes_endpoint_address_target_name
    separator: ;
    regex: Node;(.*)
    replacement: ${1}
    target_label: node
  - source_labels:
    - __meta_kubernetes_endpoint_address_target_kind
    - __meta_kubernetes_endpoint_address_target_name
    separator: ;
    regex: Pod;(.*)
    replacement: ${1}
    target_label: pod
  - source_labels:
    - __meta_kubernetes_namespace
    target_label: namespace
  - source_labels:
    - __meta_kubernetes_service_name
    target_label: service
  - source_labels:
    - __meta_kubernetes_pod_name
    target_label: pod
  - source_labels:
    - __meta_kubernetes_pod_container_name
    target_label: container
  - action: drop
    source_labels:
    - __meta_kubernetes_pod_phase
    regex: (Failed|Succeeded)

このjob全体は80行(1491〜1570行目)で、relabel_configsだけで17個のルールがある。掲載したのは先頭から10個で、監視対象の絞り込みとラベル付けを行う部分である。残り7個は__tmp_hash等を使ったシャーディング(Prometheusを複数台に分ける際の分散)用の汎用ルールで、各scrape jobに共通して付く。13行のServiceMonitor(spec以下は6行)から、意味のある部分だけでも10個のルールが生成されている。第4回で手で書いたrelabel_configsはnode・podの2ルールのみで、Failed・Succeeded状態のPodを除外するルールのように、手動構成では書いていなかったものも含まれる。

一方で、kubernetes_sd_configsのroleはendpointsである。Kubernetesの発表にある通りEndpoints APIはv1.33以降非推奨で、第4回ではrole: endpointsliceを使った。これはバージョン固有の制約ではなく、Prometheus Operator v0.94.1の時点でも、ServiceMonitorのspec.serviceDiscoveryRole(またはPrometheus Custom Resourceのspec.serviceDiscoveryRole)にEndpointSliceを指定すれば、今すぐ切り替えられる。既定がEndpointsなだけである。

kubectl patch prometheus kube-prometheus-stack-prometheus -n monitoring-operator \
  --type=merge -p '{"spec":{"serviceDiscoveryRole":"EndpointSlice"}}'
prometheus.monitoring.coreos.com/kube-prometheus-stack-prometheus patched

適用後、生成設定を確認するとrole: endpointsliceに変わっている(対象はこのPrometheus配下の全ServiceMonitor)。

kubectl get secret prometheus-kube-prometheus-stack-prometheus -n monitoring-operator \
  -o jsonpath='{.data.prometheus\.yaml\.gz}' | base64 -d | gunzip | grep -A 3 "job_name: serviceMonitor/monitoring-operator/kube-prometheus-stack-kube-state-metrics"
- job_name: serviceMonitor/monitoring-operator/kube-prometheus-stack-kube-state-metrics/0
  honor_labels: true
  kubernetes_sd_configs:
  - role: endpointslice

今回は以降の確認を既定のEndpointsのまま進めるため、設定を元に戻す。

kubectl patch prometheus kube-prometheus-stack-prometheus -n monitoring-operator \
  --type=json -p '[{"op":"remove","path":"/spec/serviceDiscoveryRole"}]'

PodMonitorを使う
#

PodMonitorはServiceを介さず、Podを直接選ぶ。GrafanaはServiceを持つが、ここではPodMonitorの例として、Pod自体を対象にする。Grafanaは/metricsでPrometheus形式のメトリクスを公開している。

kubectl exec -n monitoring deployment/grafana -- wget -qO- http://localhost:3000/metrics | head -2
# HELP deprecated_flags_inuse_total The number of deprecated flags currently set.
# TYPE deprecated_flags_inuse_total counter

GrafanaのPodにはすでにapp: grafanaラベルが付いている(第1回のDeploymentで設定済み)ため、Service側のようにラベルを追加する必要はない。

apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: grafana
  namespace: monitoring
  labels:
    release: kube-prometheus-stack
spec:
  selector:
    matchLabels:
      app: grafana
  podMetricsEndpoints:
    - portNumber: 3000
kubectl apply -f podmonitor/grafana.yaml
curl -s http://localhost:9091/api/v1/targets | jq '.data.activeTargets[] | select(.scrapePool=="podMonitor/monitoring/grafana/0") | {health, scrapeUrl, labels}'
{
  "health": "up",
  "scrapeUrl": "http://10.244.0.3:3000/metrics",
  "labels": {
    "container": "grafana",
    "instance": "10.244.0.3:3000",
    "job": "monitoring/grafana",
    "namespace": "monitoring",
    "pod": "grafana-7d9d4457b8-lrjhj"
  }
}

jobラベルの既定値が、ServiceMonitorでは対象Serviceの名前(node-exporter)だったのに対し、PodMonitorでは<Namespace>/<PodMonitor名>(monitoring/grafana)になる。PodMonitorはServiceを参照しないため、jobLabelを指定しない場合のjobラベルはPodMonitor自身のNamespace・名前から決まる。

PrometheusRuleで手動のalerting ruleを置き換える
#

第3回では、Node Exporterへのscrape失敗を検知するalerting ruleを、PrometheusのConfigMapに直接書いた。

groups:
  - name: node-exporter
    rules:
      - alert: NodeExporterDown
        expr: up{job="node-exporter"} == 0
        for: 45s

PrometheusRuleでは、alert / expr / for / labels / annotationsの書き方はそのままで、Custom Resourceの形に包むだけでよい。

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: node-exporter-rules
  namespace: monitoring
  labels:
    release: kube-prometheus-stack
spec:
  groups:
    - name: node-exporter
      rules:
        - alert: NodeExporterDown
          expr: up{job="node-exporter"} == 0
          for: 45s
          labels:
            severity: warning
          annotations:
            description: 'up{job="node-exporter", instance="{{ $labels.instance }}"} has been 0 for more than 45s'
kubectl apply -f prometheusrule/node-exporter.yaml
curl -s http://localhost:9091/api/v1/rules?type=alert | jq '.data.groups[] | select(.file | contains("monitoring-node-exporter-rules")) | .rules[0] | {name, query, health}'
{
  "name": "NodeExporterDown",
  "query": "up{job=\"node-exporter\"} == 0",
  "health": "ok"
}

health: okで、ルール自体は正しく読み込まれ評価されている(pending・firingへの状態遷移そのものは第3回で確認済みのため、ここでは再現しない)。

第4回では、ConfigMapを変更した後にPrometheusのPodを手動で再起動(rollout restart)しないと新しい設定が反映されなかった。kube-prometheus-stackのPrometheusにはconfig-reloaderというsidecarが付いており、マウントされたファイルの変更を検知して自動で設定をリロードする。これを、Podが再作成されていないことと合わせて確認する。まずPodの名前・UID・コンテナの再起動回数を記録する。

kubectl get pod -n monitoring-operator -l app.kubernetes.io/name=prometheus \
  -o jsonpath='{.items[0].metadata.name}{"\t"}{.items[0].metadata.uid}{"\t"}{.items[0].status.containerStatuses[0].restartCount}{"\n"}'
prometheus-kube-prometheus-stack-prometheus-0	68a5b083-da5f-493c-9c63-3d735e5af921	0

PrometheusRuleのannotations.descriptionだけを書き換えてkubectl applyする。

kubectl patch prometheusrule node-exporter-rules -n monitoring --type=json \
  -p='[{"op":"replace","path":"/spec/groups/0/rules/0/annotations/description","value":"CHANGED: up{job=\"node-exporter\"} has been 0"}]'

反映されたかをrules APIで確認し、Podの名前・UID・再起動回数が変わっていないことも合わせて確認する。

curl -s http://localhost:9091/api/v1/rules?type=alert | jq '.data.groups[] | select(.file | contains("monitoring-node-exporter-rules")) | .rules[0].annotations'
kubectl get pod -n monitoring-operator -l app.kubernetes.io/name=prometheus \
  -o jsonpath='{.items[0].metadata.name}{"\t"}{.items[0].metadata.uid}{"\t"}{.items[0].status.containerStatuses[0].restartCount}{"\n"}'
{
  "description": "CHANGED: up{job=\"node-exporter\"} has been 0"
}
prometheus-kube-prometheus-stack-prometheus-0	68a5b083-da5f-493c-9c63-3d735e5af921	0

変更内容はAPIに反映されているが、Pod名・UID・再起動回数はすべて変更前と一致している。Podは置き換わっておらず、コンテナも再起動していない。config-reloaderのログにも、このタイミングでリロードが走ったことが残っている。

kubectl logs -n monitoring-operator prometheus-kube-prometheus-stack-prometheus-0 -c config-reloader --tail=3
level=info ts=2026-10-02T12:36:33Z caller=reloader.go:546 msg="Reload triggered" cfg_in=/etc/prometheus/config/prometheus.yaml.gz cfg_out=/etc/prometheus/config_out/prometheus.env.yaml cfg_dirs= watched_dirs="/etc/prometheus/rules/..."

PrometheusRuleをkubectl applyしただけで、Podを再作成せずに設定がリロードされている。

手動構成とOperatorベース構成の対応関係
#

項目 第1回〜第4回(手動) 今回(Operator)
監視対象の検出 kubernetes_sd_configs + relabel_configsを手で書く ServiceMonitor / PodMonitorのラベルセレクタ
alerting rule PrometheusのConfigMapに直接記述 PrometheusRule
Prometheus用RBAC monitoring Namespaceに限定したRoleを自分で作る Helm chartがClusterRoleとClusterRoleBindingを作成する(Prometheus自身・Operator自身とも既定でクラスタ全体)
ServiceMonitor等の検出 (該当なし) Prometheus OperatorがClusterRoleでwatchし、Prometheus用の設定を生成する
設定変更の反映 rollout restart等でPodを再作成 config-reloaderが自動でリロード(Pod UID・再起動回数は変化しないことを実測)
変更の単位 ConfigMap全体を書き換える 対象ごとに独立したServiceMonitor等を増減する
初期状態の監視対象(grafana.enabled=false・nodeExporter.enabled=false時) 自分で追加した分だけ(今回までで2種類) このreleaseが作成する11個のServiceMonitor・35個のPrometheusRule

Operatorが自動化するもの、見えにくくするもの
#

自動化される点は、relabel_configsの大部分(Namespace・Pod・Service・Containerのラベル付け、Failed/Succeeded podの除外)、設定変更時のリロード、Kubernetesの標準的なコンポーネント(apiserver、kubelet等)の監視設定が最初から揃うことである。

一方で見えにくくなる点もある。

  • RBACの範囲が広がる。 第4回のRoleはmonitoring Namespace・3リソースに限定していたが、Prometheus自身・Operator自身とも、Helm chartが作成するClusterRoleでクラスタ全体のnodes・services・endpoints・pods・ingressesやCRD自体を見られる
  • 設定を生成する主体が、設定を使う主体と別になる。 ServiceMonitor等をwatchして設定を書くのはPrometheus Operator、実際にscrapeするのはPrometheusで、どちらも別々のClusterRoleを持つ。13行のServiceMonitorから、シャーディング用のルールを含めて17個のrelabel_configsが生成される。手元のPrometheus設定ファイルを直接見ても、どのルールがどのCustom Resourceから来たのか追いにくい
  • releaseラベルのように、chartの既定動作を知らないと動かない設定がある。 *SelectorNilUsesHelmValues: trueという既定値がrelease: <リリース名>という条件を生成することは、values.yamlの表示だけでは気づけなかった
  • 既定の内部実装が、必ずしも最新の推奨事項とは限らない。 今回のバージョンではrole: endpoints(非推奨)が既定だったが、serviceDiscoveryRole: EndpointSliceで切り替えられる
  • helm uninstallで消えないリソースもある。 Prometheus Operatorが実行時に作成するkube-systemのkubelet監視用Service・Endpoints(Helmのrelease管理外)は、個別に削除する必要がある(後述)

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

AlertmanagerConfigによるAlertmanagerのルーティング設定、ProbeによるBlackbox Exporter連携、Prometheus・Alertmanagerの高可用構成、long-term storage(Thanos等)は扱わなかった。次回(第6回)は、GKEとGoogle Cloud Managed Service for Prometheusを扱う。Managed Service for PrometheusもCRDに似た宣言的な設定(PodMonitoring / ClusterPodMonitoring)を使うため、今回のServiceMonitor・PodMonitorの経験はそのまま活きる。

元に戻す
#

前提として、今回の構成は学習用の非永続構成であり、PersistentVolumeは使っていない。

まず、今回作成したCustom Resource(ServiceMonitor / PodMonitor / PrometheusRule)と、手動で追加したServiceのラベルを削除する。

# 対象はmonitoring Namespace内の今回作成分のみ。削除しても第1回〜第4回の構成には影響しない
kubectl delete servicemonitor node-exporter -n monitoring
kubectl delete podmonitor grafana -n monitoring
kubectl delete prometheusrule node-exporter-rules -n monitoring
kubectl label service node-exporter -n monitoring app-

次に、kube-prometheus-stack一式を削除する。monitoring Namespaceの第1回〜第4回の構成には影響しないが、helm uninstallが削除するのはHelmがテンプレートとして管理しているリソースであり、対象はmonitoring-operator Namespace内に限らない。Prometheus・Operator用のClusterRole・ClusterRoleBindingのようなクラスタスコープのリソースも、このreleaseが作成したものであれば削除される。CRD自体はHelmの管理対象外として扱われており削除されない。

helm uninstall kube-prometheus-stack --namespace monitoring-operator
# monitoring-operator Namespace自体も削除する。影響は今回作成した分のみで、monitoring Namespaceには及ばない
kubectl delete namespace monitoring-operator
release "kube-prometheus-stack" uninstalled
namespace "monitoring-operator" deleted

これでmonitoring-operator Namespace内と、Helmがテンプレートとして管理していたクラスタスコープのリソースは消える。ただし、Prometheus Operatorはkubelet監視用にkube-system NamespaceへService・Endpointsを実行時に作成しており、これはHelmのテンプレートではなくOperator自身が作る(app.kubernetes.io/managed-by: prometheus-operatorというラベルを持ち、Helmのrelease管理外)ため、helm uninstallでは消えない。

kubectl get svc,endpoints -n kube-system | grep kube-prometheus-stack
service/kube-prometheus-stack-kubelet   ClusterIP   None   <none>   10250/TCP,4194/TCP,10255/TCP   40m
endpoints/kube-prometheus-stack-kubelet   192.168.157.2:10250,192.168.157.2:4194,192.168.157.2:10255   40m

個別に削除する。Endpointsは対応するServiceを削除すると一緒に消える。

kubectl delete svc kube-prometheus-stack-kubelet -n kube-system

CRD自体も削除する場合は、他にそのCRDを使っているリソースが無いことを確認してから削除する。CRDを削除すると、そのCRDを使う全NamespaceのCustom Resourceが削除される点に注意する(今回は他に使っていないため影響はない)。

kubectl get crd -o name | grep monitoring.coreos.com | xargs kubectl delete

まとめ
#

Prometheus Operatorは、kubernetes_sd_configsやrelabel_configsを手で組み立てる作業を、ServiceMonitor / PodMonitorという宣言的なCustom Resourceへ抽象化する。alerting ruleも同様にPrometheusRuleへ抽象化される。書く内容自体(selector、alert/expr/for)は手動構成の延長線上にあり、第1回〜第4回の知識がそのまま使える。

ただし、これらのCustom Resourceを作るだけで済む裏側では、ServiceMonitor等をwatchして設定を生成するPrometheus Operatorと、生成された設定でscrapeを行うPrometheus自身が、それぞれ別の広いRBACを持っている。13行のServiceMonitorから17個のルールを含むrelabel_configsが自動生成され、helm uninstallでは消えないkube-system内のリソースも作られる。「手で書かなくてよい」ことと「何が起きているか把握しやすい」ことは別で、生成された設定や実際のリソースを見ないと、こうした内部実装の詳細には気づけない。

次回は、この構成をGKEへ展開し、Google Cloud Managed Service for Prometheusとの違いを確認する。

参考資料
#

関連記事

Kubernetes監視入門 第4回|kubernetes_sd_configsとkube-state-metricsでPod・Nodeの状態を追う
5 分
Kubernetes Prometheus
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