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.

Install and Configure Multi-tenant Scaling

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.

This guide installs isolated KEDA operators and optional Kedify Pod Autoscaler (KPA) controllers inside one Kubernetes cluster. Each pair owns a non-overlapping set of namespaces. Workloads can use native HPA through the shared metrics adapter or KPA for a tenant-scoped 1 ↔ N control loop.

For the architecture and isolation model without the installation steps, see Multi-tenant KEDA & KPA.

Choose whether tenants will use the shared native HPA path or add KPA for a tenant-scoped 1 ↔ N control loop. A cluster can use both paths for different ScaledObjects.

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

ModePurposeShared metrics adapter, webhooks, and CRDs
""Standard single-tenant installationInstalled normally
"default"Default operator and fallback route for a multi-tenant installationInstalled once
"tenant"Additional tenant operatorReuses the default installation’s shared components

Before installing, decide the release name and watchNamespace set for every operator. Apply these rules:

  • Install exactly one KEDA release in default mode.
  • Give every additional KEDA release a unique name and use tenant mode.
  • Keep tenant watchNamespace sets non-overlapping.
  • If a tenant uses KPA, give its KPA controller exactly the same watch set as its KEDA operator.
  • Set crds.install=true on one KPA release only; use false on every additional KPA release.
  • Keep kedify.kpa.defaultClass=hpa until a low-risk KPA canary is healthy.

The commands below place the control-plane releases in keda, but you can use separate installation namespaces. See Multi-tenant KEDA & KPA for routing, registration, mTLS, and isolation details.

  • A Kubernetes cluster
  • Helm 3
  • A Kedify subscription (helm installation guide)
  • Kubernetes 1.33 through 1.35 if you plan to enable KPA

Add the Kedify chart repository:

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

Replace the version placeholders below with compatible released chart versions.

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.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. For an HPA-only topology, omit agent.features.kedifyPodAutoscalerEnabled=true and skip the KPA-specific steps below.

2. Install the default KEDA operator in multi-tenant mode

Section titled “2. Install the default KEDA operator in multi-tenant mode”
Terminal window
helm upgrade --install keda kedifykeda/keda \
--version <KEDA_VERSION> \
--namespace keda --create-namespace \
--set kedify.multitenant.mode=default \
--set-string watchNamespace=keda \
--set kedify.kpa.enabled=false \
--set kedify.kpa.defaultClass=hpa

This 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. Optionally install the default KPA controller

Section titled “3. Optionally install the default KPA controller”

Install KPA with the same namespace watch set. Exactly one KPA release installs the cluster-scoped CRD:

Terminal window
helm upgrade --install kpa oci://ghcr.io/kedify/charts/kpa \
--version <KPA_VERSION> \
--namespace keda \
--set crds.install=true \
--set-string watchNamespace=keda

Enable KPA in the default KEDA release after both controllers are ready. Keep HPA as the default until a canary workload has been verified:

Terminal window
helm upgrade keda kedifykeda/keda \
--namespace keda \
--reuse-values \
--set kedify.kpa.enabled=true \
--set kedify.kpa.defaultClass=hpa \
--set kedify.kpa.deploymentName=kpa

4. Deploy tenant KEDA operators and optional KPA controllers

Section titled “4. Deploy tenant KEDA operators and optional KPA controllers”

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 <KEDA_VERSION> \
--namespace keda \
--set kedify.multitenant.mode=tenant \
--set-string watchNamespace=foo \
--set kedify.kpa.enabled=false \
--set kedify.kpa.defaultClass=hpa

If the tenant will use KPA, pair it with a KPA release that watches the same namespace and connects to that tenant’s KEDA Service and certificate Secret:

Terminal window
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-foo

kedify.kpa.deploymentName is required for a multi-tenant 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 feature guide for class selection, evaluation intervals, supported metrics, and limitations.

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/v1alpha1
kind: ScaledObject
metadata:
name: my-scaledobject
namespace: foo
annotations:
autoscaling.kedify.io/class: kpa
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"

Remove the annotation to follow kedify.kpa.defaultClass, or set it to hpa to explicitly select native HPA. To override the KPA evaluation interval for this ScaledObject, add autoscaling.kedify.io/sync-period-seconds: "15". Keep HPA as the default until the canary has scaled out and back in successfully.

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
kedify.kpa.enabledfalseAllow KEDA to generate KPA for selected ScaledObjects
kedify.kpa.defaultClasshpaAutoscaler 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)”
ValueDefaultDescription
agent.features.multitenantKEDAEnabledfalseEnable multi-tenant KEDA discovery and certificate sync
agent.features.kedifyPodAutoscalerEnabledfalseEnable KPA telemetry and Dashboard support

KPA Chart (oci://ghcr.io/kedify/charts/kpa)

Section titled “KPA Chart (oci://ghcr.io/kedify/charts/kpa)”
ValueDefaultDescription
crds.installtrueInstall the cluster-scoped KPA CRD from one designated release
watchNamespace""Namespaces owned by this controller (empty = cluster-wide)
keda.metricsAddresskeda-operator.keda.svc:9666gRPC address of the paired KEDA operator
keda.metricsAuthoritykeda-operator.keda.svcTLS authority of the paired KEDA operator
keda.certificateSecretkedaorg-certsSecret containing the paired KEDA client certificates

Wait for the default controllers and every tenant pair, then confirm that each workload has exactly one generated horizontal autoscaler. Repeat the rollout checks with each tenant Deployment name, such as keda-operator-foo and kpa-foo:

Terminal window
kubectl rollout status deployment/keda-operator -n keda
kubectl rollout status deployment/kpa -n keda
kubectl get scaledobject,hpa,kpa -A
kubectl get events -A --sort-by=.lastTimestamp

On an HPA-only installation without the KPA CRD, use:

Terminal window
kubectl rollout status deployment/keda-operator -n keda
kubectl get scaledobject,hpa -A

For a KPA canary, confirm that the target has one KPA and no HPA before applying production load.

The Kedify Agent reports multi-tenant status in the KedifyConfiguration custom resource:

Terminal window
kubectl get kedifyconfiguration -n keda -o yaml

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

FieldDescription
nameInstallation identifier in namespace/release form
namespaceNamespace containing the tenant KEDA control plane
operatorDeploymentNameRegistered KEDA operator Deployment
watchNamespaceComma-separated workload namespaces owned by this installation
addressTenant KEDA MetricsService gRPC address
isDefaultWhether this is the default tenant installation
operatorReadyWhether the KEDA operator Deployment is ready
tlsCertReadyWhether the TLS certificate Secret is available
configSyncedWhether tenant routing is synced to the shared metrics adapter
messageHuman-readable tenant diagnostic, when present
kpa.operatorObservedWhether the Agent inspected this tenant’s KEDA operator KPA configuration
kpa.enabledWhether KPA is enabled in this tenant’s KEDA operator
kpa.deploymentNameMatched KPA controller Deployment in namespace/name form
kpa.deploymentUIDKubernetes UID used to authorize the registered KPA Deployment
kpa.controllerReadyWhether all desired KPA controller replicas are available
kpa.versionMatched KPA controller version
kpa.watchNamespaceNamespace set observed on the KPA controller
kpa.kedaMetricsAddressKEDA MetricsService address used by the KPA controller
kpa.pairedWhether the KPA namespace set and MetricsService match this KEDA installation
kpa.crdOwnerHelm release that owns the cluster-scoped KPA CRD, in namespace/release form
kpa.messageHuman-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.

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

If a KPA cannot fetch an external metric, verify that its watch set matches the paired KEDA operator and that keda.metricsAddress, keda.metricsAuthority, and keda.certificateSecret identify that operator. For CPU or memory, verify the resource metrics path with kubectl top pods -n <tenant-namespace>.

Keep KPA running until every selected workload has returned to a healthy HPA:

  1. Set kedify.kpa.defaultClass=hpa on the default KEDA release and every tenant release.
  2. Set autoscaling.kedify.io/class: hpa on every ScaledObject that explicitly selects KPA.
  3. Wait for KEDA to remove the KPAs and create healthy replacement HPAs.
  4. Set kedify.kpa.enabled=false, then remove the tenant KPA controller releases.
  5. Remove the cluster-scoped KPA CRD last, only after no KPA objects or tenant KPA installations remain. Helm retains the CRD on uninstall by default.