RBAC: Namespace-Scoped Runner¶
By default, the Robusta runner is a cluster-wide agent: it discovers workloads in every namespace,
reads cluster-scoped resources (nodes, namespaces, persistent volumes) and runs with a ClusterRole
bound by a ClusterRoleBinding.
This guide runs the runner with access limited to a single namespace — the namespace Robusta is
installed in. Setting runner.rbac.namespaceScoped: true makes the chart render namespaced RBAC
(a Role + RoleBinding instead of the ClusterRole + ClusterRoleBinding) and presets the
runner's scoped-mode environment variables. Everything stays Helm-managed — no hand-applied RBAC —
and because all rendered objects are namespaced, you can install multiple independent Robusta
instances in different namespaces without name collisions. The default (false) keeps today's
cluster-wide behavior.
What works and what doesn't¶
In this mode the runner keeps working as a platform agent: it connects to the Robusta platform, reports cluster status (workload counts for its namespace, Kubernetes version, provider), answers UI requests for resources in its namespace, and can reach Holmes and an explicitly-configured Prometheus/Alertmanager.
The following are not available with a namespace-scoped service account:
Cluster-wide resource discovery (the UI's apps/nodes/namespaces inventory) — disabled via
DISABLE_DISCOVERY.Kubernetes change tracking and
on_kubernetes_*triggers — the kubewatch forwarder is disabled (see below).Playbooks. This setup assumes all playbooks are disabled. Many built-in actions need cluster-scoped access (nodes, persistent volumes, cluster-wide events and pod listings) and will fail with permission errors if invoked.
Node-based features: node enrichments, drains, node count (reported as
1), auto-detection of the cluster provider (declare it withCLUSTER_PROVIDERinstead).Auto-discovery of Prometheus/Alertmanager — set
prometheus_url/alertmanager_urlexplicitly, or leave them unset to run without those integrations.
Setup¶
Add the following to your Helm values:
# scoped-values.yaml
runner:
rbac:
# renders a namespaced Role + RoleBinding (no ClusterRole/ClusterRoleBinding)
# and presets the scoped-mode env vars listed below
namespaceScoped: true
additional_env_vars:
- name: CLUSTER_PROVIDER
value: "EKS" # your provider; or "OpenShift", "Unknown", ...
- name: ENABLE_TELEMETRY
value: "false" # optional
# no playbooks in this mode — many built-in actions need cluster-wide access
builtinPlaybooks: []
customPlaybooks: []
enablePlatformPlaybooks: false
# Robusta-managed Prometheus alerts sync PrometheusRule CRs in the installation
# namespace. Disable it (your generated_values.yaml may have it enabled), or keep it
# and grant the prometheusrules rule via runner.customClusterRoleRules (see note below).
enabledManagedConfiguration: false
# if you use Prometheus/Alertmanager, point at them explicitly instead of auto-discovery
# globalConfig:
# prometheus_url: "http://prometheus.monitoring.svc.cluster.local:9090"
# alertmanager_url: "http://alertmanager.monitoring.svc.cluster.local:9093"
# don't deploy the kubewatch forwarder at all (see the section below)
kubewatch:
enabled: false
Then install:
helm upgrade --install robusta robusta/robusta \
-f generated_values.yaml \
-f scoped-values.yaml \
-n robusta --create-namespace
For multiple instances, repeat per namespace with a unique clusterName per install (each
instance appears as its own cluster in the Robusta platform).
What runner.rbac.namespaceScoped does¶
RBAC — instead of <release>-runner-cluster-role (ClusterRole) + ClusterRoleBinding, the
chart renders <release>-runner-role (a namespaced Role with the same rules) bound by a
RoleBinding in the release namespace. The ServiceAccount and the namespaced create-permissions
Role (<release>-runner-local-role) are unchanged. Because a Role is namespaced, multiple
installs — even with the same release name — cannot collide on cluster-scoped RBAC objects.
Environment variables — the flag presets these on the runner (any entry you add to
runner.additional_env_vars with the same name overrides the preset):
Variable |
Preset by the flag |
Effect |
|---|---|---|
|
|
Disables the cluster-wide resource discovery loop. |
|
the release namespace |
Cluster-status workload counts are taken from this namespace only; the node count is
reported as |
|
|
Namespace labels/annotations (used by sink scopes) are read only for the installation
namespace. Other values: |
|
the release namespace |
Search for the Holmes service only in this namespace. Alternatively set |
|
|
Never search the cluster for a Prometheus/Victoria Metrics service. Set |
|
|
Never search the cluster for an Alertmanager service. Set |
|
not preset |
Declare the cluster provider instead of detecting it from nodes. One of: |
|
not preset |
Set |
Note
The Role carries the same rules as the default ClusterRole, so cluster-scoped entries in it
(nodes, persistentvolumes, ...) are legal but grant nothing — a RoleBinding
structurally cannot grant cluster-scoped access. One side effect: kubectl auth can-i list
nodes -n <ns> may answer yes even though a real kubectl get nodes is denied; check
without -n (as in the verification below) for the true answer.
Note
If you keep Robusta-managed Prometheus alerts (enabledManagedConfiguration: true),
also add this to your values — the runner syncs PrometheusRule CRs in its own namespace,
and customClusterRoleRules entries are added to the scoped Role:
runner:
customClusterRoleRules:
- apiGroups: ["monitoring.coreos.com"]
resources: ["prometheusrules"]
verbs: ["get", "list", "create", "update", "patch", "delete"]
Alternative: bring your own service account¶
If your security process requires RBAC managed outside the chart, set
runner.createServiceAccount: false (nothing RBAC-related is rendered) plus
runner.customServiceAccount: <name>, and create the ServiceAccount, a Role with the rules the
runner needs (workload reads for cluster stats and self-registration, configmaps
get/create/update for the scheduled-jobs scheduler state, services list for Holmes
discovery, namespaces get for NAMESPACE_DATA_MODE=namespaced, and events
get/list in both the "" core and events.k8s.io API groups for resource event
enrichment) and a RoleBinding yourself. In that setup also set
the environment variables from the table above yourself via runner.additional_env_vars — the
presets come from runner.rbac.namespaceScoped, which you may still enable together with a
custom service account.
Disabling kubewatch¶
The kubewatch forwarder is a separate Deployment with its own cluster-wide ClusterRole — it is
what watches the API server for changes and feeds them to the runner. kubewatch.enabled: false
removes it entirely: the Deployment, Service, ConfigMap, ServiceAccount, ClusterRole and
ClusterRoleBinding are not rendered. Kubernetes change tracking and on_kubernetes_* triggers do
not work without it (which is consistent with this setup — playbooks are disabled anyway).
Verifying the scope¶
SA=system:serviceaccount:robusta:robusta-runner-service-account
kubectl auth can-i list pods --as=$SA -n robusta # -> yes
kubectl auth can-i list pods --as=$SA -n kube-system # -> no
kubectl auth can-i list nodes --as=$SA # -> no
kubectl auth can-i list namespaces --as=$SA # -> no
Then check the runner logs — there should be no recurring Forbidden (403) errors, and the
platform should show the cluster as connected, with workload counts for the scoped namespace and a
node count of 1.
Troubleshooting¶
The runner exits with configmaps "scheduled-jobs" is forbidden ... cannot get resource
"configmaps" — the runner's service account has no configmaps get in its namespace. With
runner.rbac.namespaceScoped: true and the chart-created service account this cannot happen; it
means a bring-your-own RoleBinding is not matching the pod's service account. This error is fatal
(the runner restarts in a loop until fixed). Check, in the installation namespace:
NS=robusta # your namespace
SA=robusta-runner-service-account # your service account name
# 1. which service account is the pod actually running as?
kubectl get deployment robusta-runner -n $NS \
-o jsonpath='{.spec.template.spec.serviceAccountName}'
# 2. does the RoleBinding reference the right Role and the right subject?
kubectl get rolebinding -n $NS -o yaml | grep -B2 -A8 "$SA"
# 3. the direct check:
kubectl auth can-i get configmaps --as=system:serviceaccount:$NS:$SA -n $NS # must be yes
Common causes: the RoleBinding's subjects[].name doesn't match the ServiceAccount name used in
runner.customServiceAccount (e.g. after renaming one but not the other), the roleRef.name
doesn't match the Role, or the objects were applied to a different namespace.
Recurring prometheusrules.monitoring.coreos.com is forbidden errors ("An error occurred
while creating CR rules") — Robusta-managed Prometheus alerts are enabled
(enabledManagedConfiguration: true, often present in generated_values.yaml). Either set
enabledManagedConfiguration: false, or grant the prometheusrules rule via
runner.customClusterRoleRules as shown above. This error is not fatal, but it repeats every
sync cycle.
Related guides¶
Read-Only Service Account — keep cluster-wide read access but remove all write permissions.
RBAC: Namespace-Scoped HolmesGPT — the equivalent setup for HolmesGPT.