# Scale vCluster workloads in dedicated mode

Run one central Kedify KEDA/KPA pair and let it reconcile ordinary ScaledObjects and ScaledJobs inside automatically discovered vClusters. Each member has its own targets, authentication resources, generated KPAs, and Jobs. Demand is evaluated independently for each member.

This walkthrough selects **dedicated** mode and uses CPU scaling to demonstrate two independent member workloads. For central DistributedScaledObjects that distribute demand across members, follow the [distributed vCluster walkthrough](https://docs.kedify.io/how-to/vcluster-scaling/).

## Prerequisites

- A central cluster with a compatible Kedify `0.8.1` release bundle or later installed in `keda`, on a Kubernetes version supported by its KPA chart.

- A working host `metrics.k8s.io` API. Check `kubectl top pods -A` in the host context before creating shared-node members.

- A default StorageClass that can provision persistent volumes for the vCluster control planes. If you need another class, use the [vCluster storage configuration](https://docs.kedify.io/how-to/vcluster-scaling/#2-create-the-member-vclusters).

- Network access from central Agent, KEDA, and KPA to each member API. See [multi-cluster networking](https://docs.kedify.io/installation/networking/#multi-cluster).

## 1. Enable dedicated mode and discovery

Merge these values into your central Agent’s maintained [Helm configuration](https://docs.kedify.io/installation/helm/) and apply your usual Helm or GitOps upgrade:

```yaml
global:
  features:
    multicluster:
      enabled: true
      type: dedicated
      vClusterDiscoveryEnabled: true
keda:
  enabled: true
kpa:
  enabled: true
```

The chart enables multicluster reconciliation in KEDA and KPA, selects KPA as the default autoscaler, and disables the Agent’s DistributedScaledObject and DistributedScaledJob controllers. Leave `agent.features.multicluster.enabled`, deprecated DSO/DSJ switches, namespace watch restrictions, and KEDA label sharding disabled in these controller releases.

Automatic discovery uses vCluster’s native tenant-admin exports. The Agent copies the five live central CRDs into each member: `scaledobjects.keda.sh`, `scaledjobs.keda.sh`, `triggerauthentications.keda.sh`, `clustertriggerauthentications.keda.sh`, and `kedifypodautoscalers.autoscaling.kedify.io`. Enabling both chart dependencies supplies the matching central CRDs.

After all five member CRDs are established, the Agent creates a managed kubeconfig Secret and `ClusterRegistration` in its installation namespace. It periodically reconciles the CRDs, refreshes credentials when the native export changes, and removes the managed registration when its source disappears. The native export remains unchanged.

## 2. Create the member vClusters

We recommend **`integrations.metricsServer.enabled=true` by default for shared-node vClusters used with Kedify**. It forwards host CPU/memory metrics into the member API, which central KPA uses for resource scaling. This setting requires working host metrics; it does not install a host metrics-server.

The commands use vCluster’s version defaults. Keep the CLI current to use the current stable release. For reproducible deployments, add `--version <version>` to Helm or `--chart-version <version>` to the CLI. Review [vCluster’s shared-node prerequisites](https://www.vcluster.com/docs/vcluster/quick-start/shared-nodes) for your host and selected release.

### Helm chart

```bash
for member in a b; do
  helm upgrade --install "vc-$member" vcluster \
    --repo https://charts.loft.sh \
    --namespace "vcluster-$member" --create-namespace \
    --set integrations.metricsServer.enabled=true \
    --wait --timeout 5m
done
```

### vCluster CLI

```bash
for member in a b; do
  vcluster create "vc-$member" \
    --driver helm \
    --namespace "vcluster-$member" \
    --set integrations.metricsServer.enabled=true \
    --connect=false
done
```

`--connect=false` keeps the current central context unchanged. The commands below use `vcluster connect` command mode for member operations. Central controllers connect through each member’s internal Service using the exported credentials. No member KEDA/KPA deployment is needed.

## 3. Verify automatic registration

Run this from the central cluster context:

```bash
CENTRAL_CONTEXT=$(kubectl config current-context)

kubectl --context "$CENTRAL_CONTEXT" -n keda get clusterregistrations
for member in a b; do
  registration=$(kubectl --context "$CENTRAL_CONTEXT" -n keda get clusterregistrations -o json |
    jq -er --arg member "vc-$member.vcluster-$member" \
      '.items[] | select(.spec.provider == "vcluster" and .spec.clusterName == $member) | .metadata.name') || break
  kubectl --context "$CENTRAL_CONTEXT" -n keda wait \
    --for=condition=Ready "clusterregistration/$registration" --timeout=120s || break
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl get crd \
    scaledobjects.keda.sh scaledjobs.keda.sh \
    triggerauthentications.keda.sh clustertriggerauthentications.keda.sh \
    kedifypodautoscalers.autoscaling.kedify.io
done
kubectl --context "$CENTRAL_CONTEXT" -n keda logs deploy/keda-operator --tail=100
kubectl --context "$CENTRAL_CONTEXT" -n keda logs deploy/kpa --tail=100
```

Confirm a registration exists for each expected member, both are ready, and **both** central KEDA and KPA connect. `KedifyConfiguration` connectivity alone does not verify dedicated consumers.

## 4. Deploy a CPU-scaled workload in each member

Each Deployment declares CPU requests so KPA can calculate utilization. Its container stays idle until the load step. Apply the ordinary ScaledObject in the same member as its target:

```bash
for member in a b; do
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl apply -f - <<EOF_WORKER
apiVersion: v1
kind: Namespace
metadata:
  name: scaling-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: worker-$member
  namespace: scaling-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: worker-$member
  template:
    metadata:
      labels:
        app: worker-$member
    spec:
      containers:
        - name: worker
          image: busybox:1.37.0
          command: ["sh", "-c", "sleep 3600"]
          resources:
            requests:
              cpu: 100m
              memory: 16Mi
            limits:
              cpu: 500m
              memory: 64Mi
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: worker-$member
  namespace: scaling-demo
  annotations:
    autoscaling.kedify.io/class: kpa
spec:
  scaleTargetRef:
    name: worker-$member
  minReplicaCount: 1
  maxReplicaCount: 3
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleDown:
          stabilizationWindowSeconds: 60
  triggers:
    - type: cpu
      metricType: Utilization
      metadata:
        value: "70"
EOF_WORKER
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl -n scaling-demo rollout status "deployment/worker-$member" --timeout=120s
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl top pods -n scaling-demo
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl -n scaling-demo get scaledobject,kpa
done
```

Wait until `kubectl top pods` returns member pod metrics before generating load. KEDA creates each KPA in its member; central KPA evaluates that member’s pod metrics and patches the member Deployment’s scale. CPU-only scaling needs a running pod to supply demand, so this example keeps a minimum of one replica.

For external triggers, the ScaledObject and authentication resources also belong to the member. Central KEDA evaluates them with member-scoped clients, and central KPA requests the corresponding member-scoped external metrics over gRPC. Ordinary ScaledJobs and their generated Jobs likewise belong to their member.

## 5. Verify independent scaling

In one terminal, watch member A’s replicas:

```bash
vcluster connect vc-a -n vcluster-a -- kubectl -n scaling-demo get deployment worker-a -w
```

In another terminal, generate CPU load in one of member A’s worker pods for about two minutes. The container’s CPU limit bounds the load:

```bash
vcluster connect vc-a -n vcluster-a -- kubectl -n scaling-demo exec deployment/worker-a -- \
  sh -c 'end=$(( $(date +%s) + 120 )); while [ "$(date +%s)" -lt "$end" ]; do :; done'
```

Check both members’ metrics and autoscaler conditions:

```bash
for member in a b; do
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl top pods -n scaling-demo
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl -n scaling-demo describe kpa "keda-hpa-worker-$member"
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl -n scaling-demo get deployment "worker-$member"
done
```

Member A should scale above one replica, up to the configured maximum of three. Member B should remain at one while idle. After the load ends, allow time for metrics collection and the 60-second scale-down stabilization window before expecting A to return to one.

## 6. Clean up

Delete the demo resources while the members are still connected, so central controllers can remove generated KPAs:

```bash
for member in a b; do
  vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl delete namespace scaling-demo
done
```

Then delete the demo vClusters through the Helm or vCluster CLI workflow you used to create them. The Agent removes their managed registrations and kubeconfig Secrets when the native exports disappear.

## Troubleshooting

- **No registration:** inspect Agent logs and `VClusterBootstrapFailed` events. All five source CRDs must exist centrally, and the native export must permit member CRD installation.

- **Ready registration, but no KPA:** check both central controller logs, the member ScaledObject conditions, and the autoscaler class. Readiness proves API connectivity; workload reconciliation needs both dedicated consumers.

- **CPU metrics unavailable:** check host and member `kubectl top pods`, the metrics-server integration, and container CPU requests. Private-node vClusters need a metrics API for their own nodes; shared-node host forwarding is not a substitute.

- **Unexpected central scaling:** keep the central mode `dedicated` and create ordinary ScaledObjects in members. DistributedScaledObjects belong to the separate [distributed walkthrough](https://docs.kedify.io/how-to/vcluster-scaling/).

See [diagnose multi-cluster controllers](https://docs.kedify.io/troubleshooting/platform-scaling/) and [KPA behavior](https://docs.kedify.io/features/kedify-pod-autoscaler/) for further checks.

---
Canonical: https://docs.kedify.io/how-to/vcluster-dedicated-scaling/
Source: src/content/docs/how-to/vcluster-dedicated-scaling.mdx
Documentation index: https://docs.kedify.io/llms.txt
