, ,

VKS Cluster YAML Edits That Change a Live Deployment (VCAP-VKS Exam Series, Part 29)

Objective 4.14 in practice: which fields in a VKS cluster manifest you may edit, which edits roll every node, which the admission webhook refuses, and how to force a redeployment when an accepted edit never reaches the nodes.

VCAP-VKS Exam Series · Part 29 of 34
# Tested on VCF 9.0 GA, vSphere Supervisor 9.0, VKS 3.3.1, ClusterClass builtin-generic-v3.3.0, # kubectl v1.33.1, kubectl-vsphere plugin shipped with vCenter 9.0, VCF CLI 9.0 $ kubectl edit cluster vks-app-01 -n ns-payments error: clusters.cluster.x-k8s.io "vks-app-01" could not be patched: admission webhook "capi.mutating.tanzukubernetescluster.run.tanzu.vmware.com" denied the request: Cluster and variable validation failed: [spec.topology.variables[defaultStorageClass]: Invalid value: "vsan-default": variable is not defined, spec.topology.variables[ntp]: Invalid value: "10.20.0.10": variable is not defined, spec.topology.variables[extensionCert]: Invalid value: …: variable is not defined]

Nothing was wrong with that cluster, and nothing was wrong with the three variables the webhook named. Every one of them had been valid for the eighteen months the cluster had been running. I had opened the manifest to add one storage class and, while I was in there, retyped spec.topology.class to a newer ClusterClass because it looked untidy sitting a version behind. That single extra line turned a two minute edit into a rejected patch and a list of complaints about fields I had not touched. Objective 4.14 lives precisely in that gap between a manifest that is syntactically fine and an edit the platform will actually accept.

Key takeaways: Objective 4.14 is not a YAML syntax test. It asks whether you can predict what an edit costs before you save it. Every VKS cluster edit falls into one of three buckets: reconciled quietly with no node churn, reconciled by rolling every machine in the cluster, or refused by the Cluster API admission webhook. Headline moves are kubectl edit cluster for an in place change, kubectl patch –dry-run=client for a rehearsal you can read before you commit, and a change to spec.topology.version with the kubernetes.vmware.com/skip-auto-cc-rebase annotation removed when you want a newer ClusterClass. Never set spec.topology.class by hand. On the reference estate a vmClass edit rolled all six machines in 41 minutes while an availableStorageClasses edit rolled nothing at all, and knowing which is which before you press save is the entire objective.
Who this is for: A candidate who provisioned clusters in Part 20, watched a rolling update run in Part 21, and now has to change a running cluster by hand without turning a routine edit into an unplanned rebuild. This Part covers Objective 4.14, published wording Edit YAML code to modify VKS deployment. Terms defined on first use: a Cluster object is the Cluster API resource of kind Cluster in API group cluster.x-k8s.io that represents one workload cluster; ClusterClass is the reusable template a Cluster points at through spec.topology.class, and on VCF 9.0 the supported ones are named builtin-generic-v3.x.0; topology variables are the typed key and value pairs under spec.topology.variables that customise a cluster without editing the template; an admission webhook is the API server callout that validates or mutates an object before it is persisted; VKr is a VKS Kubernetes release, the versioned bundle of a Kubernetes version and its node image; rebasing means moving a cluster from one ClusterClass version to another; KCP is KubeadmControlPlane, MD is MachineDeployment and MA is Machine, the three Cluster API objects that show you a rollout in progress. VKS is vSphere Kubernetes Service, the product formerly named TKG Service or TKGS, and on VCF 9.0 the rename is unfinished, which is why the webhook that rejects your edit still identifies itself as tanzukubernetescluster.run.tanzu.vmware.com.

Three kinds of edit and the preflight that gates them

A VKS cluster manifest looks like one document, so operators treat it like one document. It is not. Under spec.topology there are four regions with completely different behaviour when you change them. Replica counts under controlPlane and workers scale in place by adding or removing machines. Variables under spec.topology.variables split into ones that render into node configuration, which forces every machine to be replaced, and ones that only update lists the cluster reads at runtime, which change nothing about the machines at all. And spec.topology.class plus spec.topology.version sit at the top of the chain, where an edit either triggers a whole cluster upgrade or gets thrown out by the webhook.

Before touching any of it, take a copy you can diff against. This is the one preflight step that separates a recoverable evening from a bad one, because a Cluster object has no undo and no revision history. Two more checks matter on this estate. Confirm which ClusterClass the cluster currently sits on, because variable names moved between v3.1.0 and v3.2.0 and a manifest written against the old schema will be rejected wholesale on the new one. Then confirm which VM classes and storage policies are actually bound to the vSphere Namespace, because an edit naming a class the namespace does not have will be accepted by the webhook and then strand your new machines in Provisioning forever. That is the Supervisor reaching up and deciding the outcome of a workload cluster edit, and it happens in almost every failure in this Part.

$ kubectl config use-context ns-payments $ kubectl get cluster vks-app-01 -n ns-payments -o yaml > vks-app-01.before.yaml $ kubectl get cluster vks-app-01 -n ns-payments -o jsonpath='{.spec.topology.class}{"n"}{.spec.topology.version}{"n"}’ builtin-generic-v3.3.0 v1.32.0—vmware.6-fips.1-vkr.1 $ kubectl get cluster vks-app-01 -n ns-payments -o jsonpath='{range .spec.topology.variables[*]}{.name}{"n"}{end}’ vmClass storageClass vsphereOptions osConfiguration resourceConfiguration $ kubectl get virtualmachineclassbindings -n ns-payments NAME VIRTUALMACHINECLASS AGE guaranteed-medium guaranteed-medium 41d guaranteed-large guaranteed-large 41d best-effort-small best-effort-small 41d

That last command is the one candidates skip. If guaranteed-large were missing from the binding list, an otherwise perfect vmClass edit would sail past validation and then sit still, and you would spend twenty minutes reading Cluster API logs for a problem that lives in vCenter. Use the table below as the reference artifact for this Part. It is the lookup I keep open during any live cluster edit, and it answers the only question that matters in the moment: what does this line cost.

Field you editWhat it changesBlast radiusHow you verify
spec.topology.controlPlane.replicasControl plane machine count, 1 or 3Adds or removes machines, no replacement of existing oneskubectl get kcp -n ns-payments
spec.topology.workers.machineDeployments[].replicasWorker count in one node poolAdds or removes machines onlykubectl get md -n ns-payments
variables vmClassCPU and memory of every nodeFull rolling replacement, 41 minutes on six machineskubectl get ma -n ns-payments
variables vsphereOptions.persistentVolumes.availableStorageClassesStorage classes offered inside the clusterNo machine churn, list refreshed in about two minuteskubectl get sc, from inside the workload cluster
variables osConfiguration.trust or systemProxyTrusted CA bundle and proxy on every nodeNeeds a rollout, but does not always start one on VKS 3.3.0 to 3.3.2kubectl get ma, then check the node itself
variables node.labelsLabels applied to every nodeDeliberate full rollout, the supported way to force onekubectl get kcp,md,ma -n ns-payments
spec.topology.versionKubernetes release, and the ClusterClass if rebase is allowedFull upgrade, 68 minutes on this estatekubectl get cluster -o wide
spec.topology.classWhich ClusterClass the cluster renders fromRejected by the webhook when set by handDo not edit it, change version instead
What one YAML edit costs in wall clock time Reference estate, VKS 3.3.1, three control plane and three worker machines on vSAN. Median of three runs. VKr version bump plus rebase 68 min vmClass change 41 min node.labels forced rollout 38 min availableStorageClasses edit 2 min, no machine churn Measured from the moment kubectl reported the edit to the moment every Machine object reported Running on the new spec.

Numbered run, four edits and one rejection

Last Part we proved the payments namespace could be backed up and restored with Velero. This Part we modify the same cluster in place, four times, and deliberately walk into the rejection that catches people. Work from the Supervisor context throughout. Cluster objects live on the Supervisor, not inside the workload cluster, so a kubectl session pointed at the workload cluster cannot see them at all. That is the single most common reason a candidate reports that kubectl get cluster returns nothing.

Step 1, resize every node with one variable

Vertical scaling on VKS is a variable edit, not a vCenter operation. Change vmClass and the controller provisions replacement machines on the new class and retires the old ones one at a time. Rehearse it with a client side patch first so you can read the resulting object without committing anything.

$ kubectl patch cluster vks-app-01 -n ns-payments –type=json -p='[{"op":"replace","path":"/spec/topology/variables/0/value","value":"guaranteed-large"}]’ –dry-run=client -o jsonpath='{.spec.topology.variables[0]}{"n"}’ {"name":"vmClass","value":"guaranteed-large"} # happy with that, commit it $ kubectl patch cluster vks-app-01 -n ns-payments –type=json -p='[{"op":"replace","path":"/spec/topology/variables/0/value","value":"guaranteed-large"}]’ cluster.cluster.x-k8s.io/vks-app-01 patched $ kubectl get ma -n ns-payments NAME CLUSTER NODENAME PHASE AGE vks-app-01-4hkxq-2v9pl vks-app-01 vks-app-01-4hkxq-2v9pl Running 41d vks-app-01-4hkxq-t7rmb vks-app-01 vks-app-01-4hkxq-t7rmb Running 41d vks-app-01-4hkxq-w8fdc vks-app-01 Provisioning 38s vks-app-01-np1-6c9d7-hs4kz vks-app-01 vks-app-01-np1-6c9d7-hs4kz Running 41d

A new machine in Provisioning within a minute is what green looks like here. If that machine sits in Provisioning past five minutes, stop reading Cluster API logs and go look at the vSphere Namespace, because the usual cause is a VM class that was never bound to it. Part 17 covers the binding, and the pattern generalises: a workload cluster edit fails because a Supervisor object does not permit it.

Step 2, offer a second storage class without touching a node

Here is the edit people get wrong twice. Older VKS releases exposed a flat storageClasses variable. From VKS 3.2.0 the schema became hierarchical and that list moved under vsphereOptions. Broadcom documents the mapping, and using the old name on a current ClusterClass produces exactly the variable is not defined message from the opening block. Both forms are shown so you can recognise either one on sight.

# WRONG on builtin-generic-v3.2.0 and later – name: storageClasses value: – vsan-default-storage-policy – vsan-highperf-storage-policy # CORRECT on builtin-generic-v3.2.0 and later – name: vsphereOptions value: persistentVolumes: availableStorageClasses: – vsan-default-storage-policy – vsan-highperf-storage-policy # apply it, then read it back from inside the workload cluster $ kubectl get sc NAME PROVISIONER RECLAIMPOLICY ALLOWVOLUMEEXPANSION AGE vsan-default-storage-policy csi.vsphere.vmware.com Delete true 41d vsan-highperf-storage-policy csi.vsphere.vmware.com Delete true 96s

No machine moved. That is the correct outcome and it is worth internalising, because a candidate who assumes every variable edit is expensive will refuse to make a two minute change during business hours. Note also that the storage policy must already be assigned to the vSphere Namespace before it can appear here at all, which is the same Supervisor dependency Part 26 worked through.

Step 3, the rejection, and the correct path to a newer ClusterClass

Now reproduce the failure from the top of this Part on purpose. Change spec.topology.class to a newer builtin-generic version and save. Broadcom is explicit that this is not how rebasing works and that you should not manually edit the ClusterClass version, but the manifest gives you no hint of that, and the error message points at variables rather than at the field you actually changed.

$ kubectl patch cluster vks-app-01 -n ns-payments –type=merge -p='{"spec":{"topology":{"class":"builtin-generic-v3.4.0"}}}’ Error from server: admission webhook "capi.mutating.tanzukubernetescluster.run.tanzu.vmware.com" denied the request: Cluster and variable validation failed: [spec.topology.variables[defaultStorageClass]: Invalid value: "vsan-default": variable is not defined, … ] # correct path: let the platform rebase during a version change $ kubectl annotate cluster vks-app-01 -n ns-payments kubernetes.vmware.com/skip-auto-cc-rebase- cluster.cluster.x-k8s.io/vks-app-01 annotated $ kubectl patch cluster vks-app-01 -n ns-payments –type=merge -p='{"spec":{"topology":{"version":"v1.33.2—vmware.1-fips.1-vkr.1"}}}’ cluster.cluster.x-k8s.io/vks-app-01 patched

Two details decide whether this works. Clusters created before VKS 3.2.0 carry the skip-auto-cc-rebase annotation automatically after the service is upgraded, so the removal above is mandatory on them and a no operation on newer clusters. And removal only takes effect when an upgrade starts, so removing the annotation on its own changes nothing, and removing it during an upgrade already in flight is too late. Confirm the exact VKr string with kubectl get vkr on the Supervisor before you type it, because a version that does not exist is accepted into spec and then quietly never reconciles.

Step 4, force a redeployment when an accepted edit goes nowhere

On VKS 3.3.0, 3.3.1 and 3.3.2 with builtin-generic-v3.3.x, node configuration is held in a Secret that is referenced by a KubeadmConfig per machine. When you change osConfiguration.trust or systemProxy or resourceConfiguration.systemReserved, the service rewrites that Secret in place and leaves the name alone, so nothing downstream notices and no machine is replaced. kubectl reports success. Your nodes keep the old configuration. Broadcom documents the workaround and states that automatic redeployment after these changes arrives in 3.3.3.

# add or change this variable to force a cluster wide rolling redeployment variables: – name: vmClass value: guaranteed-large – name: node value: labels: update: manual-2026-08 $ kubectl get kcp,md,ma -n ns-payments NAME CLUSTER REPLICAS READY UPDATED UNAVAILABLE kubeadmcontrolplane.controlplane.cluster.x-k8s.io/vks-app-01-4hkxq vks-app-01 3 2 1 1 NAME CLUSTER REPLICAS READY UPDATED UNAVAILABLE machinedeployment.cluster.x-k8s.io/vks-app-01-np1 vks-app-01 3 3 0 0

UPDATED lagging REPLICAS is the rollout in progress. That labels block stays in the manifest until you remove it, and changing the value again queues another full redeployment, which is why I date stamp the value rather than leaving it as the word manual. Two engineers reaching for the same trick a week apart with the same value produce no rollout at all and a long argument about whether the command worked.

Verification, rollback and a failure lookup

flowchart TD
  A[Open cluster manifest on Supervisor] --> B{Which field changed}
  B -->|replicas| C[Machines added or removed]
  B -->|vmClass or osConfiguration| D[Rolling replacement of all machines]
  B -->|availableStorageClasses| E[No machine churn, list refreshed]
  B -->|version| F[VKr upgrade plus auto rebase]
  B -->|class by hand| G[Webhook rejects the patch]
  D --> H[Watch kcp md ma until UPDATED equals REPLICAS]
  C --> H
  F --> H
  E --> I[Read storageclasses inside the cluster]
  G --> J[Revert class, change version instead]
  H --> K[Compare against the saved before file]
  I --> K
Every edit in objective 4.14 resolves to one of five outcomes. Deciding which before you save is the skill being tested.

Rollback deserves plain talk. A Cluster object has no rollback verb. What you have is the before file you saved in preflight and the fact that most edits are symmetric: put the old vmClass back and the cluster rolls again to the original size, at the same cost in time. Two edits are not symmetric. A version bump cannot be reversed, because VKS does not downgrade Kubernetes releases, so treat spec.topology.version as a one way door and rehearse it on a throwaway cluster first. A ClusterClass rebase inherits that same one way property, since it only happens as part of a version change. Reducing control plane replicas from three to one is technically reversible but destroys quorum on the way through, so it is not something to do casually on a cluster carrying work.

What you seeReal causeFix
variable is not defined, listing fields you never touchedYou edited spec.topology.class by hand and the old variables no longer validateRestore the original class, remove the skip-auto-cc-rebase annotation, change spec.topology.version instead
kubectl reports patched, no machine is replaced, nodes keep old configVKS 3.3.0 to 3.3.2 rewrites the config Secret without changing its nameAdd or change the node.labels variable to force a rollout, or move the service to 3.3.3 or later
New Machine stuck in Provisioning past five minutesVM class named in the edit is not bound to the vSphere NamespaceAdd the class to the namespace on the Supervisor, the pending machine then proceeds on its own
PVC stays Pending after adding a storage class to the manifestClass was added under the retired storageClasses variable, or the policy is not on the namespaceMove it under vsphereOptions.persistentVolumes.availableStorageClasses and confirm the namespace has the policy
kubectl get cluster returns no resources foundSession is pointed at the workload cluster, where Cluster objects do not existSwitch to the Supervisor namespace context with kubectl config use-context
Rollout stalls, old machine will not deleteA PodDisruptionBudget inside the workload cluster blocks the drainInspect with kubectl get pdb -A on the workload cluster and relax or scale the offending workload
Version accepted, nothing ever happensVKr string does not match a release the content library has syncedList releases with kubectl get vkr and retype the exact name
What a clean result looks like: a saved before file you can diff against; kubectl reporting the object patched with no webhook complaint; kcp, md and ma all showing UPDATED equal to REPLICAS and UNAVAILABLE at zero; the change actually visible where it should be, meaning kubectl get sc inside the cluster for a storage edit or the node spec for a class change; spec.topology.class untouched by your hands; and no node.labels marker left behind that you did not intend to keep.

Exam focus for objective 4.14

Objective 4.14, Edit YAML code to modify VKS deployment: this objective expects you to read a cluster manifest, locate the field that produces a stated outcome, and know the consequence of changing it. Because the source material is a block of YAML, it attracts the visual item types more than most objectives on this exam: hot area and point and click over a rendered manifest, drag and drop matching a change to its effect, and build list ordering the steps of a rebase. Multiple selection items tend to ask which of several edits force a node rollout. The trap that catches experienced admins is treating acceptance as completion. You are fluent in kubectl, the patch returns cleanly, and you move on. On VKS 3.3.x a whole class of configuration edits is accepted and then silently not propagated, and the second trap sits right beside it, which is reaching for spec.topology.class because that is the field named after the thing you want to change. Anchor both to the same rule: on this platform you declare intent through version and variables, and the Supervisor decides the template.

Objective checkpoint

Three original questions, written from the published objective wording:

1. A cluster provisioned before the VKS service was upgraded is running on builtin-generic-v3.3.0 and must move to a newer ClusterClass. Which change starts the rebase? Answer: remove the kubernetes.vmware.com/skip-auto-cc-rebase annotation and then change spec.topology.version. Rebasing is a side effect of a version change, and the annotation is applied automatically to pre 3.2.0 clusters, so leaving it in place blocks the rebase silently.

2. Which single edit changes what an application can consume without replacing any node? Answer: adding an entry to the availableStorageClasses list under the vsphereOptions variable. That variable updates cluster facing configuration rather than node configuration, so no machine is rendered again and nothing rolls.

3. On VKS 3.3.1 you edit osConfiguration.trust to add a certificate authority, kubectl reports the cluster patched, and thirty minutes later no machine has been replaced and no node trusts the new authority. What is the next action? Answer: add or change the node.labels variable to trigger a cluster wide rolling redeployment. On 3.3.0 through 3.3.2 the configuration Secret is rewritten under its existing name, so nothing downstream detects a change and the nodes are never rebuilt.

Editing habits worth standardising

My war story is the one at the top, and it cost more than pride. That rejected patch happened at 22:10 inside a 90 minute Tuesday window. I spent 25 minutes convinced the storage class was the problem, because the storage class was what I had come to change and the error named storage first. Only when I diffed against a manifest from the previous week did the class line show up. Reverting it and reapplying just the storage variable took under two minutes. Twenty five minutes lost to an edit I made for tidiness, on a field I had no business touching.

So, three habits. Save the object to a file before every edit, without exception, because diff is the only rollback you have. Change one thing per edit, since the webhook error names symptoms rather than causes and a single field change makes the cause obvious by elimination. And prefer kubectl patch with an explicit JSON pointer over kubectl edit for anything scripted or repeated, because an interactive editor invites the exact opportunistic tidying that started this Part. My verdict on the popular study advice for this objective: memorising the full variable schema is the wrong investment. Schema names have already moved once, between VKS 3.1.x and 3.2.0, and they will move again. What transfers is the blast radius table above and the rule that the Supervisor owns the template while you own the intent. Tonight, take one lab cluster, save its manifest, change vmClass, and time the rollout yourself. A number you measured is worth more under exam pressure than a number you read. Next Part opens Section 5 and moves from making changes to diagnosing them, starting with Supervisor and VKS provisioning failures.

VCAP-VKS Exam Series · Part 29 of 34
« Previous: Part 28  |  Guide  |  VKS Series  |  Next: Part 30 »

References

About The Author


Discover more from Journal of Intelligent Infrastructure

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure

Subscribe now to keep reading and get access to the full archive.

Continue reading