kube-state-metrics only reads Kubernetes objects — it never writes, patches, or deletes anything — so its ClusterRole is entirely list and watch verbs. This guide explains every rule in that ClusterRole, when to use a namespaced Role instead, how to scope permissions down with --resources, and how to read and fix the Forbidden errors that show up after upgrades.
The Annotated ClusterRole
Each rule below grants read access to exactly the object types a specific set of KSM collectors needs. If you disable a collector with --resources, you can remove the matching rule.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kube-state-metrics
rules:
# Core API group ("") — covers the pod, node, service and
# config collectors. This is the largest rule and the one
# most often hit by "Forbidden" errors after an upgrade.
- apiGroups: [""]
resources:
- configmaps
- secrets
- nodes
- pods
- services
- serviceaccounts
- resourcequotas
- replicationcontrollers
- limitranges
- persistentvolumeclaims
- persistentvolumes
- namespaces
- endpoints
verbs: ["list", "watch"]
# Workload controllers — Deployments, ReplicaSets, DaemonSets,
# StatefulSets. Required for kube_deployment_* / kube_statefulset_*.
- apiGroups: ["apps"]
resources: [statefulsets, daemonsets, deployments, replicasets]
verbs: ["list", "watch"]
# Batch workloads — needed for kube_job_* / kube_cronjob_*.
- apiGroups: ["batch"]
resources: [cronjobs, jobs]
verbs: ["list", "watch"]
# HPA collector — kube_horizontalpodautoscaler_*.
- apiGroups: ["autoscaling"]
resources: [horizontalpodautoscalers]
verbs: ["list", "watch"]
# Ingress/NetworkPolicy collectors.
- apiGroups: ["networking.k8s.io"]
resources: [ingresses, networkpolicies]
verbs: ["list", "watch"]
# StorageClass/VolumeAttachment collectors.
- apiGroups: ["storage.k8s.io"]
resources: [storageclasses, volumeattachments]
verbs: ["list", "watch"]
# PodDisruptionBudget collector — commonly forgotten when
# people hand-write a ClusterRole instead of using the Helm chart.
- apiGroups: ["policy"]
resources: [poddisruptionbudgets]
verbs: ["list", "watch"]
# CertificateSigningRequest collector.
- apiGroups: ["certificates.k8s.io"]
resources: [certificatesigningrequests]
verbs: ["list", "watch"] helm upgrade over manually edited YAML.Role vs ClusterRole: Namespaced Installs
If you run KSM with --namespaces=team-a,team-b to restrict it to specific namespaces (common in multi-tenant clusters), you don't need a cluster-scoped ClusterRole + ClusterRoleBinding at all — a namespaced Role + RoleBinding per target namespace is more secure and satisfies most compliance requirements:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: kube-state-metrics
namespace: team-a
rules:
- apiGroups: [""]
resources: [pods, services, configmaps, resourcequotas, persistentvolumeclaims]
verbs: ["list", "watch"]
- apiGroups: ["apps"]
resources: [deployments, replicasets, statefulsets, daemonsets]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: kube-state-metrics
namespace: team-a
subjects:
- kind: ServiceAccount
name: kube-state-metrics
namespace: monitoring
roleRef:
kind: Role
name: kube-state-metrics
apiGroup: rbac.authorization.k8s.io Note the cluster-scoped resources — nodes, namespaces, persistentvolumes, storageclasses — cannot be granted through a namespaced Role. If your allowlisted metrics include any of those collectors, you still need a (much smaller) ClusterRole covering just those cluster-scoped types alongside the per-namespace Roles.
Scoping Down With --resources
Every rule you never use is unnecessary attack surface. If you only need Pod and Deployment metrics, disable the rest and delete the matching ClusterRole rules:
--resources=pods,deployments | --resources value | ClusterRole rules still required |
|---|---|
pods | Core API group only (pods) |
pods,deployments,replicasets | Core + apps |
pods,horizontalpodautoscalers | Core + autoscaling |
certificatesigningrequests | certificates.k8s.io only |
Diagnosing Forbidden Errors
The most common RBAC failure shows up in the pod logs as a Forbidden error, usually right after a Kubernetes upgrade that introduced a new API group KSM's ClusterRole hasn't been updated to cover:
E0101 12:00:00.000000 1 reflector.go:138: pkg/mod/k8s.io/client-go/tools/cache/reflector.go:167:
Failed to watch *v1.PodDisruptionBudget: failed to list *v1.PodDisruptionBudget:
poddisruptionbudgets.policy is forbidden: User
"system:serviceaccount:monitoring:kube-state-metrics" cannot list resource
"poddisruptionbudgets" in API group "policy" at the cluster scope | Error fragment | Meaning | Fix |
|---|---|---|
cannot list resource "X" in API group "Y" | ClusterRole is missing a rule for that resource/group | helm upgrade to regenerate the ClusterRole, or add the rule manually |
at the cluster scope | KSM is using a ClusterRole but the object it needs is cluster-scoped (e.g. nodes) | A namespaced Role can't grant this — you need a ClusterRole for that specific rule |
User "system:serviceaccount:...:kube-state-metrics" cannot... | ServiceAccount exists but has no binding, or the binding references the wrong role name | Check kubectl get clusterrolebinding kube-state-metrics -o yaml matches the ServiceAccount name/namespace |
| No RBAC error, but metric silently missing | Collector disabled via --resources, not an RBAC issue at all | Check the deployment's args for --resources |
Forbidden error doesn't crash kube-state-metrics — it logs the error and continues without that resource type. If a whole category of metrics (e.g. all kube_poddisruptionbudget_*) is silently missing from Prometheus, check the pod logs for Forbidden before assuming the collector is broken.For general deployment failures beyond RBAC — CrashLoopBackOff, OOMKilled, high CPU — see the full troubleshooting guide. For the base install and quickstart, see the docs RBAC reference.