Multitenant KEDA & KPA
Multitenant KEDA lets you run isolated KEDA and Kedify Pod Autoscaler (KPA) pairs inside one Kubernetes cluster. Each pair owns a non-overlapping set of namespaces, giving tenants independent event processing and horizontal autoscaling control loops. Workloads can still use native HPA through the shared metrics adapter, while KPA removes the cluster-wide HPA controller and external-metrics API path from their 1 ↔ N scaling loop.
Architecture
Section titled “Architecture”Multitenant KEDA uses three core components and, when KPA is enabled, a tenant autoscaler:
-
Tenant KEDA operators - each typically runs in the
kedanamespace (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. -
Shared metrics adapter - a single
keda-operator-metrics-apiserverthat the Kubernetes API server calls for external metrics. It routes each native HPA request to the correct tenant operator based on the ScaledObject’s namespace. -
Kedify agent is responsible for discovering tenant registrations and synchronizing TLS certificates to the metrics adapter.
-
Tenant KPA controllers - each KPA controller watches the same namespaces as its paired KEDA operator and gets external metrics directly from that operator over mTLS. KPA uses
metrics.k8s.iofor CPU and memory.
HPA Request Flow
Section titled “HPA Request Flow”- The Kubernetes HPA controller queries
external.metrics.k8s.iofor external metric values referenced by an HPA associated with workloads in namespacefoo - The metrics adapter looks up
fooin its namespace-to-tenant routing table - The adapter forwards the gRPC
GetMetricscall to the tenant operator watchingfooover mTLS - 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.
KPA Request Flow
Section titled “KPA Request Flow”KPA does not replace the multitenant KEDA operator or its namespace ownership model. It changes only the horizontal autoscaler path for selected ScaledObjects:
- Kedify KEDA generates a
KedifyPodAutoscalerinstead of an HPA. - The KPA controller watching that namespace evaluates the resource.
- KPA queries its paired tenant operator directly over mTLS gRPC for external metrics, or
metrics.k8s.iofor CPU and memory. - KPA updates the target’s scale subresource.
The paired KEDA and KPA releases must watch the same non-overlapping namespace set. A single KEDA operator with several KPA controllers is not a supported tenant topology: every KPA must connect to the KEDA operator that owns its namespaces. KEDA still handles scaler polling, activation, fallback, cooldown, and scale to zero. The shared metrics adapter remains available for tenants and ScaledObjects that use HPA.
Deployment Modes
Section titled “Deployment Modes”The KEDA Helm chart supports three modes via kedify.multitenant.mode:
| Mode | keda-operator | Metrics Server | Webhooks | CRDs |
|---|---|---|---|---|
"" (disabled) | Standard single-tenant | Standard | Standard | Standard |
"default" | Default tenant + multitenant routing | Yes, routes to tenants | Yes | Yes |
"tenant" | Tenant operator only | No | No | No |
- 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.
Tenant Discovery and Registration
Section titled “Tenant Discovery and Registration”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:
| Field | Description |
|---|---|
name | Tenant identifier (<namespace>/<release-name>) |
namespace | Namespace where the operator runs |
watchNamespace | Namespace(s) this operator watches for ScaledObjects |
address | gRPC address of the operator (e.g. keda-operator-foo.keda.svc.cluster.local:9666) |
tlsSecretRef | Name of the Secret containing the operator’s TLS certificates |
isDefaultTenant | Whether this is the default (fallback) tenant |
operatorDeploymentName | Name of the operator Deployment |
kpaDeploymentName | Exact KPA controller Deployment name when KPA is enabled |
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.
Operator Sharding
Section titled “Operator Sharding”Instead of one operator and one horizontal autoscaling controller handling the whole cluster, you shard work across KEDA/KPA pairs that each watch a subset of namespaces.
You can deploy several pairs in one installation namespace, one pair per tenant namespace, or a combination of both topologies. Release names, KEDA Services, and certificate Secrets keep pairs distinct even when they share an installation namespace.
mTLS Certificate Management
Section titled “mTLS Certificate Management”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
authorityis used for SNI/server certificate SAN matching
Routing and Isolation
Section titled “Routing and Isolation”The metrics adapter maintains a routing table that maps each watched namespace to its tenant’s gRPC client. The routing semantics are:
- Fast path - direct namespace-to-client map lookup (O(1))
- Default tenant fallback - if a namespace doesn’t match any tenant, the request goes to the tenant installed with
kedify.multitenant.mode=default - 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.
Prometheus Metrics
Section titled “Prometheus Metrics”The metrics adapter exposes the following Prometheus metrics for monitoring multitenant routing:
| Metric | Type | Description |
|---|---|---|
kedify_metrics_adapter_tenant_requests_total | Counter | Total metric requests per tenant |
kedify_metrics_adapter_tenant_route_fallback_total | Counter | Requests that fell back to the default tenant |
kedify_metrics_adapter_tenant_route_errors_total | Counter | Routing errors (labeled by tenant, cause) |
kedify_metrics_adapter_tenant_connection_ready | Gauge | Live 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_ready | Gauge | Connection readiness observed when routing a request; only present after a tenant has served at least one scaling request. |
Kubernetes Events
Section titled “Kubernetes Events”The metrics adapter emits events on its own Pod:
| Type | Reason | Description |
|---|---|---|
Normal | KedifyTenantConnectionReady | gRPC connection to a tenant is established |
Warning | KedifyTenantConnectionNotReady | gRPC connection to a tenant failed |
Getting Started
Section titled “Getting Started”Prerequisites
Section titled “Prerequisites”- A Kubernetes cluster
- Helm 3
- A Kedify subscription (helm installation guide)
Add the Kedify chart repository:
helm repo add kedifykeda https://kedify.github.io/chartshelm repo update1. Install the kedify-agent with multitenancy enabled
Section titled “1. Install the kedify-agent with multitenancy enabled”helm upgrade --install kedify-agent kedifykeda/kedify-agent \ --namespace keda --create-namespace \ --set agent.features.multitenantKEDAEnabled=true \ --set agent.features.kedifyPodAutoscalerEnabled=true \ --set agent.orgId="<your-org-id>" \ --set agent.apiKey="<your-api-key>"The agent watches tenant registrations, synchronizes TLS certificates to the metrics adapter, and enables KPA telemetry and Dashboard support. Upgrade the Agent chart and image together: the chart derives the CRD metadata, Lease, ReplicaSet, and tenant log permissions from these existing feature flags, so no additional RBAC values are required.
2. Install the default KEDA operator in multitenant mode
Section titled “2. Install the default KEDA operator in multitenant mode”helm upgrade --install keda kedifykeda/keda --version v2.20.2-0 \ --namespace keda --create-namespace \ --set kedify.multitenant.mode=default \ --set watchNamespace=keda \ --set kedify.kpa.enabled=false \ --set kedify.kpa.defaultClass=hpaThis installs the full KEDA stack with the multitenant routing layer enabled and keeps workloads on HPA while KPA starts. watchNamespace=keda scopes this operator to the keda namespace. Namespaces not matched by any tenant still fall back to this default tenant for HPA metric routing. To also reconcile ScaledObjects in unassigned namespaces, omit watchNamespace to watch cluster-wide.
3. Install the default KPA controller
Section titled “3. Install the default KPA controller”Install KPA with the same namespace watch set. Exactly one KPA release installs the cluster-scoped CRD:
helm upgrade --install kpa oci://ghcr.io/kedify/charts/kpa \ --version <KPA_VERSION> \ --namespace keda \ --set crds.install=true \ --set-string watchNamespace=kedaEnable KPA in the default KEDA release after both controllers are ready. Keep HPA as the default until a canary workload has been verified:
helm upgrade keda kedifykeda/keda \ --namespace keda \ --reuse-values \ --set kedify.kpa.enabled=true \ --set kedify.kpa.defaultClass=hpa \ --set kedify.kpa.deploymentName=kpa4. Deploy tenant KEDA/KPA pairs
Section titled “4. Deploy tenant KEDA/KPA pairs”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:
helm upgrade --install foo kedifykeda/keda --version v2.20.2-0 \ --namespace keda \ --set kedify.multitenant.mode=tenant \ --set watchNamespace=foo \ --set kedify.kpa.enabled=false \ --set kedify.kpa.defaultClass=hpaPair it with a KPA release that watches the same namespace and connects to that tenant’s KEDA Service and certificate Secret:
helm upgrade --install kpa-foo oci://ghcr.io/kedify/charts/kpa \ --version <KPA_VERSION> \ --namespace keda \ --set crds.install=false \ --set-string watchNamespace=foo \ --set keda.metricsAddress=keda-operator-foo.keda.svc:9666 \ --set keda.metricsAuthority=keda-operator-foo.keda.svc \ --set keda.certificateSecret=kedaorg-certs-foo
helm upgrade foo kedifykeda/keda \ --namespace keda \ --reuse-values \ --set kedify.kpa.enabled=true \ --set kedify.kpa.defaultClass=hpa \ --set kedify.kpa.deploymentName=kpa-fookedify.kpa.deploymentName is required for a multitenant KPA-enabled KEDA release. It gives the Agent an exact Deployment identity instead of relying on workload-controlled labels. The Agent records that Deployment’s Kubernetes UID and checks it again before collecting KPA metrics or logs.
Repeat for each tenant with a unique release name and non-overlapping watchNamespace. Keep kedify.kpa.defaultClass aligned across the default and tenant KEDA releases if ScaledObjects will rely on the installation default. See the KPA guide for canary selection, evaluation intervals, monitoring, and rollback.
5. Create ScaledObjects
Section titled “5. Create ScaledObjects”Create ScaledObjects in tenant namespaces as usual; they are picked up by the correct operator automatically. Add autoscaling.kedify.io/class: kpa to move a canary to the tenant’s KPA controller without changing the ScaledObject spec.
apiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: name: my-scaledobject namespace: foo annotations: autoscaling.kedify.io/class: kpaspec: 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"Helm Values Reference
Section titled “Helm Values Reference”KEDA Chart (kedifykeda/keda)
Section titled “KEDA Chart (kedifykeda/keda)”| Value | Default | Description |
|---|---|---|
kedify.multitenant.mode | "" | Deployment mode: "" (disabled), "default", or "tenant" |
watchNamespace | "" | Namespace(s) this operator watches (empty = cluster-wide) |
kedify.multitenant.agentNamespace | keda | Namespace where kedify-agent runs |
kedify.multitenant.agentServiceAccount | kedify-agent | ServiceAccount of kedify-agent |
kedify.kpa.enabled | false | Allow KEDA to generate KPA for selected ScaledObjects |
kedify.kpa.defaultClass | hpa | Autoscaler class when a ScaledObject has no class annotation |
kedify.kpa.deploymentName | "" | Exact KPA Deployment name; required with multitenant KPA |
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)”| Value | Default | Description |
|---|---|---|
agent.features.multitenantKEDAEnabled | false | Enable multitenant KEDA discovery and certificate sync |
agent.features.kedifyPodAutoscalerEnabled | false | Enable KPA telemetry and Dashboard support |
KPA Chart (oci://ghcr.io/kedify/charts/kpa)
Section titled “KPA Chart (oci://ghcr.io/kedify/charts/kpa)”| Value | Default | Description |
|---|---|---|
crds.install | true | Install the cluster-scoped KPA CRD from one designated release |
watchNamespace | "" | Namespaces owned by this controller (empty = cluster-wide) |
keda.metricsAddress | keda-operator.keda.svc:9666 | gRPC address of the paired KEDA operator |
keda.metricsAuthority | keda-operator.keda.svc | TLS authority of the paired KEDA operator |
keda.certificateSecret | kedaorg-certs | Secret containing the paired KEDA client certificates |
Status and Troubleshooting
Section titled “Status and Troubleshooting”The kedify-agent reports multitenant status in the KedifyConfiguration custom resource:
kubectl get kedifyconfiguration -n keda -o yamlCheck status.discoveredTenants[] for per-tenant health:
| Field | Description |
|---|---|
name | Installation identifier in namespace/release form |
namespace | Namespace containing the tenant KEDA control plane |
operatorDeploymentName | Registered KEDA operator Deployment |
watchNamespace | Comma-separated workload namespaces owned by this installation |
address | Tenant KEDA MetricsService gRPC address |
isDefault | Whether this is the default/primary installation |
operatorReady | Whether the KEDA operator Deployment is ready |
tlsCertReady | Whether the TLS certificate Secret is available |
configSynced | Whether tenant routing is synced to the shared metrics adapter |
message | Human-readable tenant diagnostic, when present |
kpa.operatorObserved | Whether the Agent inspected this tenant’s KEDA operator KPA configuration |
kpa.enabled | Whether KPA is enabled in this tenant’s KEDA operator |
kpa.deploymentName | Matched KPA controller Deployment in namespace/name form |
kpa.deploymentUID | Kubernetes UID used to authorize the registered KPA Deployment |
kpa.controllerReady | Whether all desired KPA controller replicas are available |
kpa.version | Matched KPA controller version |
kpa.watchNamespace | Namespace set observed on the KPA controller |
kpa.kedaMetricsAddress | KEDA MetricsService address used by the KPA controller |
kpa.paired | Whether the KPA namespace set and MetricsService match this KEDA installation |
kpa.crdOwner | Helm release that owns the cluster-scoped KPA CRD, in namespace/release form |
kpa.message | Human-readable KPA diagnostic, when capability or pairing is incomplete |
An installation where KPA is intentionally not installed should report kpa.enabled: false, kpa.controllerReady: false, and kpa.paired: false. This is distinct from kpa.operatorObserved: false, which means the Agent could not inspect the tenant KEDA operator.
The Dashboard uses this tenant status rather than inferring KPA support from the cluster-wide CRD. The cluster Summary aggregates KEDA, KPA, and Agent usage across installations; Components provides the per-installation status, CPU and memory breakdown, and ownership-verified KEDA/KPA logs.

To check the metrics adapter’s tenant connections:
# Check adapter logs for tenant routing eventskubectl logs -n keda deploy/keda-operator-metrics-apiserver | grep -i tenant
# Check Kubernetes events for connection statekubectl get events -n keda --field-selector reason=KedifyTenantConnectionReadykubectl get events -n keda --field-selector reason=KedifyTenantConnectionNotReady