{"version": 2, "width": 112, "height": 40, "timestamp": 1789485156, "env": {"TERM": "xterm-256color"}, "title": "vCluster + Kedify: independent distributed scaling"}
[1.261204, "o", "\u001b[2J\u001b[H\u001b[1;36m01 / ONE CENTRAL SCALING CONTROL PLANE\u001b[0m\r\n\r\nKEDA and Kedify are already installed centrally. Tenant clusters will join automatically.\r\n\r\n"]
[4.263374, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n keda get deployments\u001b[0m\r\n"]
[5.604041, "o", "NAME                              READY   UP-TO-DATE   AVAILABLE   AGE\r\nkeda-admission-webhooks           1/1     1            1           13m\r\nkeda-operator                "]
[5.604103, "o", "     1/1     1            1           13m\r\nkeda-operator-metrics-apiserver   1/1     1            1           13m\r\nkedify-agent                      1/1     1            1           12m\r\n"]
[7.610201, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mcat manifests/discovery-values.yaml\u001b[0m\r\n"]
[8.614229, "o", "global:\r\n  features:\r\n    multicluster:\r\n      enabled: true\r\n      vClusterDiscoveryEnabled: true\r\n"]
[10.617321, "o", "\r\n\u001b[38;5;117mConfigure discovery once. New vClusters supply their native kubeconfig Secret and Service.\u001b[0m\r\n"]
[13.620406, "o", "\u001b[2J\u001b[H\u001b[1;36m02 / CREATE TWO VCLUSTERS\u001b[0m\r\n\r\nStandard vCluster installations. No KEDA installation or registration command inside either tenant.\r\n\r\n"]
[16.624388, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster create vc-a --driver helm -n vcluster-a \\\r\n  --chart-version 0.37.1 -f manifests/vcluster.yaml --connect=false\u001b[0m\r\n"]
[18.333675, "o", "\u001b[0;1;37m15:12:54 \u001b[0m\u001b[0;1;36minfo \u001b[0mCreating namespace vcluster-a\r\n"]
[18.465252, "o", "\u001b[0;1;37m15:12:54 \u001b[0m\u001b[0;1;36minfo \u001b[0mCreate vcluster vc-a...\r\n"]
[22.285517, "o", "\u001b[0;1;37m15:12:58 \u001b[0m\u001b[0;1;32mdone \u001b[0mSuccessfully created virtual cluster vc-a in namespace vcluster-a. \r\n- Use 'vcluster connect vc-a --namespace vcluster-a' to access the virtual cluster\r\n- Use `vcluster connect vc-a --namespace vcluster-a -- kubectl get ns` to run a command directly within the vcluster\r\n"]
[24.295936, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster create vc-b --driver helm -n vcluster-b \\\r\n  --chart-version 0.37.1 -f manifests/vcluster.yaml --connect=false\u001b[0m\r\n"]
[26.004353, "o", "\u001b[0;1;37m15:13:02 \u001b[0m\u001b[0;1;36minfo \u001b[0mCreating namespace vcluster-b\r\n"]
[26.138192, "o", "\u001b[0;1;37m15:13:02 \u001b[0m\u001b[0;1;36minfo \u001b[0mCreate vcluster vc-b...\r\n"]
[29.660184, "o", "\u001b[0;1;37m15:13:05 \u001b[0m\u001b[0;1;32mdone \u001b[0mSuccessfully created virtual cluster vc-b in namespace vcluster-b. \r\n- Use 'vcluster connect vc-b --namespace vcluster-b' to access the virtual cluster\r\n- Use `vcluster connect vc-b --namespace vcluster-b -- kubectl get ns` to run a command directly within the vcluster\r\n"]
[31.670597, "o", "\r\n\u001b[38;5;117mThe virtual APIs start first. Kedify connects as soon as each API is ready.\u001b[0m\r\n"]
[34.673726, "o", "\r\n\u001b[1mELAPSED   VCLUSTER A API     KEDIFY A           VCLUSTER B API     KEDIFY B          \u001b[0m\r\n"]
[35.606763, "o", "0s        Starting           Waiting            Starting           Waiting           \r\n"]
[41.550397, "o", "6s        Starting           Waiting            Starting           Waiting           \r\n"]
[50.451573, "o", "15s       Starting           Waiting            Starting           Waiting           \r\n"]
[53.451829, "o", "18s       Ready              Waiting            Starting           Waiting           \r\n"]
[59.299629, "o", "24s       Ready              Waiting            Starting           Waiting           \r\n"]
[62.273717, "o", "27s       Ready              Connected          Starting           Waiting           \r\n"]
[65.291739, "o", "30s       Ready              Connected          Ready              Waiting           \r\n"]
[68.281763, "o", "33s       Ready              Connected          Ready              Waiting           \r\n"]
[71.203464, "o", "36s       Ready              Connected          Ready              Connected         \r\n"]
[71.211877, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n keda get kedifyconfiguration -o json |\r\n  jq -r '[\"MEMBER\", \"PROVIDER\", \"STATE\"],\r\n    (.items[].status.multiClusterStatus.clusters | to_entries[] |\r\n    [.key, .value.provider, .value.state]) | @tsv' | column -t\u001b[0m\r\n"]
[72.514507, "o", "MEMBER           PROVIDER  STATE\r\nvc-a.vcluster-a  vcluster  Connected\r\nvc-b.vcluster-b  vcluster  Connected\r\n"]
[74.518115, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n vcluster-a get secret vc-vc-a\u001b[0m\r\n"]
[75.885184, "o", "NAME      TYPE     DATA   AGE\r\nvc-vc-a   Opaque   5      24s\r\n"]
[77.890713, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n vcluster-b get secret vc-vc-b\u001b[0m\r\n"]
[79.221322, "o", "NAME      TYPE     DATA   AGE\r\nvc-vc-b   Opaque   5      14s\r\n"]
[81.226095, "o", "\r\n\u001b[38;5;117mThese native kubeconfig exports stay in their original namespaces. Kedify follows credential updates.\u001b[0m\r\n"]
[84.228674, "o", "\u001b[2J\u001b[H\u001b[1;36m03 / WORKLOADS IN TENANTS, POLICIES CENTRAL\u001b[0m\r\n\r\nEach DistributedScaledObject selects one member and one workload.\r\n\r\n"]
[87.232114, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl apply -f manifests/workload-a.yaml\u001b[0m\r\n"]
[91.35324, "o", "namespace/scaling-demo created\r\n"]
[91.488934, "o", "deployment.apps/worker-a created\r\n"]
[93.504355, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl apply -f manifests/workload-b.yaml\u001b[0m\r\n"]
[96.850625, "o", "namespace/scaling-demo created\r\n"]
[96.984141, "o", "deployment.apps/worker-b created\r\n"]
[98.997472, "o", "\r\n\u001b[38;5;117mThis demo uses two controlled demand values. Production metrics can come from queues or monitoring.\u001b[0m\r\n"]
[101.999969, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl apply -f manifests/metric-source.yaml\u001b[0m\r\n"]
[103.558661, "o", "namespace/scaling-demo created\r\n"]
[103.681525, "o", "configmap/demo-metrics created\r\n"]
[105.686714, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mcat manifests/dso-a.yaml\u001b[0m\r\n"]
[106.692231, "o", "apiVersion: keda.kedify.io/v1alpha1\r\nkind: DistributedScaledObject\r\nmetadata:\r\n  name: worker-a\r\n  namespace: scaling-demo\r\nspec:\r\n  memberClusters:\r\n  - name: vc-a.vcluster-a\r\n  scaledObjectSpec:\r\n    scaleTargetRef:\r\n      kind: Deployment\r\n      name: worker-a\r\n    minReplicaCount: 0\r\n    maxReplicaCount: 5\r\n    pollingInterval: 5\r\n    cooldownPeriod: 15\r\n    advanced:\r\n      horizontalPodAutoscalerConfig:\r\n        behavior:\r\n          scaleDown:\r\n            stabilizationWindowSeconds: 0\r\n    triggers:\r\n    - type: kubernetes-resource\r\n      metadata:\r\n        resourceKind: ConfigMap\r\n        resourceName: demo-metrics\r\n        key: a\r\n        targetValue: '1'\r\n"]
[108.694928, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl apply -f manifests/dso-a.yaml -f manifests/dso-b.yaml\u001b[0m\r\n"]
[110.206997, "o", "distributedscaledobject.keda.kedify.io/worker-a created\r\n"]
[110.338878, "o", "distributedscaledobject.keda.kedify.io/worker-b created\r\n"]
[112.344223, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n scaling-demo wait dso --all --for=condition=Ready --timeout=180s\u001b[0m\r\n"]
[113.735715, "o", "distributedscaledobject.keda.kedify.io/worker-a condition met\r\n"]
[113.836978, "o", "distributedscaledobject.keda.kedify.io/worker-b condition met\r\n"]
[115.843593, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n scaling-demo get dso,scaledobject,hpa \\\r\n  -o custom-columns=KIND:.kind,NAME:.metadata.name\u001b[0m\r\n"]
[117.246836, "o", "KIND                      NAME\r\nDistributedScaledObject   worker-a\r\nDistributedScaledObject   worker-b\r\nScaledObject              worker-a\r\nScaledObject              worker-b\r\nHorizontalPodAutoscaler   keda-hpa-worker-a\r\nHorizontalPodAutoscaler   keda-hpa-worker-b\r\n"]
[119.251316, "o", "\u001b[2J\u001b[H\u001b[1;36m04 / SCALE EACH TENANT INDEPENDENTLY\u001b[0m\r\n\r\nOne central installation. Separate demand and replica counts for each tenant.\r\n\r\n"]
[122.2545, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n scaling-demo patch configmap demo-metrics --type merge \\\r\n  -p '{\"data\":{\"a\":\"3\",\"b\":\"1\"}}'\u001b[0m\r\n"]
[123.581638, "o", "configmap/demo-metrics patched\r\n"]
[125.586404, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl -n scaling-demo wait deployment/worker-a \\\r\n  --for=jsonpath='{.status.availableReplicas}'=3 --timeout=180s\u001b[0m\r\n"]
[172.067043, "o", "deployment.apps/worker-a condition met\r\n"]
[174.079521, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo wait deployment/worker-b \\\r\n  --for=jsonpath='{.status.availableReplicas}'=1 --timeout=180s\u001b[0m\r\n"]
[177.595426, "o", "deployment.apps/worker-b condition met\r\n"]
[179.608434, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl -n scaling-demo get deployments\u001b[0m\r\n"]
[182.52486, "o", "NAME       READY   UP-TO-DATE   AVAILABLE   AGE\r\nworker-a   3/3     3            3           91s\r\n"]
[184.535754, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo get deployments\u001b[0m\r\n"]
[187.441069, "o", "NAME       READY   UP-TO-DATE   AVAILABLE   AGE\r\nworker-b   1/1     1            1           90s\r\n"]
[189.453405, "o", "\r\n\u001b[38;5;117mTenant A: 3 replicas. Tenant B: 1 replica. Both decisions are managed centrally.\u001b[0m\r\n"]
[192.455694, "o", "\u001b[2J\u001b[H\u001b[1;36m05 / ONE TENANT IDLES WHILE THE OTHER GROWS\u001b[0m\r\n\r\nScale A to zero while B scales from one replica to two.\r\n\r\n"]
[195.458582, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n scaling-demo patch configmap demo-metrics --type merge \\\r\n  -p '{\"data\":{\"a\":\"0\",\"b\":\"2\"}}'\u001b[0m\r\n"]
[196.856565, "o", "configmap/demo-metrics patched\r\n"]
[198.863431, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl -n scaling-demo wait deployment/worker-a \\\r\n  --for=jsonpath='{.spec.replicas}'=0 --timeout=180s\u001b[0m\r\n"]
[210.340993, "o", "deployment.apps/worker-a condition met\r\n"]
[212.355321, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl -n scaling-demo wait pod \\\r\n  -l app=worker-a --for=delete --timeout=120s\u001b[0m\r\n"]
[217.027864, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo wait deployment/worker-b \\\r\n  --for=jsonpath='{.status.availableReplicas}'=2 --timeout=180s\u001b[0m\r\n"]
[231.258858, "o", "deployment.apps/worker-b condition met\r\n"]
[233.268666, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl -n scaling-demo get deployments,pods\u001b[0m\r\n"]
[236.142031, "o", "NAME                       READY   UP-TO-DATE   AVAILABLE   AGE\r\ndeployment.apps/worker-a   0/0     0        "]
[236.142064, "o", "    0           2m25s\r\n"]
[238.152509, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo get deployments,pods\u001b[0m\r\n"]
[241.023447, "o", "NAME                       READY   UP-TO-DATE   AVAILABLE   AGE\r\ndeployment.apps/worker-b   2/2     2            2           2m24s\r\n"]
[241.023609, "o", "\r\nNAME                            READY   STATUS    RESTARTS   AGE\r\npod/worker-b-58b458544f-c2swg   1/1     "]
[241.023683, "o", "Running   0          116s\r\npod/worker-b-58b458544f-nfr76   1/1     Running   0          11s\r\n"]
[243.034574, "o", "\u001b[2J\u001b[H\u001b[1;36m06 / BOTH TENANTS RETURN TO ZERO\u001b[0m\r\n\r\nIdle workload pods disappear. The virtual APIs remain available.\r\n\r\n"]
[246.037627, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n scaling-demo patch configmap demo-metrics --type merge \\\r\n  -p '{\"data\":{\"a\":\"0\",\"b\":\"0\"}}'\u001b[0m\r\n"]
[247.381104, "o", "configmap/demo-metrics patched\r\n"]
[249.387434, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo wait deployment/worker-b \\\r\n  --for=jsonpath='{.spec.replicas}'=0 --timeout=180s\u001b[0m\r\n"]
[260.487081, "o", "deployment.apps/worker-b condition met\r\n"]
[262.499752, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo wait pod \\\r\n  -l app=worker-b --for=delete --timeout=120s\u001b[0m\r\n"]
[267.295414, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-a -n vcluster-a -- kubectl -n scaling-demo get deployments,pods\u001b[0m\r\n"]
[270.155857, "o", "NAME                       READY   UP-TO-DATE   AVAILABLE   AGE\r\ndeployment.apps/worker-a   0/0     0            0           2m59s\r\n"]
[272.168539, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mvcluster -s connect vc-b -n vcluster-b -- kubectl -n scaling-demo get deployments,pods\u001b[0m\r\n"]
[274.973413, "o", "NAME                       READY   UP-TO-DATE   AVAILABLE   AGE\r\ndeployment.apps/worker-b   0/0     0            0           2m58s\r\n"]
[276.986803, "o", "\r\n\u001b[1;32m$\u001b[0m \u001b[1mkubectl -n keda get kedifyconfiguration -o json |\r\n  jq -r '[\"MEMBER\", \"PROVIDER\", \"STATE\"],\r\n    (.items[].status.multiClusterStatus.clusters | to_entries[] |\r\n    [.key, .value.provider, .value.state]) | @tsv' | column -t\u001b[0m\r\n"]
[278.31952, "o", "MEMBER           PROVIDER  STATE\r\nvc-a.vcluster-a  vcluster  Connected\r\nvc-b.vcluster-b  vcluster  Connected\r\n"]
[280.32415, "o", "\r\n\u001b[38;5;117mTwo tenant APIs. Independent scaling. One central Kedify installation.\u001b[0m\r\n"]
[283.326838, "o", "\r\nHands-on tutorial: https://docs.kedify.io/how-to/vcluster-scaling/\r\n"]
