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.
Prerequisites
Section titled “Prerequisites”- A central cluster with a compatible Kedify
0.8.1release bundle or later installed inkeda, on a Kubernetes version supported by its KPA chart. - A working host
metrics.k8s.ioAPI. Checkkubectl top pods -Ain 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.
- Network access from central Agent, KEDA, and KPA to each member API. See multi-cluster networking.
1. Enable dedicated mode and discovery
Section titled “1. Enable dedicated mode and discovery”Merge these values into your central Agent’s maintained Helm configuration and apply your usual Helm or GitOps upgrade:
global: features: multicluster: enabled: true type: dedicated vClusterDiscoveryEnabled: truekeda: enabled: truekpa: enabled: trueThe 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
Section titled “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 for your host and selected release.
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 5mdonefor member in a b; do vcluster create "vc-$member" \ --driver helm \ --namespace "vcluster-$member" \ --set integrations.metricsServer.enabled=true \ --connect=falsedone--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
Section titled “3. Verify automatic registration”Run this from the central cluster context:
CENTRAL_CONTEXT=$(kubectl config current-context)
kubectl --context "$CENTRAL_CONTEXT" -n keda get clusterregistrationsfor 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.iodonekubectl --context "$CENTRAL_CONTEXT" -n keda logs deploy/keda-operator --tail=100kubectl --context "$CENTRAL_CONTEXT" -n keda logs deploy/kpa --tail=100Confirm 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
Section titled “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:
for member in a b; do vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl apply -f - <<EOF_WORKERapiVersion: v1kind: Namespacemetadata: name: scaling-demo---apiVersion: apps/v1kind: Deploymentmetadata: name: worker-$member namespace: scaling-demospec: 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/v1alpha1kind: ScaledObjectmetadata: name: worker-$member namespace: scaling-demo annotations: autoscaling.kedify.io/class: kpaspec: 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,kpadoneWait 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
Section titled “5. Verify independent scaling”In one terminal, watch member A’s replicas:
vcluster connect vc-a -n vcluster-a -- kubectl -n scaling-demo get deployment worker-a -wIn 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:
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:
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"doneMember 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
Section titled “6. Clean up”Delete the demo resources while the members are still connected, so central controllers can remove generated KPAs:
for member in a b; do vcluster connect "vc-$member" -n "vcluster-$member" -- kubectl delete namespace scaling-demodoneThen 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
Section titled “Troubleshooting”- No registration: inspect Agent logs and
VClusterBootstrapFailedevents. 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
dedicatedand create ordinary ScaledObjects in members. DistributedScaledObjects belong to the separate distributed walkthrough.
See diagnose multi-cluster controllers and KPA behavior for further checks.