Overview

“Hope for the best, prepare for the worst.” In Kubernetes production environments, disaster recovery is the last line of defense. Whether it’s etcd corruption, accidental deletion, node failure, or even entire cluster loss, having a solid backup and recovery strategy is critical.

This article systematically covers K8s disaster recovery—from etcd backup and restore, Velero full-cluster backup, PV data backup, to cross-cluster recovery and disaster recovery architecture design.

Based on Kubernetes v1.30 and Velero v1.14. Reference: Kubernetes etcd Backup, Velero Documentation

Disaster Recovery Fundamentals

RPO and RTO

MetricFull NameDescriptionExample
RPORecovery Point ObjectiveMaximum acceptable data lossRPO=15min means at most 15min of data loss
RTORecovery Time ObjectiveMaximum acceptable downtimeRTO=4h means recovery within 4 hours
RTO/RPO-DR strategy targetsLow values = high cost

DR Strategy Levels

LevelRPORTODescription
Level 124h24hDaily backup, manual recovery
Level 21h4hHourly backup, semi-automated recovery
Level 315min1hContinuous backup, automated recovery
Level 400Active-active, no data loss, instant switch

Recovery Scope

ScopeDescriptionMethod
Single resourceAccidental deletionetcd rollback or Velero restore
Multiple resourcesBulk misconfigurationVelero restore
Node failureNode unavailableReschedule + data recovery
etcd corruptionCluster state lostetcd restore
Cluster lossEntire cluster downCross-cluster recovery

etcd Backup and Restore

Why Back Up etcd

etcd is the sole persistent store for K8s cluster state—everything (Pods, Services, Deployments, Secrets, ConfigMaps) lives in etcd. If etcd is lost and there’s no backup, the entire cluster is gone.

etcd Backup

# Method 1: etcdctl snapshot (recommended)
export ETCDCTL_API=3
etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d-%H%M%S).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

# Verify snapshot
etcdctl snapshot status /backup/etcd-snapshot-20260101-120000.db \
  --write-out=table
# Method 2: Copy etcd data directory
# Stop etcd first
systemctl stop etcd

# Copy data directory
cp -r /var/lib/etcd /backup/etcd-data-$(date +%Y%m%d)

# Restart etcd
systemctl start etcd
# Method 3: Automated backup script
cat > /usr/local/bin/etcd-backup.sh << 'EOF'
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/backup/etcd"
RETENTION_DAYS=7
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/etcd-snapshot-${TIMESTAMP}.db"

export ETCDCTL_API=3

# Create backup directory
mkdir -p "${BACKUP_DIR}"

# Take snapshot
etcdctl snapshot save "${BACKUP_FILE}" \
  --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

# Verify
etcdctl snapshot status "${BACKUP_FILE}" --write-out=table

# Clean up old backups
find "${BACKUP_DIR}" -name "etcd-snapshot-*.db" -mtime +${RETENTION_DAYS} -delete

# Upload to remote storage (e.g., S3)
# aws s3 cp "${BACKUP_FILE}" "s3://k8s-etcd-backup/$(date +%Y/%m/%d)/"

echo "Backup completed: ${BACKUP_FILE}"
EOF

chmod +x /usr/local/bin/etcd-backup.sh

# Schedule with cron
echo "0 */6 * * * root /usr/local/bin/etcd-backup.sh >> /var/log/etcd-backup.log 2>&1" > /etc/cron.d/etcd-backup

etcd Restore

# 1. Stop all control plane components
systemctl stop kube-apiserver
systemctl stop kube-controller-manager
systemctl stop kube-scheduler
systemctl stop etcd

# 2. Backup current etcd data (safety measure)
mv /var/lib/etcd /var/lib/etcd.broken

# 3. Restore from snapshot
export ETCDCTL_API=3
etcdctl snapshot restore /backup/etcd-snapshot-20260101-120000.db \
  --data-dir=/var/lib/etcd \
  --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

# 4. Fix permissions
chown -R etcd:etcd /var/lib/etcd

# 5. Restart etcd
systemctl start etcd

# 6. Verify etcd is healthy
etcdctl endpoint health \
  --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

# 7. Restart control plane components
systemctl start kube-apiserver
systemctl start kube-controller-manager
systemctl start kube-scheduler

# 8. Verify cluster
kubectl get nodes
kubectl get pods -A

HA etcd Cluster Recovery

For multi-node etcd clusters, recovery depends on how many nodes failed:

ScenarioData StatusRecovery Method
1 node failedQuorum intactRemove failed member, rejoin
Multiple nodes, quorum lostMay be inconsistentRestore from backup on all members
All nodes failedRestore from backupFull restore from backup
# Recover single failed member
# On healthy member:
etcdctl member list \
  --endpoints=https://healthy-node:2379 \
  --cacert=... --cert=... --key=...

# Remove failed member
etcdctl member remove <failed-member-id> \
  --endpoints=https://healthy-node:2379 \
  --cacert=... --cert=... --key=...

# Rejoin fixed node
systemctl stop etcd
rm -rf /var/lib/etcd
etcdctl member add node-3 \
  --peer-urls=https://node-3:2380 \
  --endpoints=https://healthy-node:2379 \
  --cacert=... --cert=... --key=...

# Update etcd config to use initial-existing flag
# Start etcd with --initial-cluster-state=existing
systemctl start etcd

Velero Overview

What Is Velero

Velero (formerly Heptio Ark) is an open-source K8s backup and migration tool. Unlike etcd snapshots (which back up everything), Velero provides granular resource-level backup and restore:

Featureetcd SnapshotVelero
Backup scopeEntire etcdSelected namespaces/resources
Resource filteringNoYes (labels, selectors)
PV data backupNoYes (via snapshots or restic)
Cross-cluster migrationNoYes
Scheduled backupsManualYes (Schedule CRD)
Recovery granularityAll or nothingPer-resource
Version compatibilityStrictFlexible

Velero Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Velero Architecture                      │
│                                                              │
│   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│   │   Backup     │    │   Schedule   │    │   Restore    │  │
│   │   (CRD)      │    │   (CRD)      │    │   (CRD)      │  │
│   └──────┬───────┘    └──────┬───────┘    └──────┬───────┘  │
│          │                    │                    │          │
│          ▼                    ▼                    ▼          │
│   ┌─────────────────────────────────────────────────────┐    │
│   │                   Velero Controller                   │    │
│   │   ┌─────────────────┐  ┌─────────────────────────┐  │    │
│   │   │  Backup/Restore │  │  Storage Location        │  │    │
│   │   │  Controller     │  │  Controller              │  │    │
│   │   └─────────────────┘  └─────────────────────────┘  │    │
│   └─────────────────────────────────────────────────────┘    │
│          │                    │                               │
│          ▼                    ▼                               │
│   ┌──────────────┐    ┌──────────────┐                       │
│   │   Object     │    │   Volume     │                       │
│   │   Storage    │    │   Snapshot   │                       │
│   │   (S3/MinIO) │    │   (CSI/     │                       │
│   │               │    │    restic)   │                       │
│   └──────────────┘    └──────────────┘                       │
└─────────────────────────────────────────────────────────────┘

Installation

# Install Velero CLI
wget https://github.com/vmware-tanzu/velero/releases/download/v1.14.0/velero-v1.14.0-linux-amd64.tar.gz
tar -xzf velero-v1.14.0-linux-amd64.tar.gz
sudo mv velero-v1.14.0-linux-amd64/velero /usr/local/bin/

# Create credentials file
cat > /root/velero-credentials << 'EOF'
[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF

# Install Velero with S3 backend
velero install \
  --provider aws \
  --bucket k8s-velero-backup \
  --backup-location-config region=us-east-1,s3ForcePathStyle=true,s3Url=https://minio.example.com \
  --snapshot-location-config region=us-east-1 \
  --secret-file /root/velero-credentials \
  --use-volume-snapshots=true \
  --use-restic \
  --default-volumes-to-restic \
  --namespace velero

# Verify installation
kubectl get pods -n velero
kubectl get backupstoragelocation -n velero

Velero Backup

On-demand Backup

# Full cluster backup
velero backup create full-cluster-backup-20260101

# Backup specific namespace
velero backup create production-backup --include-namespaces production

# Backup with label selector
velero backup create frontend-backup \
  --selector app=frontend

# Backup specific resource types
velero backup create deployments-only \
  --include-resources deployments,services

# Backup with exclude rules
velero backup create exclude-system \
  --exclude-namespaces kube-system,velero \
  --exclude-resources secrets,configmaps

Scheduled Backups

# Daily full cluster backup, retain 7 copies
velero schedule create daily-full-backup \
  --schedule="0 2 * * *" \
  --include-namespaces production,staging \
  --ttl 168h

# Hourly namespace backup
velero schedule create hourly-prod-backup \
  --schedule="@every 1h" \
  --include-namespaces production \
  --ttl 24h

# List schedules
velero schedule get

# Pause/resume schedule
velero schedule pause daily-full-backup
velero schedule unpause daily-full-backup

Backup Hooks

# Pre/post backup hooks (e.g., flush database)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: production
  annotations:
    pre.hook.backup.velero.io/container: postgres
    pre.hook.backup.velero.io/command: '["/bin/bash", "-c", "pg_dump -U postgres mydb > /tmp/dump.sql"]'
    post.hook.backup.velero.io/container: postgres
    post.hook.backup.velero.io/command: '["/bin/bash", "-c", "rm /tmp/dump.sql"]'
spec:
  template:
    spec:
      containers:
      - name: postgres
        image: postgres:16

View Backups

# List all backups
velero backup get

# View backup details
velero backup describe daily-full-backup-20260101 --details

# View backup logs
velero backup logs daily-full-backup-20260101

Velero Restore

On-demand Restore

# Full restore from backup
velero restore create --from-backup daily-full-backup-20260101

# Restore specific namespaces
velero restore create --from-backup daily-full-backup-20260101 \
  --include-namespaces production

# Restore with new namespace name
velero restore create --from-backup daily-full-backup-20260101 \
  --namespace-mappings production:production-restored

# Restore specific resources
velero restore create --from-backup daily-full-backup-20260101 \
  --include-resources deployments,services,configmaps \
  --include-namespaces production

# Restore with label selector
velero restore create --from-backup daily-full-backup-20260101 \
  --selector app=critical

Restore Strategy

# Dry run (don't actually restore, just show what would be restored)
velero restore create --from-backup daily-full-backup-20260101 \
  --dry-run \
  -o yaml > restore-plan.yaml

# Review restore plan
cat restore-plan.yaml

# Actual restore
velero restore create --from-backup daily-full-backup-20260101

# Verify
velero restore get
velero restore describe daily-full-backup-20260101-202601011500 --details

Restore to Different Cluster

# On target cluster, configure the same backup storage
velero install \
  --provider aws \
  --bucket k8s-velero-backup \
  --backup-location-config region=us-east-1,s3Url=https://minio.example.com \
  --secret-file /root/velero-credentials \
  --use-volume-snapshots=false \
  --namespace velero

# Set source backup location
velero backup-location create shared-backup \
  --provider aws \
  --bucket k8s-velero-backup \
  --config region=us-east-1,s3Url=https://minio.example.com \
  --default

# List backups from source cluster
velero backup get

# Restore
velero restore create --from-backup daily-full-backup-20260101

PV Data Backup

Volume Snapshot Method

# Requires CSI driver with snapshot support
# Check if VolumeSnapshotClass is available
kubectl get volumesnapshotclass

# Create snapshot during backup
velero backup create snapshot-backup \
  --include-namespaces production \
  --snapshot-volumes=true
# VolumeSnapshotClass example
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-hostpath-snapclass
driver: hostpath.csi.k8s.io
deletionPolicy: Retain

Restic Backup

For CSI drivers that don’t support snapshots, Velero uses restic for file-level backup:

# Install Velero with restic support
velero install \
  --provider aws \
  --bucket k8s-velero-backup \
  --backup-location-config region=us-east-1 \
  --secret-file /root/velero-credentials \
  --use-restic \
  --default-volumes-to-restic    # Default all volumes to restic backup
# Annotate Pod to specify restic backup volumes
apiVersion: v1
kind: Pod
metadata:
  name: app-with-data
  namespace: production
  annotations:
    backup.velero.io/backup-volumes: data,config    # Comma-separated volume names
spec:
  containers:
  - name: app
    image: myapp:v1
    volumeMounts:
    - name: data
      mountPath: /data
    - name: config
      mountPath: /config
  volumes:
  - name: data
    persistentVolumeClaim:
      claimName: app-data
  - name: config
    persistentVolumeClaim:
      claimName: app-config

Backup Method Comparison

FeatureVolume SnapshotRestic
Backup methodBlock-level snapshotFile-level copy
CSI dependencyRequires snapshot supportNone
Backup speedFast (seconds)Slow (depends on data size)
Cross-providerNoYes
DeduplicationNoYes
IncrementalDepends on CSIYes
Resource overheadLowMedium

Cross-Cluster Recovery

Scenario: Cluster Migration

Source cluster: Cluster-A (us-east-1)
Target cluster: Cluster-B (eu-west-1)

1. Backup on Cluster-A  Upload to shared S3
2. Configure Cluster-B to read from same S3
3. Restore on Cluster-B
4. Verify applications
5. Switch traffic (DNS or load balancer)

Scenario: Disaster Failover

Primary cluster: Cluster-A (us-east-1)
DR cluster: Cluster-B (us-west-2)

Normal operation:
  - Application runs on Cluster-A
  - Velero scheduled backup → S3 (cross-region replication)
  - DR cluster standby

Disaster failover:
  1. Cluster-A fails
  2. Cluster-B restores latest backup from S3
  3. Traffic switches to Cluster-B
  4. Cluster-A rebuilt (new cluster, restore from S3)

Cross-Cluster Recovery Steps

# On DR cluster (Cluster-B):

# 1. Install Velero, configure same backup storage
velero install \
  --provider aws \
  --bucket k8s-velero-backup \
  --backup-location-config region=us-west-2 \
  --secret-file /root/velero-credentials \
  --use-restic

# 2. Sync backups from source
velero backup-location create primary-backup \
  --provider aws \
  --bucket k8s-velero-backup \
  --config region=us-east-1 \
  --default

# 3. List backups
velero backup get

# 4. Restore
velero restore create --from-backup daily-full-backup-20260101

# 5. Verify
kubectl get pods -A
kubectl get svc -A

Recovery Drills

Why Drill

A backup that hasn’t been tested in recovery is not a backup—it’s a hope. Regular recovery drills:

  • Verify backup integrity
  • Practice recovery procedures
  • Measure actual RTO
  • Identify gaps
  • Build team confidence

Drill Plan

Drill TypeFrequencyScopeSuccess Criteria
etcd restoreQuarterlyTest clusterCluster recovers, all Pods running
Velero restoreMonthlyTest clusterResources restored, data intact
Cross-cluster failoverSemi-annuallyDR clusterApplication accessible on DR
Full disaster simulationAnnuallyAll clustersRTO/RPO met

Drill Execution

# etcd Recovery Drill

# 1. Record current state
kubectl get pods -A -o wide > /tmp/before-drill-pods.txt
kubectl get deployments -A > /tmp/before-drill-deployments.txt

# 2. Simulate disaster (on test cluster only!)
# Delete a namespace
kubectl delete namespace test-app

# 3. Restore from backup
velero restore create --from-backup latest-backup \
  --include-namespaces test-app

# 4. Verify recovery
kubectl get pods -n test-app
diff <(kubectl get pods -A -o wide) /tmp/before-drill-pods.txt

# 5. Record results
echo "Drill completed at $(date)" >> /tmp/drill-log.txt
echo "RTO: <measured time>" >> /tmp/drill-log.txt

Drill Checklist

□ Backup files exist and are accessible
□ Recovery procedure documented and up to date
□ Recovery team knows the procedure
□ Test environment available
□ Recovery completed within RTO
□ All critical resources restored
□ Application functional post-recovery
□ Data loss within RPO
□ Recovery logs captured
□ Improvement actions identified

DR Architecture Design

Single Cluster with Backup

┌──────────────────────────────────────────┐
│              Single Cluster               │
│                                          │
│  ┌────────────┐  ┌────────────────────┐ │
│  │ K8s Cluster │  │ Velero Backup      │ │
│  │             │──│ → S3 Storage       │ │
│  │ Applications │  │ → Daily snapshots  │ │
│  │             │  │                   │ │
│  └────────────┘  └────────────────────┘ │
└──────────────────────────────────────────┘

RPO: 24h (daily backup)
RTO: 4-8h (manual recovery)
Cost: Low
Suitable: Development, testing

Active-Passive (Primary-DR)

┌─────────────────────────────────────────────────────────────┐
│                  Active-Passive Architecture                   │
│                                                              │
│  ┌──────────────────┐          ┌──────────────────┐        │
│  │  Primary Cluster  │          │   DR Cluster      │        │
│  │  (us-east-1)      │          │   (us-west-2)     │        │
│  │                   │          │                   │        │
│  │  ┌─────────────┐  │          │  ┌─────────────┐  │        │
│  │  │ Applications│  │          │  │  Standby     │  │        │
│  │  │ (Running)   │  │          │  │  (Idle)      │  │        │
│  │  └─────────────┘  │          │  └─────────────┘  │        │
│  └────────┬──────────┘          └──────────────────┘        │
│           │                    ▲                              │
│           ▼                    │                              │
│  ┌──────────────────────────────────────────┐               │
│  │      Cross-region S3 Replication         │               │
│  │      (Velero backup sync)                │               │
│  └──────────────────────────────────────────┘               │
└─────────────────────────────────────────────────────────────┘

RPO: 1h (hourly backup + replication)
RTO: 1-2h (automated restore)
Cost: Medium (DR cluster runs minimal)
Suitable: Production

Active-Active

┌─────────────────────────────────────────────────────────────┐
│                  Active-Active Architecture                    │
│                                                              │
│  ┌──────────────────┐          ┌──────────────────┐        │
│  │  Cluster A       │          │   Cluster B       │        │
│  │  (us-east-1)      │          │   (eu-west-1)      │        │
│  │                   │          │                   │        │
│  │  ┌─────────────┐  │          │  ┌─────────────┐  │        │
│  │  │ Applications│  │          │  │ Applications│  │        │
│  │  │ (Running)   │  │          │  │ (Running)   │  │        │
│  │  └─────────────┘  │          │  └─────────────┘  │        │
│  └────────┬──────────┘          └────────┬──────────┘        │
│           │                              │                    │
│           ▼                              ▼                    │
│  ┌─────────────────────────────────────────────────────┐    │
│  │            Global Load Balancer                     │    │
│  │    (DNS / Traffic Manager / Service Mesh)          │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │         Shared State Replication                    │    │
│  │    (Database replication / Object storage sync)     │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘

RPO: 0 (real-time replication)
RTO: 0 (instant switch)
Cost: High (both clusters fully active)
Suitable: Mission-critical

Best Practices

Backup Strategy

  1. Multiple backup types: etcd snapshots (full cluster) + Velero (granular) + application-level backups (database dumps).
  2. Backup frequency matches RPO: If RPO=15min, schedule 15min backups.
  3. Backup retention: Keep at least 7 daily, 4 weekly, 12 monthly backups.
  4. Offsite backup: Store backups in a different region or cloud.
  5. Backup verification: Regularly verify backup integrity.

Recovery Strategy

  1. Documented runbooks: Every recovery procedure should have a step-by-step runbook.
  2. Regular drills: Test recovery quarterly at minimum.
  3. Automated recovery: Use scripts or tools to reduce human error.
  4. Team training: Ensure multiple team members can perform recovery.
  5. Post-incident review: After any real recovery, document lessons learned.

etcd Specific Best Practices

PracticeDescription
Regular snapshotsAt least every 6 hours for production
Multiple backup locationsLocal + remote (S3)
Verify snapshotsUse etcdctl snapshot status
Test restoreQuarterly on test environment
Monitor etcd healthDisk space, memory, latency
Compact and defragPeriodically compact and defrag etcd

Velero Best Practices

PracticeDescription
Use schedulesAutomate backups with Schedule CRD
Filter resourcesExclude kube-system if not needed
Use hooksPre/post hooks for database dumps
Test restoresMonthly restore drills
Monitor backupsCheck backup status daily
Restic for non-CSIUse restic for volumes without snapshot support

Common Issues

Backup Failure

# View backup errors
velero backup describe <backup-name> --details

# Common issues:
# 1. Object storage unreachable
#    Check credentials and endpoint
# 2. Volume snapshot timeout
#    Check CSI driver and storage class
# 3. Restic repository lock
#    Run: velero restic repo unlock
# 4. Resource too large
#    Split backup by namespace

Restore Failure

# View restore errors
velero restore describe <restore-name> --details

# Common issues:
# 1. Resource already exists
#    Use --existing-resource-policy=update
# 2. Namespace doesn't exist
#    Create namespace first or use --namespace-mappings
# 3. PV binding fails
#    Check storage class and PV availability
# 4. Restic restore slow
#    Check network and storage performance

etcd Restore Issues

# etcd won't start after restore
# Check:
# 1. File permissions
chown -R etcd:etcd /var/lib/etcd
# 2. Certificate validity
openssl x509 -in /etc/kubernetes/pki/etcd/server.crt -text -noout | grep -A2 Validity
# 3. Data directory conflicts
rm -rf /var/lib/etcd.broken
# 4. etcd config file
# Check --data-dir and --initial-cluster flags

Summary

K8s disaster recovery is the last line of defense—hope for the best, prepare for the worst. Key takeaways:

  1. etcd is the most critical backup: All cluster state lives in etcd; without etcd backup, there’s no recovery. Schedule regular snapshots and store them offsite.
  2. Velero for granular recovery: Velero enables resource-level backup and restore, more suitable for daily ops than etcd snapshots.
  3. PV data needs special attention: etcd and Velero back up K8s resource objects; PV data requires volume snapshots or restic.
  4. Cross-cluster recovery requires planning: Backup storage must be accessible from both clusters; test cross-cluster restore.
  5. Untested backups are not backups: Regular recovery drills are critical—verify backup integrity and practice recovery procedures.
  6. Match DR architecture to requirements: Choose single-cluster with backup, active-passive, or active-active based on RPO/RTO requirements and budget.
  7. Document and automate: Every recovery procedure should be documented, automated where possible, and team-trained.

Disaster recovery is not a one-time project—it’s an ongoing practice. Backups, drills, and documentation must be kept up to date as the cluster evolves.

References & Acknowledgments

This article referenced the following materials during writing. We thank the original authors for their contributions:

  1. Kubernetes etcd Backup — Kubernetes Official, referenced for Kubernetes etcd Backup
  2. Kubernetes 灾难恢复文档 — Kubernetes Official, referenced for Kubernetes 灾难恢复文档
  3. Velero Documentation — Velero Project, referenced for Velero Documentation