Multitenant KEDA
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.
Architecture
Section titled “Architecture”Multitenant KEDA introduces three components:
-
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 HPA’s metric 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.
Request Flow
Section titled “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.
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 supports installation of the full KEDA stack (operator, metrics server, webhooks, CRDs) with the multitenant routing layer enabled in the metrics adapter.
- Tenant mode the installation of CRDs, metrics server, and webhooks must be explicitly disabled because they are already provided by the default installation. Tenants can be installed in the same namespace as the default or a different one, but they must have unique release names, operator deployment names, service accounts, and TLS secret names.
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 |
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 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.
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 | Tenant gRPC connection state (1=ready, 0=not) |
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)
1. Install the kedify-agent with multitenancy enabled
Section titled “1. Install the kedify-agent with multitenancy enabled”helm upgrade --install kedify-agent oci://ghcr.io/kedify/charts/kedify-agent --version v0.5.1-mt \ --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”helm upgrade --install keda oci://ghcr.io/kedify/charts/keda --version v2.19.0-0-mt \ --namespace keda --create-namespace \ --set kedify.multitenant.mode=default \ --set watchNamespace=kedaThis installs the full KEDA stack with the multitenant routing layer enabled in the metrics adapter. The watchNamespace=keda scopes this operator to the keda namespace only. Namespaces not matched by any tenant operator will still fall back to this default tenant for metric routing, but the default operator will only reconcile ScaledObjects in its own watched namespace. If you need the default tenant to also reconcile ScaledObjects in unassigned namespaces, omit watchNamespace to watch cluster-wide.
3. Deploy tenant operators
Section titled “3. Deploy tenant operators”For each tenant, install a KEDA operator scoped to its namespace. For example, a tenant foo watching namespace foo:
helm upgrade --install foo oci://ghcr.io/kedify/charts/keda --version v2.19.0-0-mt \ --namespace keda \ --set kedify.multitenant.mode=tenant \ --set watchNamespace=foo \ --set operator.name=keda-operator-foo \ --set serviceAccount.operator.name=keda-operator-foo \ --set certificates.secretName=kedaorg-certs-foo \ --set crds.install=false \ --set metricsServer.enabled=false \ --set webhooks.enabled=falseThe tenant install disables three components that are already handled by the default installation:
- CRDs - cluster-scoped resources like
ScaledObjectare already installed by the default release. Installing them again would create ownership conflicts. - Metrics server - there’s a single shared metrics adapter that routes HPA requests to the correct tenant. Each tenant doesn’t need its own.
- Webhooks - the admission webhook (ValidatingWebhookConfiguration) is cluster-scoped and already validates ScaledObjects for all namespaces.
Repeat for each additional tenant with a unique release name, operator.name, service account, and certificate secret name.
4. Create ScaledObjects
Section titled “4. Create ScaledObjects”Create ScaledObjects in tenant namespaces as usual - they’ll be picked up by the correct operator automatically.
apiVersion: keda.sh/v1alpha1kind: ScaledObjectmetadata: name: my-scaledobject namespace: foospec: 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 (kedify/charts/keda)
Section titled “KEDA Chart (kedify/charts/keda)”| Value | Default | Description |
|---|---|---|
kedify.multitenant.mode | "" | Deployment mode: "" (disabled), "default", or "tenant" |
kedify.multitenant.configSecretName | kedify-multitenancy-config | Secret name for tenant connection configs |
kedify.multitenant.address | "" | Override gRPC address for this tenant |
kedify.multitenant.authority | "" | SNI authority override |
kedify.multitenant.agentNamespace | keda | Namespace where kedify-agent runs |
kedify.multitenant.agentServiceAccount | kedify-agent | ServiceAccount of kedify-agent |
metricsServer.enabled | true | Set to false for tenant-mode deployments |
webhooks.enabled | true | Set to false for tenant-mode deployments |
crds.install | true | Set to false for tenant-mode deployments |
watchNamespace | "" | Namespace(s) this operator watches |
operator.name | keda-operator | Operator deployment name (must be unique per tenant) |
serviceAccount.operator.name | keda-operator | Operator service account name |
certificates.secretName | kedaorg-certs | TLS certificate secret name (must be unique per tenant) |
Kedify Agent Chart (kedify/charts/kedify-agent)
Section titled “Kedify Agent Chart (kedify/charts/kedify-agent)”| Value | Default | Description |
|---|---|---|
agent.features.multitenantKEDAEnabled | false | Enable multitenant KEDA support |
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 |
|---|---|
operatorReady | Whether the tenant operator is running |
tlsCertReady | Whether the TLS certificate Secret is available |
configSynced | Whether the tenant config has been synced to the metrics adapter |
message | Human-readable status message |
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