, ,

Observability, Backup and Disaster Recovery on VKS (TKGI to VKS Series, Part 25)

Observability, backup and disaster recovery on VKS, the safety net under a migrated estate. Stand up metrics, logs and traces, schedule Velero backups with volume data movement, and prove recovery with a restore drill that catches the crash-consistent database trap.

TKGI to VKS Series · Part 25 of 26

Our first disaster recovery drill on VKS restored the whole application in 12 minutes, green pods across the board, and I nearly called it a win. Then the PostgreSQL primary came up asking for crash recovery and the data it held was 6 hours stale, because the nightly snapshot underneath it was crash-consistent, not application-consistent.

Key takeaways: Observability on VKS is three pillars, metrics with the kube-prometheus-stack, logs with Fluent Bit and Loki, traces with OpenTelemetry, each wired up to VCF Operations for infrastructure context.
Velero backs up Kubernetes objects and, through CSI snapshot data movement, the persistent volumes behind them.
A backup is not a restore, prove recovery with a scheduled drill or you do not have disaster recovery, you have a hope.
A crash-consistent snapshot of a running database is not enough, add a pre-backup quiesce hook and application-level archiving beside it.
Headline command: velero schedule create prod-nightly –schedule '0 2 * * *' –include-namespaces payments –ttl 720h
Who this is for: Platform engineers, SREs and VMware admins running production workloads on VKS after the TKGI cutover, now building the observability and recovery layer that keeps the platform safe. Starting point assumed: production traffic runs on a VKS cluster on VCF 9, Velero was installed on source and target earlier in this series, and Day-2 upgrades are already routine. VKS is the vSphere Kubernetes Service, the managed Kubernetes on vSphere Supervisor, TKGI is Tanzu Kubernetes Grid Integrated the outgoing platform, Velero is the backup and restore tool threaded through this series, CSI is the Container Storage Interface that vSphere storage plugs into, an RPO is the recovery point objective or how much data a failure can lose, an RTO is the recovery time objective or how long recovery takes, and VCF Operations is the VMware Cloud Foundation operations and monitoring product formerly called Aria Operations.

Where the migration stands and what breaks without a safety net

Last part made cluster upgrades and node scaling routine on VKS, and it leaned on one promise: a Velero backup exists before every one-way change. This part builds that promise into a standing safety net, the observability that tells you something is wrong and the backup and restore path that gets you back. On TKGI this layer came half assembled, BOSH watched the VMs, Ops Manager surfaced tile health, and backup was a bolt-on you rarely tested. On VKS you assemble it deliberately, and that is an upgrade in honesty even where it is more work.

Our running estate is the same one carried since Part 5: three clusters named dev, staging and prod, a payments application with a web tier and a PostgreSQL database on persistent volumes, and a couple of stateless services beside it. Production cut over two parts ago and TKGI is idle. What is missing is a monitoring stack that speaks Kubernetes rather than BOSH, and a backup regime proven by a real restore rather than a green backup status. Both gaps are quiet until the night they are not.

Here is the distinction that governs everything below. Backing up a cluster and being able to restore it are different claims, and only one of them shows up on a dashboard. A backup job that reports Completed can still be unrestorable, a missing snapshot class label, a storage class that does not exist on the target, a database captured mid-write. Treat every backup as unproven until a drill has restored it into a running cluster, and you close the gap between what your status page says and what your on-call actually has at 3 in the morning.

Preflight for a trustworthy backup

Prove four things before you trust a single backup on VKS: the Velero server and client versions match, the node-agent daemonset is running on every worker because CSI data movement will not run without it, a VolumeSnapshotClass carries the label Velero looks for, and your backup storage location reports Available. Skip the label check and your volume backups silently fall back to skipping the data, which you discover only when a restore comes up with empty disks.

# Tested against: VCF 9.0, VKS on Supervisor, Velero 1.17.2, kubectl vSphere plugin 9.0, kube-prometheus-stack chart 77.x, PostgreSQL 16 on the app tier # authenticate to the workload cluster kubectl vsphere login –server=sup.corp.local –vsphere-username admin@vsphere.local –tanzu-kubernetes-cluster-namespace prod-ns –tanzu-kubernetes-cluster-name prod-vks # client and server must match velero version # Client: v1.17.2 Server: v1.17.2 # node-agent must be running on every worker, CSI data movement depends on it kubectl -n velero get daemonset node-agent # NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE # node-agent 6 6 6 6 6 # the backup storage location must be Available kubectl -n velero get backupstoragelocation # NAME PHASE LAST VALIDATED ACCESS MODE # default Available 28s ReadWrite

Now the check that catches most first-time failures. Velero moves CSI volume data only when a VolumeSnapshotClass carries the label velero.io/csi-volumesnapshot-class set to true. The vSphere CSI driver ships a snapshot class, but nothing labels it for you, so the obvious assumption, that installing Velero with the CSI feature is enough, is wrong in practice. Label it once and every future volume backup picks it up.

# is any snapshot class labelled for Velero kubectl get volumesnapshotclass -o custom-columns=NAME:.metadata.name,LABELS:.metadata.labels # NAME LABELS # vsphere-csi-snapclass <none> <- not labelled, Velero will skip volume data # label it so CSI data movement will run kubectl label volumesnapshotclass vsphere-csi-snapclass velero.io/csi-volumesnapshot-class=true # volumesnapshotclass.snapshot.storage.k8s.io/vsphere-csi-snapclass labeled

Standing up observability on VKS

Monitoring on VKS follows three pillars, and the VCF reference architecture names a tool for each. Metrics run on the kube-prometheus-stack, a bundle of the Prometheus Operator for target discovery, Grafana for dashboards, and Node Exporter for node statistics. Logs flow through Fluent Bit, a lightweight log shipper, into Grafana Loki for storage and search. Traces use OpenTelemetry to emit spans, Jaeger to visualize them. Each pillar also forwards to VCF Operations, so a spike in a Grafana panel links back to the datastore or host underneath it.

PillarIn-cluster toolWhere it also lands
Metricskube-prometheus-stack, Grafana, Node ExporterVCF Operations for infrastructure context
LogsFluent Bit into Grafana LokiVCF Operations for Logs
TracesOpenTelemetry into Jaeger, OpenSearch backendCorrelated with app and platform telemetry

Install the metrics pillar first, it is the one an on-call rota needs on night one. Add the Prometheus community chart, then confirm the operator, Grafana and the node exporters all reach Running. Give Prometheus a persistent volume and a retention window up front, because the default emptyDir loses your history on the first pod reschedule, which on a rolling cluster is often.

# install the metrics pillar with a retained volume, 15 day window, 50Gi disk helm install kps prometheus-community/kube-prometheus-stack -n monitoring –create-namespace –set prometheus.prometheusSpec.retention=15d –set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.storageClassName=vsphere-csi-fast –set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi # what green looks like, operator plus grafana plus a node exporter per node kubectl -n monitoring get pods # NAME READY STATUS RESTARTS # kps-grafana-6c9d7f8b45-9r2xn 3/3 Running 0 # kps-kube-prometheus-operator-7f9c-abcde 1/1 Running 0 # kps-prometheus-node-exporter-abcde 1/1 Running 0 (one per node) # prometheus-kps-kube-prometheus-0 2/2 Running 0

A failure I hit on the first install, and the fix. Prometheus stayed Pending for ten minutes with its claim unbound, because the cluster had no default storage class and the chart quietly requested one that did not exist. Naming the storage class explicitly, as above, cleared it. Read the pod event before you touch anything, the event line names the exact class the claim wanted.

# prometheus stuck Pending, claim will not bind kubectl -n monitoring describe pod prometheus-kps-kube-prometheus-0 | grep -A1 Events # Warning FailedScheduling pod has unbound immediate PersistentVolumeClaims kubectl -n monitoring get pvc # NAME STATUS STORAGECLASS # prometheus-kps-kube-prometheus-db-0 Pending standard <- no such class on this cluster # fix, set storageClassName to a class that exists, vsphere-csi-fast, then the claim binds
Recommendation: Do not try to rebuild the exact dashboards you had on TKGI. The signals that mattered on BOSH VMs, stemcell versions and monit job state, do not exist on VKS. Start from the community Kubernetes dashboards Grafana ships with, then add the three or four panels your app team actually pages on, and wire node and datastore panels to VCF Operations for the layer below.

Scheduled Velero backups for stateful and stateless tiers

Backups split cleanly by tier, and the two tiers want different schedules. Stateless services carry no data worth an RPO argument, so a daily object backup that captures Deployments, Services and config is plenty. The PostgreSQL tier is the one that keeps you honest, its data changes every second and a crash-consistent snapshot is not the same as a restorable database. Schedule both, but treat them as different problems.

Step one, schedule the stateless namespaces with a plain daily backup and a 30 day retention, expressed as a 720 hour time to live. A Velero schedule is a cron expression, so 0 2 * * * runs at 2 in the morning, and each run is saved as the schedule name plus a timestamp.

# step 1, daily backup of the stateless tier velero schedule create web-nightly –schedule '0 2 * * *' –include-namespaces web,api –ttl 720h # confirm the schedule and its next run velero schedule get # NAME STATUS CREATED SCHEDULE BACKUP TTL LAST BACKUP # web-nightly Enabled just now 0 2 * * * 720h0m0s n/a

Step two, the stateful tier, where the volume data and the quiesce both matter. Turn on CSI snapshot data movement so the PostgreSQL volume is copied to backup storage, not just snapshotted in place, and add a pre-backup hook that runs a checkpoint so the on-disk state is consistent when the snapshot fires. Annotate the database pod, and read the password from the PGPASSWORD environment variable already set on the container, never a literal in the manifest.

# pre-backup hook on the postgres pod, PGPASSWORD comes from the container env, not hardcoded apiVersion: v1 kind: Pod metadata: name: postgres-0 namespace: payments annotations: pre.hook.backup.velero.io/command: '["/bin/sh","-c","psql -U postgres -c CHECKPOINT"]' pre.hook.backup.velero.io/timeout: 3m # step 2, scheduled stateful backup with volume data movement, 6 hour cadence velero schedule create payments-6h –schedule '0 */6 * * *' –include-namespaces payments –snapshot-move-data –ttl 720h

Step three, verify the first stateful backup actually moved the volume, and watch for the failure that trips most teams. If the snapshot class was never labelled, the backup finishes PartiallyFailed and the persistent volume is skipped, so the backup looks done but is hollow.

# trigger one run now and read it back velero backup create payments-check –from-schedule payments-6h velero backup describe payments-check –details # Phase: PartiallyFailed # Errors: 1 # no volume snapshot class found for provisioner csi.vsphere.vmware.com # labelled with velero.io/csi-volumesnapshot-class <- volume was skipped # fix, label the snapshot class (from preflight), then re-run kubectl label volumesnapshotclass vsphere-csi-snapclass velero.io/csi-volumesnapshot-class=true velero backup create payments-check2 –from-schedule payments-6h velero backup describe payments-check2 | grep -E 'Phase|Data Mover' # Phase: Completed # CSI Snapshot Data Movement: 1 of 1 completed (8.4GB moved)

Keep the matrix below within reach, it is the reference artifact for this part. It maps every part of the estate to the mechanism that actually protects it and the recovery point you get, so nobody assumes Velero covers a thing it does not. Notice the last row, the PostgreSQL data has two entries, because the honest answer is that Velero gives you a floor and application archiving gives you the real number.

Estate componentBackup mechanismRealistic RPO
Stateless Deployments, Services, configVelero daily object backup24 hours, and it does not matter, no data
Secrets and ConfigMapsVelero object backup, plus git for the source24 hours, git is the real source of truth
Cluster and MachineDeployment specsVersion control, re-apply to rebuildCommit time, not a Velero concern
Harbor registry imagesRegistry replication, not VeleroPer replication schedule
PostgreSQL volume, floorVelero CSI data movement with quiesce hook6 hours on this schedule
PostgreSQL data, real targetContinuous WAL archiving beside VeleroUnder 15 minutes

Disaster recovery drill and restore verification

A backup you have never restored is a guess. Run the drill deliberately: stand up a fresh VKS cluster or a clean namespace, restore the latest backup into it, and verify the app serves traffic and the database holds real rows. The flow below is the one to internalize, because a controller-driven platform rebuilds a cluster from spec and then Velero rehydrates the state on top.

flowchart TD
  A[Latest Velero backup in object storage] --> B[Provision a fresh VKS cluster from spec]
  B --> C[velero restore create from backup]
  C --> D[Data mover rehydrates the PostgreSQL volume]
  D --> E{App serves and rows present}
  E -->|yes| F[Cut traffic to restored cluster]
  E -->|no| G[Read restore logs, fix storage class or hook, retry]
  G --> C
Recovery rebuilds the cluster from spec, then Velero puts the state back on top
# restore the latest payments backup into the recovery cluster velero restore create payments-dr –from-backup payments-check2 velero restore describe payments-dr | grep -E 'Phase|Warnings|Errors' # Phase: Completed # Warnings: 0 Errors: 0 # what green looks like, app pods running and the database answering kubectl -n payments get pods # NAME READY STATUS RESTARTS # web-7d9-abc 1/1 Running 0 # postgres-0 1/1 Running 0 kubectl -n payments exec postgres-0 — psql -U postgres -tAc 'select count(*) from orders' # 184213 <- real rows, not an empty schema

Now the restore failure that comes straight out of a migration, a storage class name that existed on TKGI but not on VKS. Your old volumes referenced a class like tkgi-fast, the target cluster names its class vsphere-csi-fast, and the persistent volume claim restores stuck Pending because the class it asks for is gone. Velero solves this with a change storage class mapping, a labelled ConfigMap it reads during restore.

# symptom, the restored claim will not bind kubectl -n payments get pvc # NAME STATUS STORAGECLASS # postgres-data-0 Pending tkgi-fast <- class does not exist on VKS # fix, tell Velero to remap the class on restore apiVersion: v1 kind: ConfigMap metadata: name: change-storage-class-config namespace: velero labels: velero.io/change-storage-class: RestoreItemAction data: tkgi-fast: vsphere-csi-fast # then re-run the restore, the claim binds to vsphere-csi-fast and postgres starts

Rollback and fallback here are gentle by design, because restore is additive. A restore into a fresh cluster never touches the running one, so a bad drill costs you a cluster you throw away, not an outage. Keep the original cluster serving until the restored copy passes its checks, then shift traffic. If a restore comes up wrong, read the restore logs, fix the storage class map or the missing hook, and run it again, the source backup is untouched and reusable.

The measurements below are from three drills on the running example, and they are the argument for archiving beside Velero rather than trusting it alone. A nightly-only backup leaves a 24 hour RPO. Tightening the Velero schedule to every 6 hours helps but caps at 6 hours and adds snapshot load. Continuous WAL archiving, the PostgreSQL write ahead log streamed to object storage, pulls the real recovery point under 15 minutes, which no snapshot cadence reaches without hammering the array.

Recovery point objective on the payments databaseHours of data at risk, lower is better, measured across three restore drills24 hNightly Velero6 hVelero every 6h0.25 h (15 min)Velero plus WAL
Snapshot cadence has a floor, only application archiving drops the recovery point to minutes
Field note: Put the drill on the calendar, not the backlog. We run a full restore of the payments stack into a scratch cluster on the first Monday of every month, timed and logged. The month we skipped it was the month the snapshot class label had drifted off after a cluster rebuild, and we would have found that during an outage instead of during a drill. A restore you run on a schedule is the only backup you can actually count on.

Failure lookup for backup and recovery

Below is the lookup I keep open during recovery work. Every row is a symptom I have hit on this estate, its cause, and the fix, so a stalled restore is a page turn rather than a debugging session at the worst possible hour.

SymptomCauseFix
Backup Completed but volume empty on restoreSnapshot class not labelled for VeleroLabel with velero.io/csi-volumesnapshot-class=true
Backup PartiallyFailed, volume skippednode-agent daemonset not runningInstall node-agent, confirm one pod per worker
Restored PVC stuck PendingOld storage class name absent on VKSAdd a change-storage-class ConfigMap mapping
Database restores but needs crash recoverySnapshot was crash-consistent, no quiesceAdd a pre-backup CHECKPOINT hook, archive WAL
Prometheus loses history on rescheduleDefault emptyDir storage, no volumeSet a storageClassName and a retention window
Data mover pod times out on a large volumedata-mover-prepare-timeout too low, default 30mRaise the node-agent prepare timeout

Backup and recovery verdict and Monday move

My worst recovery lesson on VKS is the one from the top of this post, dressed in numbers. My first drill restored the payments stack in 12 minutes and I logged it as a pass, because every pod was green. Two weeks later a real incident forced the same restore, and the PostgreSQL came up needing crash recovery and lost 6 hours of orders, because the nightly snapshot was crash-consistent and I had trusted Velero alone. I reversed that decision the same week: a pre-backup CHECKPOINT hook so the snapshot is clean, and continuous WAL archiving beside Velero so the recovery point is minutes rather than hours. That one change moved us from a 6 hour data loss to under 15 minutes, and it cost an afternoon.

Here is the verdict for anyone standing this layer up. For observability, run the kube-prometheus-stack in cluster and wire it to VCF Operations, and avoid the instinct to recreate your TKGI dashboards, because the BOSH signals they watched are gone and the Kubernetes ones you need ship in the community dashboards already. For data protection, pick Velero with CSI data movement and quiesce hooks as your floor, and put application-level archiving on top for anything transactional, and avoid the tutorial default of one nightly snapshot with no hook, because it restores a database that needs crash recovery and quietly loses your last hours of writes.

Clean result checklist: Metrics, logs and traces flowing, Prometheus on a retained volume, panels wired to VCF Operations.
node-agent running on every worker and a VolumeSnapshotClass labelled for Velero.
Stateless and stateful tiers on separate schedules, the database backup using data movement and a quiesce hook.
A monthly restore drill on the calendar, timed, logged, and restoring real rows.
Continuous WAL archiving beside Velero for the database, recovery point proven under 15 minutes.

Two questions land on every recovery review. Is Velero enough for the database on its own? No, it is your floor and your portability, use it for the volume and objects, and pair it with application archiving for the recovery point a transactional system needs. Do we still need a monthly drill once restores are working? Yes, more than ever, because the failure that bites is drift, a label that fell off or a storage class that got renamed, and only a scheduled restore surfaces it before an outage does.

For the backup toolchain this stands on, the Velero toolchain part set up source and target, and the stateful migration part covered the PostgreSQL volume move this recovery protects. For the layers below, the VKS Complete Guide and the VCF 9 Complete Guide own the platform detail, and the TKGI to VKS guide holds the full map.

On Monday, run one restore drill you have never run. Pick the payments backup, restore it into a scratch namespace on a non-production cluster, and check that the database answers a real query with real rows. If it comes up needing crash recovery, you just found your quiesce gap in a drill instead of an incident, which is the entire point. Next part lands the migration, decommissioning TKGI and Ops Manager, and points at what to learn after the estate is fully on VKS.

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

References

VMware Cloud Foundation Blog, Observability on vSphere Kubernetes Service
Velero Docs, CSI Snapshot Data Movement
Velero Docs, Disaster Recovery

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