Skip to content

For the complete documentation index and AI-optimized content, see /llms.txt. All pages support markdown format via .md extension or Accept: text/markdown header.

Multitenant KEDA

For the complete documentation index and AI-optimized content, see /llms.txt. All pages support markdown format via .md extension or Accept: text/markdown header.

Multitenant KEDA lets you run multiple isolated KEDA operators inside a single Kubernetes cluster, each responsible for scaling workloads in its own set of namespaces. A single shared metrics adapter routes HPA requests to the correct operator automatically, with full mTLS isolation between tenants.

Multitenant KEDA introduces three components:

  1. Tenant KEDA operators - each typically runs in the keda namespace (but can be installed into a different namespace) and watches only its assigned target namespace(s). They are fully independent: separate deployments, separate TLS certificates, separate reconcile loops.

  2. Shared metrics adapter - a single keda-operator-metrics-apiserver that the Kubernetes API server calls for external metrics. It routes each HPA’s metric request to the correct tenant operator based on the ScaledObject’s namespace.

  3. Kedify agent is responsible for discovering tenant registrations and synchronizing TLS certificates to the metrics adapter.

  1. The Kubernetes HPA controller queries external.metrics.k8s.io for external metric values referenced by an HPA associated with workloads in namespace foo
  2. The metrics adapter looks up foo in its namespace-to-tenant routing table
  3. The adapter forwards the gRPC GetMetrics call to the tenant operator watching foo over mTLS
  4. The result is returned to the HPA

If a namespace doesn’t match any tenant, the request goes to the default tenant. This keeps backward compatibility with existing single-tenant setups.

The KEDA Helm chart supports three modes via kedify.multitenant.mode:

Modekeda-operatorMetrics ServerWebhooksCRDs
"" (disabled)Standard single-tenantStandardStandardStandard
"default"Default tenant + multitenant routingYes, routes to tenantsYesYes
"tenant"Tenant operator onlyNoNoNo
  • Default mode installs the full KEDA stack (operator, metrics server, webhooks, CRDs) with the multitenant routing layer enabled in the metrics adapter.
  • Tenant mode installs only the operator. The shared singletons (metrics server, webhooks, CRDs) are turned off automatically because the default installation already provides them. The operator name, service account, and TLS secret are derived from the Helm release name, so tenants sharing a namespace don’t collide. Give each tenant a unique release name and point it at its namespace with watchNamespace.

Setting correct mode configures the Kedify/KEDA helm chart. Under the hood, each tenant KEDA operator registers itself by creating a ConfigMap labeled kedify.io/tenant-registration: "true" as part of the helm release. The registration ConfigMap contains:

FieldDescription
nameTenant identifier (<namespace>/<release-name>)
namespaceNamespace where the operator runs
watchNamespaceNamespace(s) this operator watches for ScaledObjects
addressgRPC address of the operator (e.g. keda-operator-foo.keda.svc.cluster.local:9666)
tlsSecretRefName of the Secret containing the operator’s TLS certificates
isDefaultTenantWhether this is the default (fallback) tenant
operatorDeploymentNameName of the operator Deployment

The kedify-agent discovers these ConfigMaps, reads TLS certs from each tenant’s Secret, and syncs everything into a single configuration Secret (kedify-multitenancy-config) that the metrics adapter has mounted as a volume.

Instead of one operator handling every ScaledObject in the cluster, you shard the work across multiple operators, each watching a subset of namespaces.

You can deploy multiple keda-operators in a single namespace, one keda-operator per namespace, or any combination of both topologies.

Each tenant KEDA operator generates its own TLS certificate pair (managed by KEDA’s built-in cert rotation). The kedify-agent reads these certificates and distributes them to the metrics adapter via the shared configuration Secret.

Certificate rotation is handled automatically:

  • The agent periodically checks for certificate changes (SHA256 hash comparison, every 60 seconds)
  • Updated certificates are synced to the shared configuration Secret
  • The metrics adapter detects the Secret change via filesystem watch and reloads TLS credentials without a restart
  • TLS 1.3 minimum is enforced on all tenant connections
  • Per-tenant authority is used for SNI/server certificate SAN matching

The metrics adapter maintains a routing table that maps each watched namespace to its tenant’s gRPC client. The routing semantics are:

  1. Fast path - direct namespace-to-client map lookup (O(1))
  2. Default tenant fallback - if a namespace doesn’t match any tenant, the request goes to the tenant installed with kedify.multitenant.mode=default
  3. Error - if no tenants are configured, the request fails

Each tenant operator only sees ScaledObjects in its own namespace. The mTLS certificates are unique per tenant, so even the gRPC transport is isolated.

The metrics adapter exposes the following Prometheus metrics for monitoring multitenant routing:

MetricTypeDescription
kedify_metrics_adapter_tenant_requests_totalCounterTotal metric requests per tenant
kedify_metrics_adapter_tenant_route_fallback_totalCounterRequests that fell back to the default tenant
kedify_metrics_adapter_tenant_route_errors_totalCounterRouting errors (labeled by tenant, cause)
kedify_metrics_adapter_tenant_connection_readyGaugeLive tenant gRPC connection state (1=ready, 0=not). Present for every tenant, updated on connection changes regardless of traffic.
kedify_metrics_adapter_tenant_routed_connection_readyGaugeConnection readiness observed when routing a request; only present after a tenant has served at least one scaling request.

The metrics adapter emits events on its own Pod:

TypeReasonDescription
NormalKedifyTenantConnectionReadygRPC connection to a tenant is established
WarningKedifyTenantConnectionNotReadygRPC connection to a tenant failed

Add the Kedify chart repository:

Terminal window
helm repo add kedifykeda https://kedify.github.io/charts
helm repo update

1. Install the kedify-agent with multitenancy enabled

Section titled “1. Install the kedify-agent with multitenancy enabled”
Terminal window
helm upgrade --install kedify-agent kedifykeda/kedify-agent \
--namespace keda --create-namespace \
--set agent.features.multitenantKEDAEnabled=true \
--set agent.orgId="<your-org-id>" \
--set agent.apiKey="<your-api-key>"

The agent watches for tenant registration ConfigMaps and syncs TLS certificates to the metrics adapter.

2. Install the default KEDA operator in multitenant mode

Section titled “2. Install the default KEDA operator in multitenant mode”
Terminal window
helm upgrade --install keda kedifykeda/keda --version v2.20.1-4 \
--namespace keda --create-namespace \
--set kedify.multitenant.mode=default \
--set watchNamespace=keda

This installs the full KEDA stack with the multitenant routing layer enabled. watchNamespace=keda scopes this operator to the keda namespace. Namespaces not matched by any tenant still fall back to this default tenant for routing. To also reconcile ScaledObjects in unassigned namespaces, omit watchNamespace to watch cluster-wide.

For each tenant, install a KEDA operator in tenant mode pointed at its namespace. Tenant mode turns off the shared singletons (CRDs, metrics server, webhooks) automatically, and the operator/service-account/certificate names are derived from the release name, so a minimal install is all you need:

Terminal window
helm upgrade --install foo kedifykeda/keda --version v2.20.1-4 \
--namespace keda \
--set kedify.multitenant.mode=tenant \
--set watchNamespace=foo

Repeat for each tenant with a unique release name and watchNamespace.

Create ScaledObjects in tenant namespaces as usual; they’re picked up by the correct operator automatically.

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: my-scaledobject
namespace: foo
spec:
scaleTargetRef:
name: my-deployment
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus.monitoring.svc:9090
query: sum(rate(http_requests_total{namespace="foo"}[2m]))
threshold: "100"
ValueDefaultDescription
kedify.multitenant.mode""Deployment mode: "" (disabled), "default", or "tenant"
watchNamespace""Namespace(s) this operator watches (empty = cluster-wide)
kedify.multitenant.agentNamespacekedaNamespace where kedify-agent runs
kedify.multitenant.agentServiceAccountkedify-agentServiceAccount of kedify-agent

In tenant mode the chart turns off the shared singletons (metricsServer, webhooks, crds) and derives the operator, service account, and certificate names from the release name automatically, so those values don’t need to be set.

Kedify Agent Chart (kedifykeda/kedify-agent)

Section titled “Kedify Agent Chart (kedifykeda/kedify-agent)”
ValueDefaultDescription
agent.features.multitenantKEDAEnabledfalseEnable multitenant KEDA support

The kedify-agent reports multitenant status in the KedifyConfiguration custom resource:

Terminal window
kubectl get kedifyconfiguration -n keda -o yaml

Check status.discoveredTenants[] for per-tenant health:

FieldDescription
operatorReadyWhether the tenant operator is running
tlsCertReadyWhether the TLS certificate Secret is available
configSyncedWhether the tenant config has been synced to the metrics adapter
messageHuman-readable status message

To check the metrics adapter’s tenant connections:

Terminal window
# Check adapter logs for tenant routing events
kubectl logs -n keda deploy/keda-operator-metrics-apiserver | grep -i tenant
# Check Kubernetes events for connection state
kubectl get events -n keda --field-selector reason=KedifyTenantConnectionReady
kubectl get events -n keda --field-selector reason=KedifyTenantConnectionNotReady