title: "Kubernetes Disaster Recovery at Scale: Velero, etcd Backups & Multi-Region Failover Architecture" date: 2026-09-18 tags: [Kubernetes, Disaster Recovery, Velero, etcd, Multi-Region, Platform Engineering, GitOps, AWS, SRE, FinOps] cover: ../images/kubernetes-dr-velero-multiregion-cover.png

Cover

Kubernetes Disaster Recovery at Scale: Velero, etcd Backups & Multi-Region Failover Architecture

1. Topic of the Day

Most platform teams treat Kubernetes disaster recovery as "we have Velero backups running on a CronJob, we're covered." That's a false sense of security, and it's the single most common gap CNCF's own working groups keep flagging: a CNCF blog published this month (September 2026), "Kubernetes disaster recovery: guidance from three reproducible failure scenarios," makes the point bluntly — having backups and being able to recover are different capabilities, and most organizations only validate the first one. A velero backup create that succeeds every night for two years tells you nothing about whether velero restore create will actually rebuild a working cluster under time pressure, with the right restore order, without CRD version drift, and within an RTO your business actually agreed to.

Kubernetes DR exists as a discipline because the cluster has two categories of state that fail independently and require entirely different recovery mechanics. First, etcd — the single source of truth for every object in the API server, the Raft-replicated key-value store that, if lost with no usable quorum, means the cluster's control plane cannot be reconstructed from anything except a snapshot. Second, application and persistent state — the actual Deployments, ConfigMaps, Secrets, CRDs, and (critically) the PersistentVolume data backing StatefulSets, databases, and message queues running on top of the cluster. Losing either one alone is recoverable if you planned for it; losing both simultaneously, in a full region outage, is the scenario most DR architectures on paper have never actually been tested against.

This has become more urgent, not less, through 2026. Kubernetes 1.34 shipped etcd v3.6.5 as the default via kubeadm (a jump from the 3.5.x line pinned since 1.31), and the etcd and Kubernetes projects jointly published guidance in December 2025 on avoiding "zombie" cluster members during that upgrade — a reminder that even the backup target itself is not a static, solved problem. AWS Backup added native, policy-driven EKS support with cross-region copy and logically air-gapped (immutable) vaults in the same window. And CNCF accepted K8gb (Kubernetes Global Balancer) as an incubating project in August 2026, specifically to standardize DNS-based multi-cluster failover using CoreDNS and ExternalDNS instead of every org hand-rolling GSLB scripts. The tooling ecosystem is maturing quickly because the failure mode it protects against — a full AZ or region outage, a corrupted etcd write, a ransomware event that touches the backup target itself — is no longer hypothetical for teams running Kubernetes as tier-0 infrastructure.

At the level this post is written for, DR is not "click restore and hope." It's an architecture with defined RPO/RTO SLOs, automated and continuously rehearsed restore paths, immutable backup storage that survives a compromised cluster identity, and a failover mechanism that doesn't depend on the very control plane that just went down.

2. Real Business Problem

Scenario: A fintech platform runs its core ledger and payments-adjacent services on a single-region EKS cluster (us-east-1) serving roughly 2,200 pods across 60 namespaces, with 40 StatefulSets backing Postgres read replicas, Kafka, and Redis clusters via EBS-backed CSI volumes. The company processes ~$40M/day in transaction volume. Compliance (SOC 2 Type II renewal, plus a new PCI-DSS 4.0 requirement around "tested recovery procedures") now requires a documented, demonstrated RTO of 30 minutes and RPO of 5 minutes for the payments path — and an annual auditor-witnessed DR drill.

The investigation that follows the compliance mandate surfaces just how much theater was in the existing "DR strategy":

  • Velero has been running nightly full backups for 18 months. Nobody has run a full restore in production-equivalent conditions since it was installed. The one restore attempt, done ad hoc six months ago to recover a single accidentally-deleted namespace, took four hours — not because Velero was slow, but because nobody remembered the correct order to restore CRDs before the custom resources that depend on them, and two operators were restoring conflicting namespaces simultaneously.
  • etcd snapshots exist, but only on the control-plane nodes themselves, via a cron running etcdctl snapshot save to local disk. Because this is EKS, there's no direct etcd access at all — the managed control plane means the team never designed an alternative and has been assuming "AWS handles that," without verifying what AWS's control-plane backup SLA actually guarantees (multi-AZ replication within the region, not cross-region, and not a substitute for etcd's restore semantics on unmanaged clusters).
  • PersistentVolume snapshots are inconsistent. CSI VolumeSnapshots exist for some StatefulSets but were configured per-team, ad hoc, with no cluster-wide policy — three of the 40 StatefulSets, including one of the Kafka clusters carrying transaction event logs, have never had a successful snapshot taken because the VolumeSnapshotClass was misconfigured after a CSI driver upgrade eight months ago, and nothing alerted on it.
  • No second region exists at all. If us-east-1 has a control-plane-impacting event (AWS has had multiple regional EC2/EKS control-plane degradations in recent years), there is no standby cluster, no pre-staged infrastructure, and no tested runbook — recovery would mean provisioning a new EKS cluster from Terraform/eksctl, restoring from Velero cold, and hoping the object storage bucket (single-region S3, no cross-region replication) itself survived whatever caused the outage.
  • The backup bucket and the cluster share a trust boundary. The IAM role Velero uses has s3:DeleteObject on the backup bucket, and that role's credentials are reachable from within the cluster it's protecting — meaning a cluster-compromise scenario (the exact scenario ransomware and destructive-actor playbooks target first) can delete the backups meant to recover from it.

The mandate becomes a production architecture problem: design and prove a DR posture that survives (a) accidental deletion, (b) a corrupted or unrecoverable etcd, (c) a full regional outage including the backup storage's own region, and (d) an adversary with cluster-admin access trying to destroy recovery material — all while hitting a 30-minute RTO / 5-minute RPO on the payments path, and producing evidence an auditor will accept.

3. Production Architecture

Architecture image: blogs/architecture/kubernetes-dr-velero-multiregion-architecture.png

Global edge and failover control. Client traffic resolves through Route 53 latency-based routing with health checks against each region's Gateway. A dedicated health-check controller — conceptually aligned with K8gb, the CNCF-incubating Kubernetes Global Balancer that reached incubation in August 2026 — watches region-level liveness signals (API server reachability, a synthetic transaction probe through the payments path, not just a TCP health check) and flips DNS weighted routing when a region breaches its failure threshold for a sustained window, avoiding flapping on transient blips. This sits outside both regional control planes deliberately: a health-check mechanism that lives inside the cluster it's monitoring cannot be trusted to detect that cluster's own total failure.

Region A — active primary (us-east-1). A standard production EKS cluster serving live traffic. Three layers of backup run continuously and independently: an etcd snapshot CronJob (on self-managed control planes) or, on EKS, reliance on AWS Backup's native EKS integration which now captures Kubernetes object state and coordinates with EBS snapshots directly — chosen over a hand-rolled equivalent specifically because AWS Backup's EKS support (GA since late 2025, with logically air-gapped vault support added March 2026) handles the API-server-state capture without needing direct etcd access, which self-managed etcdctl snapshot save requires and EKS doesn't expose. Velero, layered on top rather than replacing this, handles the parts AWS Backup doesn't own as cleanly: namespace-scoped and selective restores, cross-provider portability (so a restore isn't locked to staying on AWS), and CSI VolumeSnapshot-driven backup of the 40 StatefulSets' PVs, using Velero 1.17+'s VolumeGroupSnapshot support so that a multi-volume application (Kafka's data and index volumes together, for instance) backs up as one crash-consistent group instead of N independently-timed snapshots that could land at different points in the write stream.

Backup storage — deliberately outside the blast radius. Backups land in a dedicated AWS account (not the workload account), in an S3 bucket with Object Lock in compliance mode (true WORM — not even the account root can delete within the retention window), cross-region replicated to the DR region, encrypted with a CMK the workload cluster's IAM roles cannot access for deletion — Velero's execution role gets s3:PutObject and s3:GetObject only, never s3:DeleteObject on this bucket, closing the exact "compromised cluster deletes its own backups" hole the investigation surfaced. Lifecycle deletion, when needed, runs from a separate, break-glass-only automation path.

Region B — warm standby (eu-west-1 or a second AWS region). Not a cold "we'll build it when we need it" plan, and deliberately not a fully active-active hot standby either — a warm standby: the EKS control plane and a minimal node pool exist continuously (via the same GitOps source as Region A), etcd/cluster-state and CRDs are kept current through a continuous restore-rehearsal process (not a break-glass one-shot script), and workloads sit scaled to zero or near-zero via Karpenter, ready to scale out the moment failover triggers. This trades a modest standing compute cost (control plane + minimal nodes + continuous restore validation compute) against the alternative — a cold-start restore into a from-scratch cluster — which was measured in the drill described in Section 7 at roughly 38 minutes end to end, blowing the 30-minute RTO before any application warm-up is even counted.

GitOps control plane, applied per-region. ArgoCD, running as a hub registering both region clusters, is the mechanism that keeps Region B's Kubernetes-native config (everything that isn't runtime data — Deployments, NetworkPolicies, RBAC, the Velero/backup schedules themselves) in sync with Region A without a restore step at all; only the stateful data path goes through Velero/AWS Backup restore. This split matters: GitOps for desired-state config (fast, cheap, continuous), backup/restore for actual runtime data (the part GitOps was never designed to carry).

Observability and enforcement. Prometheus tracks etcd_snapshot_age_seconds and velero_backup_success_total / velero_backup_partial_failure_total as first-class SLO signals, alerting through Alertmanager to PagerDuty the moment a scheduled backup is missed or a snapshot ages past its RPO budget — not just on outright failure. Kyverno enforces a cluster-wide admission policy: no StatefulSet or PVC-backed workload can be created without a matching Velero backup-schedule label, closing the "three StatefulSets nobody was backing up" gap structurally rather than by tribal knowledge. A scheduled chaos/game-day job (Section 7 and 8) periodically executes a real restore into an isolated validation namespace or cluster to keep the RTO number honest rather than assumed.

Why this design, and the trade-offs. The alternative — full active-active across two regions with synchronous or quorum-based cross-region data replication — gives a theoretical RPO near zero and eliminates the failover step entirely, but for this workload's databases (Postgres, Kafka) that means adopting cross-region-aware data topologies (Postgres logical replication with conflict handling, or a cross-region quorum store) that are a materially larger engineering lift and, for most of the 40 StatefulSets, not worth the complexity relative to the 5-minute RPO the business actually needs. Warm standby with continuous restore rehearsal was chosen as the point on the cost/complexity/RTO curve that meets the SLO without re-architecting every stateful service's replication model. As transaction volume scales past what a single-region control plane can serve even briefly during failover, the natural evolution is selective active-active for the specific services where synchronous replication is tractable (stateless services first, then the data stores with native multi-region support), while DR-via-restore remains the fallback for everything else.

4. Solution Design

Design decision: layer Velero on top of AWS Backup rather than choosing one exclusively. AWS Backup's native EKS support (GA in late 2025, cross-region copy and air-gapped vaults added through 2026) is the better tool for coordinated, policy-driven snapshots of cluster state plus EBS volumes with minimal operational overhead — but it's AWS-only, which conflicts with the org's stated goal of not being irreversibly locked into a single cloud's DR tooling. Velero's portability (it runs identically on EKS, AKS, GKE, or bare-metal) and its namespace/label-selective restore granularity are what AWS Backup doesn't give you as cleanly. Running both was judged worth the operational overhead of two backup systems, specifically because the org's five-year roadmap includes an Azure DR footprint for one regulated business unit — a Velero-only strategy pays for that portability today; an AWS Backup-only strategy would mean re-solving this problem when that Azure requirement lands.

Alternative considered and rejected: etcd-snapshot-only DR (no Velero/object-level backup). Restoring only from etcd snapshots reconstructs API objects but not PersistentVolume data — for a system where the actual money-relevant state lives in Postgres/Kafka PVs, not in ConfigMaps, this alone is close to useless. Rejected outright; etcd backup is necessary but nowhere near sufficient for this workload profile.

Alternative considered and rejected: continuous cross-region synchronous replication for every StatefulSet. Evaluated for the ledger Postgres cluster specifically (using a cross-region-aware operator). Rejected for now on cost and complexity grounds — synchronous cross-region replication adds meaningful write latency to every transaction, which the payments team was unwilling to accept for the marginal RPO improvement over a well-tested 5-minute-RPO backup/restore path. Flagged as the correct next step specifically for the ledger Postgres cluster once transaction volume or regulatory pressure changes that calculus; not applied blanket to all 40 StatefulSets, where it would be significant overkill.

Scalability considerations. Backup job duration scales with PV data volume, not object count — Velero's 1.17 VolumeGroupSnapshot and 1.18's concurrent backup processing (GA April 2026) were specifically adopted to keep backup windows from growing linearly as more StatefulSets are onboarded; concurrent processing lets independent backup jobs run in parallel rather than serializing, which was becoming the binding constraint on the nightly backup window before the 1.18 upgrade.

Cost implications. The warm-standby region's steady-state cost (control plane + minimal node pool + continuous restore-validation compute) is the largest new line item — modeled at roughly 12-18% of the primary region's compute spend, which the CFO's office accepted against the alternative of a compliance finding or an uninsurable outage. Cross-region S3 replication and Object Lock storage adds a smaller but non-trivial cost, partially offset by moving older backup tiers to Glacier Deep Archive once outside the active RPO/RTO window (kept in Object Lock, but on cheaper storage past 35 days).

Security implications. The backup-account isolation and delete-permission removal from the workload cluster's IAM role is the single highest-leverage change from this redesign — it converts backups from "recoverable unless the attacker is thorough" to "recoverable even if the attacker has full cluster-admin," which is the actual threat model compliance and the security team care about for a payments platform.

Performance implications. CSI VolumeSnapshot-based backup is near-zero-impact on running workloads (copy-on-write at the storage layer), a deliberate improvement over Velero's older restic/kopia file-system-level backup path, which was measured to add noticeable I/O pressure during backup windows on the Kafka StatefulSets specifically — file-system backup is retained only for the handful of volume types that don't support CSI snapshots.

5. Deep Technical Walkthrough

etcd's role and why its backup semantics differ from everything else in the stack. etcd stores the entire cluster state as a versioned, Raft-replicated key-value store; the API server is a thin translation layer over it. A snapshot save performs a point-in-time, consistent read of the entire keyspace via etcd's own MVCC boundary — it is not a filesystem copy of etcd's data directory (which can capture a torn write mid-Raft-log-apply) and must go through etcdctl snapshot save or the equivalent gRPC Snapshot API to guarantee consistency. Restoring is destructive and cluster-rebuilding, not additive: etcdctl snapshot restore writes a fresh data directory that becomes a new single-member cluster, which then needs the other members re-joined — this is why an etcd restore is a control-plane rebuild operation, not a live merge, and why practicing it matters more than almost any other DR step in the stack.

The etcd v3.6 upgrade path and the "zombie member" failure mode. Kubernetes 1.34's shift to etcd v3.6.5 (from the 3.5.x line pinned since 1.31) introduced a specific, documented risk: during a rolling upgrade, a member that appears to leave cleanly can, under certain sequencing, remain partially registered in the cluster's member list — a "zombie" that later causes quorum-calculation confusion. The joint Kubernetes/etcd guidance published December 2025 specifies a pre-flight checklist (verify member list consistency, confirm learner promotion completed cleanly, avoid parallel upgrades of more than one member at a time) precisely because a zombie member during a version upgrade is itself now a category of DR incident distinct from "lost a node." Kubeadm 1.34 also improved resilience here — learner promotion that succeeds etcd-side but returns a transient client error no longer forces an unnecessary re-join — but the underlying lesson holds: etcd is not a black box that "just works," and version-upgrade windows are elevated-risk periods that should coincide with, not precede, a fresh validated snapshot.

Velero's request flow, in detail. velero backup create creates a Backup custom resource; the Velero server (a Deployment watching that CRD) discovers the target resources via label/namespace selectors, queries the API server for each object, and writes them as compressed JSON into the BackupStorageLocation (an object-storage bucket abstraction). For volumes, Velero's CSI plugin creates a VolumeSnapshot object, which the CSI driver (EBS CSI driver, in this architecture) turns into a provider-native snapshot (an EBS snapshot); Velero records the mapping. Restore (velero restore create) reverses this: objects are recreated in priority order — namespaces and CRDs first, then resources that depend on them — with a restore-item-action plugin system allowing transformation (e.g., rewriting a StorageClass name or a cross-account IAM role ARN when restoring into a different AWS account, which this architecture uses to rewrite the backup-account-scoped references back to Region B's production IAM roles).

Control-plane and data-plane interaction during restore. The restore path talks exclusively to the target cluster's API server — Velero doesn't touch etcd directly, which is precisely why it composes correctly with an independent etcd-snapshot/rebuild step rather than being a substitute for one: Velero restores objects that the API server then durably writes back into (a freshly bootstrapped) etcd. On EKS specifically, there is no direct etcd access at all (AWS operates it), so the "etcd recovery" layer for this architecture is AWS's own managed control-plane resilience plus AWS Backup's coordinated snapshot of API-observable state — an important distinction from a self-managed cluster, where the team would own etcdctl snapshot restore directly.

Failure scenarios and recovery mechanisms.

  • Accidental namespace deletion: Velero restore of that namespace's Backup snapshot, filtered by --include-namespaces, typically minutes — the fastest and most common real-world DR event, and worth optimizing for specifically rather than only planning for the rare full-region case.
  • Corrupted etcd / control-plane state on a self-managed cluster: stop the API server, etcdctl snapshot restore to a new data directory, restart with a fresh single-member cluster, re-join remaining members, verify with etcdctl endpoint health across the full member list before resuming traffic — the zombie-member risk above means this verification step is not optional.
  • Full region loss including primary's object storage: this is the scenario the cross-region-replicated, separately-owned backup bucket exists for — Region B pulls from the replica bucket, which was never in the failed region's blast radius, and restores into the already-warm standby cluster rather than provisioning from scratch.
  • Compromised cluster / ransomware-style destructive actor: Object Lock compliance mode means even an attacker with full workload-account and cluster-admin access cannot delete backups inside the retention window — recovery proceeds from the immutable copy regardless of what damage occurred in the workload account.

Performance bottlenecks and scaling behavior. At this org's scale (2,200 pods, 40 StatefulSets), the binding constraint before the Velero 1.18 upgrade was serialized backup execution — one Backup CR processed at a time meant the nightly window was growing linearly with StatefulSet count. Concurrent backup processing (1.18, GA April 2026) parallelizes independent backups, and VolumeGroupSnapshot (1.17) reduces the number of snapshot operations per multi-volume application from N to one crash-consistent group, both directly targeting this bottleneck rather than just throwing more compute at it. The next scaling constraint, anticipated but not yet hit, is BackupStorageLocation API rate limits against S3 as backup-object count grows into the hundreds of thousands — mitigated preemptively by Velero's backup-repository sharding across multiple BackupStorageLocations per business unit.

6. Production Troubleshooting

Incident: the DR game-day drill misses its 30-minute RTO target, landing at 38 minutes.

Symptoms. The scheduled restore-rehearsal job (Section 3, 7) completes, but the timestamp delta between "failover triggered" and "synthetic payment transaction succeeds against Region B" exceeds SLO. The team needs to find where the 8 extra minutes went before the next auditor-witnessed drill.

Logs, metrics, traces to pull first.

  • Velero server logs (kubectl logs -n velero deploy/velero) filtered to the drill's Restore object name, checked for phase: InProgress duration per resource type — this is the first place to look for a restore that's silently retrying.
  • velero restore describe <name> --details — surfaces per-item restore errors and warnings that don't fail the overall restore but do indicate partial degradation (a common false-positive-success trap: a restore can report Completed while several items landed in PartiallyFailed sub-status).
  • Prometheus: velero_restore_total{phase="failed"}, and critically the duration histograms per resource type — velero_restore_duration_seconds broken down by whether the slow phase was object recreation (fast) or volume restore (usually the actual bottleneck, since it depends on the storage provider's snapshot-to-volume hydration time, not Velero itself).
  • Karpenter's provisioning latency metrics for the standby region — if nodes were scaled to zero, node provisioning time is frequently the hidden RTO cost nobody accounts for when only measuring "restore complete."
  • CoreDNS / K8gb health-check-controller logs for the DNS TTL and propagation delay between "failover decided" and "traffic actually arriving at Region B" — DNS TTL is a classic RTO tax that shows up in game-days and gets missed in tabletop DR plans.

Root cause analysis, step by step. In this drill, the breakdown was: ~4 minutes for the etcd/API-object restore (on target), ~11 minutes for EBS volume restore-and-attach across the Kafka StatefulSets specifically (EBS snapshot-to-volume hydration for large volumes is not instantaneous — fast-restore-enabled snapshots exist for exactly this reason and were not yet configured for the largest volumes), ~9 minutes for Karpenter to provision the node pool from zero (cold start, no pre-warmed capacity), and ~6 minutes of DNS propagation plus application warm-up (JVM-based consumers rebuilding Kafka consumer group state from scratch). None of these individually looked alarming in isolation; the compounding is what blew the SLO.

Configuration changes made. Enabled EBS fast snapshot restore on the specific snapshot IDs feeding the largest Kafka/Postgres volumes (eliminates the hydration-on-first-read penalty). Changed Region B's Karpenter NodePool from scale-to-zero to a small always-on floor (2 nodes) sized to absorb the control-plane and critical-path pods immediately, with Karpenter only handling the burst beyond that floor — converting a 9-minute cold provision into effectively zero for the critical path. Reduced Route 53 record TTL on the payments-path hostname from 300s to 30s specifically (not globally, to avoid unnecessary query volume elsewhere) ahead of the next drill.

Verification. Re-ran the drill after each change independently (not all three at once) to attribute improvement correctly — a discipline worth stating explicitly, because bundling fixes into one re-test is a common way teams end up not actually knowing which change mattered when the next incident happens under different conditions. Final measured RTO after all three changes: 14 minutes, comfortably inside the 30-minute SLO with margin for a worse day.

7. Hands-on Lab

A local, provider-agnostic version of the core mechanics, runnable on kind or minikube, validating both the etcd snapshot/restore path and a Velero object+volume backup/restore path.

Setup.

# Kind cluster with a named control-plane node for etcd access
kind create cluster --name dr-lab --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
EOF

kubectl create namespace dr-demo
kubectl -n dr-demo create deployment nginx --image=nginx --replicas=2
kubectl -n dr-demo create configmap demo-config --from-literal=key=value-before-disaster

Part A — etcd snapshot and restore.

# Exec into the control-plane node to reach etcd directly (kind-specific access path)
docker exec -it dr-lab-control-plane bash

ETCDCTL_API=3 etcdctl snapshot save /var/lib/etcd-snapshot.db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

etcdctl snapshot status /var/lib/etcd-snapshot.db --write-out=table
exit

Simulate the disaster:

kubectl -n dr-demo delete configmap demo-config
kubectl -n dr-demo scale deployment nginx --replicas=0

Restore (in a real cluster this is a full API-server-stop / new-data-dir / rejoin sequence; the lab validates the snapshot mechanics and status output rather than a full multi-member rejoin, which needs a 3-node control plane to demonstrate meaningfully):

docker exec -it dr-lab-control-plane bash
ETCDCTL_API=3 etcdctl snapshot restore /var/lib/etcd-snapshot.db \
  --data-dir=/var/lib/etcd-restored
# Inspect restored keyspace size/hash matches pre-disaster snapshot status output
ETCDCTL_API=3 etcdctl --data-dir=/var/lib/etcd-restored snapshot status /var/lib/etcd-snapshot.db

Part B — Velero backup/restore with MinIO as the object store.

helm repo add minio https://charts.min.io/
helm install minio minio/minio -n velero --create-namespace \
  --set rootUser=minioadmin,rootPassword=minioadmin123 \
  --set service.type=ClusterIP

velero install \
  --provider aws \
  --plugins velero/velero-plugin-for-aws:v1.11.0 \
  --bucket velero-backups \
  --secret-file ./minio-credentials \
  --backup-location-config region=minio,s3ForcePathStyle=true,s3Url=http://minio.velero.svc:9000 \
  --use-volume-snapshots=false

velero backup create dr-demo-backup --include-namespaces dr-demo --wait
velero backup describe dr-demo-backup --details

Simulate namespace loss and restore:

kubectl delete namespace dr-demo
velero restore create --from-backup dr-demo-backup --wait
kubectl -n dr-demo get configmap demo-config -o jsonpath='{.data.key}'
# Expect: value-before-disaster

Validation. Confirm restored object count matches backup manifest (velero backup describe dr-demo-backup --details lists item counts to diff against kubectl get all -n dr-demo post-restore), and confirm the ConfigMap's data survived byte-for-byte — the actual test of whether the pipeline, not just the tooling, works end to end.

Cleanup.

velero restore delete --all --confirm
velero backup delete dr-demo-backup --confirm
kind delete cluster --name dr-lab

8. Production Case Study

Netflix's regional failover model (Chaos Kong) is the reference point most platform teams cite, and for good reason: Netflix doesn't treat "can we survive a region loss" as a documented plan, they treat it as a continuously exercised capability, deliberately failing an entire AWS region out of production traffic on a recurring cadence via Chaos Kong (an extension of the Chaos Monkey lineage) specifically so that regional failover is muscle memory for the systems and the on-call org, not a runbook nobody has executed under pressure. The core lesson that transfers directly to Kubernetes DR: a failover path that isn't regularly exercised in production-adjacent conditions degrades silently — config drift, forgotten manual steps, and stale assumptions accumulate exactly during the period nobody is testing it, which is precisely when you can least afford discovering them.

Google's approach to control-plane resilience informs the "don't co-locate your DR trigger with the thing it's protecting" principle applied in Section 3's health-check controller design — GKE's control-plane architecture and Google's internal SRE practice both emphasize that failure-detection and failover-decision logic must have an independent failure domain from what they're monitoring, otherwise you get the class of incident where the monitoring system fails silently at the same moment as the thing being monitored, and nobody notices either failure.

AWS's own EKS backup evolution through 2025-2026 — native AWS Backup EKS support, cross-region copy, logically air-gapped vaults — is itself a response to enterprise customers running exactly the payments-platform pattern in Section 2: regulated workloads that need provably immutable, cross-account, cross-region recoverable state, where a customer-built Velero-only pipeline previously had to solve the account-isolation and immutability problem by hand. The architecture in this post's Section 3 deliberately mirrors that direction — using the managed primitive where it's strictly better (coordinated etcd-adjacent state plus EBS snapshot orchestration) and layering Velero only where portability and granularity still matter more than convenience.

Uber's multi-region data infrastructure work is the cautionary counterpoint on cost: Uber has published extensively on the real cost of true multi-region active-active for stateful systems — cross-region replication lag, conflict resolution complexity for write-heavy systems, and the operational cost of running every data store's replication topology as a first-class engineering concern. It's the strongest real-world argument for why this post's Section 4 rejected blanket synchronous cross-region replication for all 40 StatefulSets: at Uber's scale, that complexity is worth it for the specific systems where it's load-bearing; applied indiscriminately, it becomes an organization-wide tax that most services never needed to pay.

9. Architecture Review

Strengths. The backup-account isolation with delete-permission removal is the single highest-value control in this design — it changes the threat model from "recoverable if the attacker isn't thorough" to "recoverable regardless of workload-account compromise," which is the actual bar a payments platform's auditors and security team care about. The continuous restore-rehearsal process (rather than an annual break-glass drill) is what converts the RTO number from a hopeful estimate into a measured, defended SLO — Section 6's incident is exactly the kind of gap that only surfaces when you actually run the drill.

Weaknesses. The warm-standby region, as designed, still has a single-region etcd/control-plane story within each region — Region A's control plane, even with AWS-managed multi-AZ etcd replication, is a single logical control plane, and an AZ-spanning-but-region-scoped failure mode (rare, but not zero, per AWS's own documented control-plane incidents) isn't distinguished from a full regional loss in this design; both trigger the same cross-region failover, which is correct but means the RTO clock starts for AZ-level events that a properly multi-AZ-resilient control plane arguably shouldn't need a full regional failover to recover from. The 40 StatefulSets' backup/restore path is uniform (Velero + CSI snapshot) regardless of each workload's actual RPO sensitivity — the Kafka clusters carrying transaction event logs and a low-traffic internal reporting Postgres instance get the same backup cadence and priority today, which is operationally simple but not cost- or risk-optimal.

What fails first at 10x scale. Backup storage API rate limits and restore-time volume hydration are the first ceiling — at 10x the StatefulSet count and data volume, even with VolumeGroupSnapshot and concurrent backup processing, the nightly backup window and the restore-time EBS hydration (Section 6's root cause) become binding constraints requiring backup-repository sharding across more BackupStorageLocations and pre-warmed fast-restore snapshots as a default, not an incident-driven fix. ArgoCD's hub-and-spoke multi-cluster registration also becomes a scaling concern past a handful of DR regions — it was designed for two regions, and a third or fourth region introduces sync-fanout and potential hub-availability-as-single-point-of-failure questions this design hasn't addressed.

What changes at 100 million users / global scale. Warm standby stops being sufficient for the highest-tier services; the ledger and payments-critical path specifically would need to move to active-active with a cross-region-quorum data layer (as flagged but deferred in Section 4), because at that scale the business cost of even a 14-minute RTO on the payments path exceeds the engineering cost of solving synchronous cross-region replication properly. DNS-based failover (even with K8gb's improvements) starts to show its limits at global scale too — the shift would be toward client-side or edge-level (Anycast, or an intelligent request-routing layer) failover that doesn't depend on DNS TTL and resolver cache behavior across a globally distributed client base, which is a known weak point of pure DNS GSLB at extreme scale.

What would be redesigned. Tiered RPO/RTO by workload criticality — not every StatefulSet needs the same backup cadence or restore priority, and treating them uniformly (today's design) wastes backup-window budget on low-priority data while not necessarily prioritizing restore-ordering for the highest-priority services during an actual incident. This is the most concrete, immediately actionable redesign, independent of the 10x/100M-scale questions.

10. Best Practices

Reliability. Treat "backup succeeded" and "restore verified" as two separate SLOs with two separate alerting paths — a green backup dashboard with an untested restore path is the exact failure mode Section 2's investigation uncovered. Scalability. Adopt concurrent/parallel backup processing and volume-group snapshotting before backup-window growth becomes an incident, not after; backup infrastructure capacity planning deserves the same rigor as workload capacity planning. Observability. Instrument snapshot age, not just success/failure — etcd_snapshot_age_seconds and equivalent gauges catch the silent-drift failure mode (a CronJob quietly stuck for three days without an outright error) that binary success metrics miss entirely. Security. Isolate backup storage into a separate account/subscription with asymmetric permissions (write-only from the workload side, delete only from a break-glass path) as a non-negotiable baseline for any regulated workload, and enable Object Lock/immutability by default rather than as an afterthought bolted on post-incident. Cost Optimization. Tier backup storage by age (hot for the active RPO/RTO window, cold/archive beyond compliance retention) and size the warm-standby footprint against measured RTO need, not a reflexive "just run a full second production cluster" default. Performance. Prefer CSI VolumeSnapshot over filesystem-level backup wherever the storage driver supports it — the I/O-impact difference on live workloads during backup windows is substantial at StatefulSet scale. Maintainability. Keep backup schedules, retention policy, and restore runbooks in the same GitOps repo as the workloads they protect, version-controlled and reviewed like any other production config, not as a separately-maintained side document that drifts from reality. Operational Excellence. Run restore drills on a cadence that matches your compliance and risk tolerance (quarterly at minimum for a regulated payments platform, continuous/automated where feasible), and treat every drill's timing gaps the way Section 6 treated the 8-minute overage — as an actionable finding, not a rounding error.

11. Common Production Mistakes

Treating a green backup job as proof of recoverability is the most common and most dangerous anti-pattern — backup success and restore success are different guarantees, and only testing the first one is how organizations discover their real RTO during an actual incident instead of a drill. Storing backups with credentials reachable from the cluster being protected defeats the purpose of having backups at all in any scenario involving cluster compromise, not just accidental deletion — this is worth an explicit architecture review question, not an assumption. Skipping etcd-specific backup on the theory that "Velero covers everything" ignores that Velero backs up what the API server can show it, which depends on etcd being healthy enough to serve those reads in the first place — the two are complementary layers, not substitutes. Backing up PersistentVolumes inconsistently or per-team, without a cluster-wide enforced policy, reliably produces the "three StatefulSets nobody noticed weren't being backed up" gap from Section 2 — admission-policy enforcement (Kyverno, OPA) closes this structurally where tribal knowledge and good intentions repeatedly fail to. Finally, sizing a warm-standby region's compute floor to zero (scale-to-zero everywhere) optimizes cost at the direct expense of RTO, as Section 6's drill demonstrated concretely — the right answer is a deliberately-sized floor for the critical path, not a blanket scale-to-zero policy applied without regard to provisioning latency.

12. Interview Preparation

"Walk me through what happens, step by step, if you lose an entire etcd cluster with no surviving members." A strong answer distinguishes managed (EKS/AKS/GKE, where you don't have direct etcd access and are dependent on the provider's control-plane backup/restore SLA) from self-managed (where you own etcdctl snapshot restore directly): for self-managed, restoring a snapshot creates a new single-member cluster from that point-in-time state, which then needs remaining members re-joined as learners and promoted — emphasize that this is a full control-plane rebuild, not a live merge, and that anything written to etcd after the snapshot's timestamp is lost (this is your RPO in concrete terms), which is why snapshot frequency and off-node storage matter as much as the restore mechanism itself.

"How do you design a backup strategy that survives an attacker with full cluster-admin access?" The core answer is account/trust-boundary isolation: the credentials the backup tooling uses inside the compromised cluster must not have delete permission on the backup target, and ideally live in a separate account/subscription entirely; combine with object-storage immutability (Object Lock compliance mode, or equivalent) so that even an attacker with the backup-write credentials cannot delete or overwrite within the retention window. A weak answer stops at "we encrypt backups" — encryption doesn't address deletability, which is the actual threat in a destructive-actor scenario.

"What's the difference between RPO and RTO, and how do they drive different architecture decisions?" RPO (how much data you can afford to lose, measured backward from the incident) drives backup frequency and replication design; RTO (how long you can afford to be down, measured forward from the incident) drives standby infrastructure readiness — a warm standby with pre-provisioned capacity buys RTO, more frequent snapshots buy RPO, and conflating the two leads to over-investing in one while leaving the other exposed, which is exactly what happened in Section 2's fintech scenario (nightly backups looked like a reasonable RPO story on paper, but zero standby infrastructure meant RTO was effectively unbounded).

"Your Velero restore reports Completed but the application is broken after restore. How do you debug this?" Points toward velero restore describe --details, which surfaces per-item warnings that don't fail the overall restore phase — a PartiallyFailed sub-status on individual resources is a common false-positive-success trap. Also probe for CRD-ordering issues (restoring a custom resource before its CRD definition exists) and cross-environment reference rewriting (StorageClass names, IAM role ARNs, image registry references) that differ between source and target environments — Velero's restore-item-action plugin system exists specifically to handle this class of transformation, and not configuring it is a common silent-failure source.

"How would you design multi-region DR for a stateful, low-latency-sensitive service versus a stateless API layer, differently?" Strong candidates separate these explicitly: stateless services are close to trivial to make active-active (just run them in both regions behind GSLB, no data-consistency problem), while stateful services require a deliberate choice per data store — backup/restore-based DR (simpler, higher RPO/RTO, appropriate for most services) versus native cross-region replication or quorum (lower RPO/RTO, materially higher engineering cost, reserved for the services where the business impact justifies it) — and should be able to articulate that applying the expensive option uniformly is itself an architecture mistake, not a safety margin.

13. Latest Industry Updates

Velero 1.17 and 1.18 (through Q1-Q2 2026) shipped VolumeGroupSnapshot support for crash-consistent multi-volume backups, a modernized file-system-backup architecture, Windows cluster support, and — in 1.18 — concurrent backup processing plus incremental backup size reporting, directly targeting the backup-window scaling constraint discussed in Section 5. Teams still running pre-1.17 Velero on multi-volume StatefulSet-heavy clusters should treat the upgrade as a backup-window capacity fix, not just a routine version bump.

Kubernetes 1.34's etcd v3.6.5 default (via kubeadm) and the joint Kubernetes/etcd "zombie cluster member" guidance (published December 2025, directly relevant through 2026 upgrade cycles) means any self-managed cluster planning the 1.31/1.32/1.33 → 1.34 jump should read that guidance before upgrading, not after hitting a quorum-confusion incident — this is a live, current risk for anyone still on the 3.5.x-pinned line.

AWS Backup's native EKS support continued expanding through 2026 — logically air-gapped vault support landed in March, and European Sovereign Cloud region availability in June — meaningfully lowering the bar for the account-isolation and immutability architecture described in Section 3 for teams on EKS specifically; what required custom IAM and bucket-policy engineering even a year ago is increasingly a managed-service checkbox.

K8gb's CNCF incubation (August 2026) is worth tracking for any team currently hand-rolling DNS-based multi-cluster failover with Route 53 health checks and custom Lambda/controller glue — it's a signal the ecosystem is standardizing this exact pattern (CoreDNS + ExternalDNS-based global balancing) rather than leaving every platform team to reinvent it, and is a reasonable evaluation candidate before building bespoke failover-controller logic from scratch.

CNCF's "three reproducible failure scenarios" DR guidance (published this month, September 2026) is directly relevant to Section 6 and 7 of this post — it's built around the same core thesis this post opened with: backups and recoverability are different capabilities, and the guidance provides concrete, reproducible scenarios worth running against your own cluster rather than taking on faith that your existing DR posture would actually hold up.

14. Summary & Cheat Sheet

Key concepts. DR for Kubernetes has two independent state layers — etcd/API-observable cluster state, and PersistentVolume/application data — that fail independently and need separate, composable backup mechanisms; RPO drives backup frequency and replication design, RTO drives standby infrastructure readiness, and conflating them leads to over-investing in one while leaving the other exposed.

Architecture. Global GSLB/health-check layer (independent failure domain from the clusters it monitors) → active primary region with etcd snapshot/AWS Backup + Velero/CSI VolumeSnapshot running continuously → backup storage in an isolated account with immutability and no delete permission reachable from the workload cluster → warm-standby second region kept current via GitOps (config) and continuous restore-rehearsal (data) → observability on snapshot age and restore success, not just backup success.

Commands worth memorizing.

etcdctl snapshot save <file> --endpoints=... --cacert=... --cert=... --key=...
etcdctl snapshot status <file> --write-out=table
etcdctl snapshot restore <file> --data-dir=<new-dir>
velero backup create <name> --include-namespaces <ns> --wait
velero backup describe <name> --details
velero restore create --from-backup <name> --wait
velero restore describe <name> --details

Best-practice checklist. Separate backup-success and restore-verified SLOs with independent alerting. Backup credentials write-only, never delete, from inside the protected cluster. Object Lock / immutability enabled by default on backup storage. Admission-policy-enforced backup coverage for every StatefulSet/PVC workload — no exceptions by tribal knowledge. Warm-standby compute floor sized against measured provisioning latency, not scaled blindly to zero. Quarterly-minimum, ideally continuous, restore drills with timing gaps tracked as findings.

Troubleshooting checklist. Check velero restore describe --details for per-item PartiallyFailed warnings before trusting an overall Completed status. Break down restore duration by phase (object recreation vs. volume hydration vs. node provisioning vs. DNS propagation) before assuming the bottleneck is Velero itself. Verify etcd member-list consistency and endpoint health across all members after any restore or version-upgrade-driven rejoin, given the documented v3.6 zombie-member risk. Attribute fixes one change at a time across drill re-runs so the next incident's root cause isn't ambiguous.

Design pattern to remember. Layer managed and portable tooling deliberately (AWS Backup for the parts it does better, Velero for portability and granularity) rather than picking one exclusively — and tier your DR investment by workload criticality rather than applying the most expensive pattern (active-active, synchronous cross-region replication) uniformly across every stateful service.