# Install and verify multi-tenant scaling

This guide installs isolated KEDA operators and optional [Kedify Pod Autoscaler (KPA)](https://docs.kedify.io/features/kedify-pod-autoscaler/) 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 scaling](https://docs.kedify.io/features/multitenant-keda/).

## Plan the deployment

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`:

| Mode | Purpose | Shared metrics adapter, webhooks, and CRDs |
| --- | --- | --- |
| `""` | Standard single-tenant installation | Installed normally |
| `"default"` | Default operator and fallback route for a multi-tenant installation | Installed once |
| `"tenant"` | Additional tenant operator | Reuses 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 scaling](https://docs.kedify.io/features/multitenant-keda/) for routing, registration, mTLS, and isolation details.

## Install multi-tenant scaling

### Prerequisites

- A Kubernetes cluster

- Helm 3

- A Kedify subscription ([helm installation guide](https://docs.kedify.io/installation/helm/))

- Kubernetes 1.33 through 1.35 if you plan to enable KPA

- [Network access between the shared adapter and tenant operators](https://docs.kedify.io/installation/networking/#multi-tenant), plus KPA-to-operator access when using KPA

Add the Kedify chart repository:

```bash
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

```bash
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

```bash
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

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

```bash
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:

```bash
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

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:

```bash
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:

```bash
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](https://docs.kedify.io/features/kedify-pod-autoscaler/) for class selection, evaluation intervals, supported metrics, and limitations.

### 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.

```yaml
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.

## Helm values reference

### 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`)

| Value | Default | Description |
| --- | --- | --- |
| `agent.features.multitenantKEDAEnabled` | `false` | Enable multi-tenant KEDA discovery and certificate sync |
| `agent.features.kedifyPodAutoscalerEnabled` | `false` | Enable KPA telemetry and Dashboard support |

### 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 |

## Verify the installation

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`:

```bash
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:

```bash
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.

## Status and troubleshooting

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

```bash
kubectl get kedifyconfiguration -n keda -o yaml
```

Check `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 tenant 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.

To check the metrics adapter’s tenant connections:

```bash
# 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>`.

## Roll back KPA to HPA

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.

## Continue with this topic

**Reference:** [Helm values and component configuration](https://docs.kedify.io/reference/helm-values/).

**Diagnose:** [Diagnose multi-cluster and tenant controllers](https://docs.kedify.io/troubleshooting/platform-scaling/).

**Related capabilities:** [Access, telemetry and connectivity boundaries](https://docs.kedify.io/security-and-compliance/access-and-data/).

---
Canonical: https://docs.kedify.io/how-to/multitenant-scaling/
Source: src/content/docs/how-to/multitenant-scaling.md
Documentation index: https://docs.kedify.io/llms.txt
