Eighteen applications, three clusters, one that actually matters. That was the shape of the reference estate after the Part 5 inventory, and it matches most TKGI estates I have assessed: a long tail of services that move with almost no drama, and a short list that decides the entire timeline. Tanzu Kubernetes Grid Integrated (TKGI) is the platform you are leaving; the vSphere Kubernetes Service (VKS) on VMware Cloud Foundation 9 is where these apps land. Assessment is how you separate the easy tail from the timeline-setting few before you touch a single workload.
Two migration patterns, and when each fits
Almost every Kubernetes to Kubernetes move reduces to one of two patterns, and the whole assessment turns on which one a given app deserves. Pattern one is lift-and-shift: Velero reads the source Kubernetes API, bundles the objects into an S3-compatible bucket, and replays them into VKS. Because VKS is CNCF-conformant, the API details land cleanly on the far side. Pattern two is pipeline retarget: if the app already lives in Git and ships through Jenkins, Flux, Argo CD or Harness, you point that pipeline at the new VKS API endpoint and let it deploy a fresh instance. Same binary, new cluster, no backup tarball involved.
Choosing between them is not a taste question, it is a maturity question. Hand-deployed apps with no reliable Git source of truth are honest candidates for Velero, because the running cluster is the only place their full state exists. Anything under GitOps is the opposite case. VMware Cloud Foundation guidance is blunt about it: if you are already storing configuration in Git and deploying through a pipeline, do not use Velero for the application migration, because a restore breaks the link between your repository and the running cluster and immediately drifts from source. Retarget instead.
A migration is also the cheapest moment you will ever get to pay down platform debt. Retargeting a pipeline lets you adopt VCF-native habits on the way in, using VCF Automation to create Projects and self-service VKS clusters, and moving cluster add-ons like ingress and cert-manager under Argo CD or Flux. If that self-service angle is where you want to end up, the VCF Automation Series covers the tenancy plumbing in depth so this series does not have to repeat it.
| Factor | Lift-and-shift (Velero restore) | Pipeline retarget (redeploy) |
|---|---|---|
| Best when | hand-deployed, no reliable Git source | already GitOps or CI/CD driven |
| Source of truth after | the running cluster, debt carried over | Git, clean and reproducible |
| Effort per app | low upfront, manual cleanup after | low if pipeline already exists |
| Drift risk | high, restore fights any controller | low, controller owns state |
| Stateful data | handled via CSI snapshot backup | needs a separate data copy step |
| Verdict | default for legacy, hand-built apps | default for anything Git-managed |
Neither pattern solves stateful data on its own, and pretending otherwise is how migrations slip. You cannot simply move a disk object between clusters, because a PVC is bound to a specific volume id and CSI driver in the source infrastructure. Data that already lives on external NFS or object storage is easy, you point the new instance at the same share. Block storage is the hard case, where you need a backup tool that supports CSI snapshotting so it copies the data itself, not just the manifest, and rehydrates a fresh volume on VKS during restore. That distinction, config versus data, is the reason wave 3 exists as its own category rather than folding into wave 2.
Disposition choices for a Kubernetes to Kubernetes move
Two patterns are the spine, but real estates need a slightly wider set of dispositions, because not every workload should move at all. Five choices cover the reference estate cleanly. Redeploy retargets a pipeline for Git-managed apps. Restore uses Velero for hand-deployed apps and for stateful data that must come across intact. Refactor is a redeploy that first fixes something blocking, most often a deprecated API version or an ingress controller that does not exist on the target. Retire deletes a workload the inventory turned up that no team will claim. Replace swaps a self-run component for a platform service, for example moving off a self-managed registry onto the VKS-integrated Harbor, or off self-run PostgreSQL onto Data Services Manager.
Retire earns special attention because it is where migrations get faster instead of slower. In the reference estate the Part 5 inventory surfaced a reporting service that nobody owned and no dashboard referenced. Cutting it removed an entire assessment thread. Every workload you retire is one you never have to test, cut over or roll back, so run the retire question first, before any disposition, on every namespace.
Scoring an application for migration readiness
A disposition tells you how an app moves. A readiness score tells you how safely, and it is what orders the waves. I score six dimensions from 1 to 5, where 1 is trivial and 5 is dangerous: statefulness, automation maturity, dependency depth, blast radius if it breaks, API and CRD compatibility with the target Kubernetes version, and downtime tolerance during cutover. Sum them and the estate self-sorts. A stateless Git-managed service with no downstream consumers might score 8; the production database that everything depends on will score in the low twenties.
Three of those dimensions you can measure with quick discovery commands rather than guessing. Statefulness comes from listing PVCs, GitOps ownership comes from checking for controller annotations or Application objects, and API compatibility comes from scanning for deprecated versions against the target. Run these against each source cluster while you still have TKGI in front of you.
Compatibility is the dimension that ambushes people, so screen for it now rather than at restore time. A quick pass with pluto against the target version surfaces objects that will not survive the jump from Kubernetes 1.27 to 1.31.
That single check moved two apps in the reference estate from a planned Velero restore to a refactor-then-redeploy, and it caught them on a whiteboard instead of at 2 a.m. during a cutover. Velero will not restore into a cluster running a lower Kubernetes version than the source, and it cannot conjure an API that the target removed, so a compatibility scan is not optional homework, it is the difference between a clean wave and a half-restored namespace.
Grouping workloads into migration waves
With every app scored, waves fall out of the numbers. I use four. Wave 0 is a single throwaway pilot in non-prod, chosen precisely because losing it costs nothing, and its only job is to prove the toolchain and the target platform end to end. Wave 1 is the stateless production tier, the long tail that redeploys or restores with low blast radius. Wave 2 is stateful but recreatable, caches and queues that hold data you can rebuild or drain rather than preserve byte for byte. Wave 3 is the critical stateful core, the database and the app that cannot run without it, moved last and moved carefully.
Here is where the obvious approach is wrong. Plenty of teams sort waves by business criticality and, wanting an early win in front of leadership, put the flagship revenue app in wave 1 to prove the platform on something that matters. That inverts risk. Criticality should push a workload later, not earlier, because the whole point of wave 0 and wave 1 is to fail cheaply and learn before the expensive workload moves. Sort by a blend of statefulness and blast radius, and let importance decide lateness, not priority.
Downtime tolerance deserves its own line in the plan, because it quietly decides the technique inside each wave. A stateless app in wave 1 can usually run in both clusters at once during cutover, so its tolerance is generous and its rollback is a DNS change. A database in wave 3 often needs a quiet window to quiesce and snapshot cleanly, so its tolerance is measured in minutes and its rollback means pointing back at source data you deliberately kept read only. Write the acceptable window next to each app now, and the later cutover parts stop being guesswork about who can afford how much disruption.
Isolation is the other rule worth stating plainly. Running stateful workloads on the same cluster as stateless ones makes lifecycle management harder for the entire cluster, so where the target design allows it, land databases on dedicated VKS clusters. That choice shapes wave 3 more than any command does, and it is one reason the VKS Series is worth reading in parallel for the cluster-class and node-pool mechanics this series leans on rather than reteaching.
Sequencing the reference migration
Applied to the reference estate, the four waves carry very uneven load, and seeing the shape changes how you staff the effort. One pilot app in wave 0, eleven stateless services in wave 1, five recreatable stateful workloads in wave 2, and a single critical database app in wave 3. Wave 1 looks heavy but is mostly repetition of a known-safe procedure. Wave 3 is one app that will consume more planning than the other seventeen combined.
Verdict on sequencing: run wave 0 as a genuine dress rehearsal, not a demo. Pick something you would be willing to delete, and use it to time the Velero backup, measure the object-storage sync interval of one minute, and confirm the target storage class binds. Everything you learn there is free. Everything you skip there, you pay for in wave 3 with real data on the line.
Application disposition matrix
This is the artifact to keep and return to. One row per application, disposition and wave decided, key risk named. Build your own version of this table for your estate and it becomes the plan of record for every part that follows, from stateless cutover through the stateful database move. A representative slice of the reference estate looks like this.
| Application | State | Managed by | Disposition | Wave | Key risk |
|---|---|---|---|---|---|
| batch-worker (dev) | stateless | hand-deployed | Restore | 0 | toolchain unproven |
| web-frontend | stateless | Argo CD | Redeploy | 1 | ingress class swap |
| api-gateway | stateless | Argo CD | Refactor | 1 | removed API version |
| notifications | stateless | Helm and CI | Redeploy | 1 | secret re-creation |
| redis-cache | stateful, recreatable | Helm | Redeploy fresh | 2 | cold-cache latency |
| rabbitmq | stateful, recreatable | hand-deployed | Restore, drain first | 2 | in-flight messages |
| harbor registry | stateful | platform team | Replace with VKS Harbor | 2 | image re-push |
| legacy-reporting | stateless | no owner | Retire | n/a | nobody claims it |
| shop-web | stateless | Argo CD | Redeploy | 3 | cutover coordination |
| shop-postgres | stateful, 220Gi | hand-deployed | Restore, CSI snapshot | 3 | data integrity, downtime |
Notice that shop-web and shop-postgres share wave 3 even though the web tier is stateless and would score low on its own. An app that cannot function without its database inherits the database wave, because splitting them across waves means one of them runs blind to the other during the gap. Coupling like that is exactly what an assessment exists to surface before it becomes a cutover surprise.
Start with a throwaway, not your flagship
If you take one habit from this part, make it this: the first workload you move should be one you would happily delete, and the last should be the one your business cannot lose. Assessment is what lets you say which is which with a straight face, backed by a score and a disposition rather than a hunch. Do the boring work of scoring the estate now, while TKGI is still running and you can query it freely, and the hands-on parts ahead become procedure instead of improvisation.
References
- VMware Cloud Foundation Blog, Mastering Application Migration to VKS, Patterns and Best Practices
- Velero Documentation v1.18, Cluster migration
- Velero Documentation v1.18, Resource filtering with labels and selectors


DrJha