, ,

Day-2 Operations on VKS, Lifecycle, Upgrades and Scaling vs BOSH (TKGI to VKS Series, Part 24)

After the cutover, Day-2 on VKS replaces BOSH and Ops Manager with a declarative model. Here is how to upgrade Kubernetes versions, scale node pools, and roll back safely, with the commands that map to your old TKGI habits.

TKGI to VKS Series · Part 24 of 26
Key takeaways: VKS is declarative, you edit a cluster object and a controller reconciles, there is no BOSH deploy and no Ops Manager tile.
A Supervisor upgrade does not upgrade your clusters, VKS versions are independent and you opt each cluster in.
A Kubernetes minor version upgrade is one directional, you cannot downgrade the VKr, so a Velero backup is mandatory before you patch.
Node count lives in the MachineDeployment, kubectl scale on a workload does nothing to your node pool.
Headline command: kubectl patch cluster prod-vks -n prod-ns –type merge -p (new topology version)
Who this is for: Platform engineers, SREs and VMware admins now operating a VKS cluster in production after the TKGI cutover. Starting point assumed: production workloads live on a VKS cluster on VCF 9, TKGI has been decommissioned or is idle, and you need the routine Day-2 operations. VKS is the vSphere Kubernetes Service, the managed Kubernetes on vSphere Supervisor, TKGI is Tanzu Kubernetes Grid Integrated the outgoing platform, BOSH is the release engineering system that deployed and monitored every TKGI VM, Ops Manager is the Tanzu console that upgraded BOSH and pushed tile updates, a VKr is a vSphere Kubernetes Release that pins a Kubernetes version and its add-ons, and Velero is the backup and restore tool used through this series.

Day-2 model and where the migration stands

A platform lead on my team asked one question in the first week after cutover: where did the BOSH director go, and what runs the upgrades now. It is the right question, because Day-2 on VKS is not a smaller version of Day-2 on TKGI, it is a different machine. On TKGI you told BOSH what you wanted and BOSH deployed it, step by step, until it finished. On VKS you write the desired state into a Kubernetes object and a controller reconciles the cluster toward it, continuously, whether or not you are watching.

Last part cut production traffic over to the VKS cluster and kept TKGI warm through the rollback window. That window has now closed cleanly, so this part is about living on VKS: upgrading Kubernetes versions, scaling node pools, and backing out when a change goes wrong. Ops Manager and BOSH do not exist on this platform, and their absence changes how every routine task is done. What used to be a tile click and a long deploy is now a field edit and a watch.

That single shift, from imperative deploy to declarative reconcile, is the thing to internalize before any command makes sense. A reconcile loop is patient and self-correcting, but it also means the platform, not your runbook, decides the order and pace of a change. Your job moves from running the upgrade to describing the end state correctly and reading what the controller is doing.

Lifecycle models compared, BOSH deploy vs declarative reconcile

Both platforms give you managed Kubernetes, but the operating model underneath is opposite in one way that matters. BOSH is imperative at the seams, you run tkgi upgrade-cluster or bosh deploy, a process executes, and when it finishes the change is done. VKS is declarative all the way down, you change a field, a controller notices the drift between desired and actual state, and it closes the gap on its own schedule. That difference decides how you upgrade, how you scale, and above all how you roll back.

DimensionTKGI, BOSH and Ops ManagerVKS, declarative Cluster API
What drives a changeA deploy from a manifest, a tile in Ops ManagerEdit the cluster spec, the controller reconciles
Kubernetes upgradetkgi upgrade-cluster after a tile updatePatch topology version to a new VKr
Node scalingChange the plan or resize, then redeployEdit MachineDeployment replicas or annotate for autoscaler
Health during changeBOSH canary instances and max in flightRolling machine replace, one at a time
RollbackRedeploy the prior stemcell or releaseNo version downgrade, restore from Velero
Platform upgradeOps Manager upgrades the BOSH director and pulls tilesSupervisor and VKS upgrade independently
Source of truthThe BOSH director databaseKubernetes objects, Cluster and MachineDeployment

Here is the verdict for anyone weighing the two. For steady-state operations the declarative model wins, because a controller that continuously reconciles catches drift you would otherwise fix by hand. Where BOSH still feels safer is rollback, and that gap is real, on VKS a Kubernetes minor version upgrade is one directional and you cannot edit the VKr back to undo it. So the reconcile model buys less operational toil at the cost of a harder undo, which is exactly why Velero backups move from nice to have on TKGI to mandatory on VKS.

flowchart TD
  A[Edit cluster spec, new VKr version] --> B[Cluster controller detects drift]
  B --> C[Roll a new control plane machine]
  C --> D[Drain and replace each worker machine]
  D --> E{Health checks pass}
  E -->|yes| F[Cluster Provisioned at new version]
  E -->|no| G[Pause rollout, fix blocker or restore Velero]
  G --> D
The reconcile loop replaces machines one at a time and never has a step without an exit

Preflight before a cluster upgrade

Prove three things before you touch a production cluster version: the target VKr is present and marked compatible, the jump is a single minor version, and a fresh Velero backup exists because there is no downgrade to fall back on. Skip the compatibility check and you learn the answer mid rollout, with half your nodes on the new version and half on the old.

# Tested against: VCF 9.0, VKS on Supervisor, VCF CLI 9.0, kubectl vSphere plugin 9.0, TKGI 1.18 source # authenticate to the Supervisor namespace that owns the cluster kubectl vsphere login –server=sup.corp.local –vsphere-username admin@vsphere.local –tanzu-kubernetes-cluster-namespace prod-ns # list the vSphere Kubernetes releases and their compatibility kubectl get vkr # NAME VERSION COMPATIBLE CREATED # v1.30.8—vmware.1-vkr.1 v1.30.8+vmware.1 True 40d # v1.31.4—vmware.1-vkr.1 v1.31.4+vmware.1 True 12d # read the current cluster version kubectl get cluster prod-vks -n prod-ns -o jsonpath='{.spec.topology.version}’ # v1.30.8+vmware.1

Kubernetes does not allow skipping a minor version, and the admission webhook enforces it. A jump from 1.30 straight to 1.32 is rejected before a single node moves, which is the safe failure. A worse version of this mistake is assuming the platform will let you catch up in one hop after you have fallen behind on patching, so stay within one minor of the latest compatible VKr and you never meet this wall.

# a skip-level upgrade is rejected by the version webhook kubectl patch cluster prod-vks -n prod-ns –type merge -p ‘{"spec":{"topology":{"version":"v1.32.1+vmware.1"}}}’ # Error from server: admission webhook denied the request: # upgrading from 1.30 to 1.32 skips minor version 1.31, upgrade one minor version at a time

Rolling upgrade of a VKS cluster, step by step

An upgrade is one edit and a watch. First, patch the cluster topology version to the compatible VKr you picked. Second, watch the machines roll, VKS provisions a new control plane node, joins it, retires an old one, then walks the worker MachineDeployments the same way, one machine at a time. Third, confirm every node reports the new version and the cluster phase returns to Provisioned. No tile, no director, no separate upgrade binary, the controller does the work.

# step 1, edit the topology version to the target VKr kubectl patch cluster prod-vks -n prod-ns –type merge -p ‘{"spec":{"topology":{"version":"v1.31.4+vmware.1"}}}’ # cluster.cluster.x-k8s.io/prod-vks patched # step 2, watch machines roll one at a time kubectl get machine -n prod-ns # NAME PHASE VERSION # prod-vks-cp-fghij Provisioning v1.31.4+vmware.1 <- new control plane joining # prod-vks-cp-abcde Running v1.30.8+vmware.1 # prod-vks-md-0-klmno Running v1.30.8+vmware.1 # step 3, verification, what green looks like kubectl get cluster prod-vks -n prod-ns # NAME PHASE VERSION AGE # prod-vks Provisioned v1.31.4+vmware.1 63d kubectl get nodes -o custom-columns=NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion # every node now reports v1.31.4

On my first production upgrade this rolled cleanly through the control plane and then stopped dead on worker two for 40 minutes. A PodDisruptionBudget on the payments app was set to allow zero disruptions, so the drain could never evict the last pod and the machine sat in Deleting. Relaxing the budget to allow one eviction let the rollout finish in another eight minutes. A budget that permits no disruption and a rolling upgrade that must evict every pod cannot both win, and the controller will wait rather than break your availability promise.

# rollout stalls, one old worker will not drain kubectl get machine -n prod-ns # prod-vks-md-0-klmno Deleting v1.30.8+vmware.1 <- stuck 40m kubectl describe machine prod-vks-md-0-klmno -n prod-ns | grep -A1 Drain # Warning DrainBlocked eviction blocked by PodDisruptionBudget payments-pdb, 0 disruptions allowed # fix, relax the PDB so at least one pod can be evicted, then the drain completes kubectl patch pdb payments-pdb -n payments --type merge -p '{"spec":{"minAvailable":1}}' # poddisruptionbudget.policy/payments-pdb patched

Keep the mapping below within reach for the first month on VKS. It is the reference artifact for this part, the muscle-memory translation from the BOSH and TKGI commands your team knows by heart to the kubectl and VCF CLI equivalents that replace them. Pin it next to your terminal, because the wrong reflex here wastes an afternoon.

Day-2 taskTKGI commandVKS command
Upgrade Kubernetestkgi upgrade-cluster prodkubectl patch cluster prod-vks (topology version)
Scale workerstkgi resize prod –num-nodes 6kubectl scale machinedeployment prod-vks-md-0 –replicas 6
List versionstkgi cluster prodkubectl get vkr
Check node statusbosh -d service-instance instanceskubectl get machine -n prod-ns
Autoscale nodesNot native, resize by handAnnotate MachineDeployment, install Cluster Autoscaler
Roll backbosh -d service-instance deploy priorRestore the Velero backup on a known-good cluster

Numbers help set expectations for the first upgrade. On the running example, a cluster with three control plane and six worker nodes, a clean VKS rolling upgrade ran end to end in 54 minutes, roughly eight to nine minutes per machine as each drained and rejoined. The equivalent tkgi upgrade-cluster on the old platform took about 62 minutes for the same shape. The version with the PodDisruptionBudget stall took 94, and that gap is the cost of a preflight you skipped.

Kubernetes upgrade wall-clock, same nine node clusterMinutes end to end, three control plane and six worker nodes62BOSH upgrade54VKS clean94VKS with PDB stall
A clean rolling upgrade edges out BOSH, a blocked drain wipes out the win

Scaling nodes, manual and autoscaler

Scaling is where the declarative model trips up BOSH veterans most, because the obvious command does nothing. Node count on VKS lives in the MachineDeployment replicas field, not in any kubectl scale of a workload. To add workers you scale the MachineDeployment, and the same controller that ran the upgrade provisions the new nodes and joins them.

# manual scale, node count lives in the MachineDeployment kubectl scale machinedeployment prod-vks-md-0 -n prod-ns –replicas=6 # machinedeployment.cluster.x-k8s.io/prod-vks-md-0 scaled # the reflex that does nothing, scaling a workload does not add nodes kubectl scale deployment web -n payments –replicas=6 # deployment.apps/web scaled <- more pods, same node count, some may go Pending

For elastic workloads, install the Cluster Autoscaler as a standard package and let node count follow demand. Here is the catch that cost me an afternoon: the autoscaler reads its bounds from min and max annotations on the MachineDeployment object, and if you put them on the Cluster or a MachinePool it silently ignores the group. The pod stays healthy, it just never scales, which is the worst kind of failure because nothing looks broken.

# enable autoscaling by annotating the MachineDeployment with min and max kubectl annotate machinedeployment prod-vks-md-0 -n prod-ns cluster.x-k8s.io/cluster-api-autoscaler-node-group-min-size=3 cluster.x-k8s.io/cluster-api-autoscaler-node-group-max-size=8 # machinedeployment.cluster.x-k8s.io/prod-vks-md-0 annotated # failure seen the first time, pod healthy but never scales kubectl -n kube-system logs deploy/cluster-autoscaler | tail -1 # node group prod-vks-md-0 has no min/max annotation, skipping <- annotation was on the wrong object # fix, annotate the MachineDeployment as above, not the Cluster or the MachinePool
Gotcha: Read the autoscaler credentials and any registry token from an environment variable, never a hardcoded string in a manifest you commit. A token pasted into a values file is the leak you find in a git history six months later, long after the person who pasted it has moved teams.

Rollback and failure remediation

Rollback on VKS is not a version downgrade, and pretending otherwise is how you lose data. A Kubernetes minor upgrade cannot be reversed by editing the VKr back, the controller refuses it. So rollback has two real shapes. For a rollout still in progress, pause it and clear the blocker, a strict PodDisruptionBudget or a stuck node, so it completes forward. For a cluster that upgraded but then misbehaves, restore the workloads from the Velero backup you took in preflight onto a cluster running the known-good version. That is the whole reason the backup is mandatory and not a formality.

Scaling, by contrast, is fully reversible, so treat it differently. If a scale up causes contention, scale the MachineDeployment back down and the extra machines are drained and removed. The asymmetry is worth holding in your head: version changes are one way and need a backup, capacity changes are two way and do not.

Below is the failure lookup I keep open during Day-2 work. Each row is a symptom I have actually hit, its cause, and the fix, so a stall becomes a page turn instead of a debugging session.

SymptomCauseFix
Upgrade rejected by a webhookSkip-level minor version jumpUpgrade one minor version at a time
Machine stuck Deleting for many minutesDrain blocked by a strict PodDisruptionBudgetRelax the PDB to allow one eviction
Autoscaler never adds nodesMin and max annotation on the wrong objectAnnotate the MachineDeployment
kubectl scale changed nothingScaled a workload, not the node poolScale the MachineDeployment instead
Upgrade blocked by a precheck conditionSoftware misconfiguration detected on the clusterFix the issue, or override the check knowingly
Clusters still on the old version after a Supervisor upgradeSupervisor upgrade does not cascade to clustersPatch the VKr version on each cluster

Day-2 operating verdict and next move

My worst Day-2 lesson on VKS was not the upgrade stall, it was assuming an upgrade would cascade. I upgraded the Supervisor to a new build expecting the three guest clusters to follow, the way an Ops Manager tile upgrade pulls the whole foundation along. Nothing happened to the clusters. They sat on the old Kubernetes version for two more weeks until I learned that Supervisor and VKS clusters upgrade independently, and that you opt each cluster in by patching its VKr version. On TKGI the platform upgrade and the cluster upgrade were coupled through Ops Manager. On VKS they are deliberately decoupled, so you can patch the Supervisor for a security fix without forcing a Kubernetes version bump on every workload the same night. Once I read that as a feature and not a bug, our upgrade cadence got calmer.

Field note: The single most useful habit after cutover was keeping every cluster spec in version control and applying changes from there. When an upgrade or a scale is a reviewed commit, the reconcile model gives you an audit trail BOSH never did, and a bad edit is a revert away rather than a memory of what the plan used to say.

For the lifecycle model, my pick is to lean fully into declarative operations, manage clusters as Kubernetes objects, keep them in version control, and let the controller reconcile. Avoid the instinct to script imperative upgrade runbooks the way you did around tkgi upgrade-cluster, because a controller that already reconciles does not need a babysitter, it needs correct desired state. For rollback, treat every minor version upgrade as one directional and take a Velero backup first, no exceptions, because the downgrade escape hatch BOSH gave you is gone.

Clean result checklist: Target VKr present and marked compatible before any patch.
Upgrade moves one minor version, never a skip-level jump.
Fresh Velero backup taken because the version change cannot be undone.
Node scaling done on the MachineDeployment, autoscaler annotations on the right object.
Supervisor and cluster upgrades tracked separately, each cluster opted in on purpose.

Two questions land on every Day-2 handover. Do we still need to schedule upgrade windows now that it is a rolling replace? Yes, a rolling upgrade is graceful but it still cycles every node and evicts every pod, so run it in a change window and check your PodDisruptionBudgets first. Can we let the autoscaler manage production without supervision? Set a sane max so a runaway workload cannot provision the datastore into the ground, and alert on the cluster hitting that max, because a group pinned at its ceiling is capacity you planned to have and do not.

For the backup mechanics this rollback depends on, the Velero toolchain part set up the source and target backups, and the production cutover part is the move that got you here. The TKGI to VKS guide holds the full map, and the series hub links the related VKS and VCF 9 series.

On Monday, pick one non-production VKS cluster and run kubectl get vkr against it. If a newer compatible release is listed, take a Velero backup and patch the topology version by one minor step, then watch the machines roll. Doing it once on a cluster nobody depends on turns the production upgrade from a leap into a repeat. Next part covers observability, backup and disaster recovery on VKS, the safety net under everything you just learned to change.

TKGI to VKS Series · Part 24 of 26
« Previous: Part 23  |  Guide  |  Next: Part 25 »

References

Broadcom TechDocs, Understanding the Rolling Update Model for VKS Clusters
Broadcom TechDocs, Updating VKS Service Clusters
Broadcom TechDocs, Autoscaling VKS Clusters

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