Kubernetes etcd: backup, restore en disaster recovery

etcd bevat elk object in je Kubernetes-cluster: Deployments, Secrets, RBAC-regels, CRDs. Als je etcd kwijtraakt, ben je het cluster kwijt. Deze handleiding behandelt het maken van snapshots met etcdctl, geautomatiseerde backups via een CronJob, single-node restore, multi-node HA restore, en de revision-bump-vlaggen die controllercache-corruptie na herstel voorkomen.

Inhoudsopgave

Voordat je begint

Deze handleiding richt zich op self-managed Kubernetes-clusters die met kubeadm zijn opgezet. Draai je EKS, GKE of AKS, dan beheert de cloudprovider etcd voor je en heb je er geen directe toegang toe. Op managed clusters verschuift je backupverantwoordelijkheid naar Kubernetes-objecten (via Velero) en persistent volume data.

Vereisten:

  • SSH-toegang tot alle control plane nodes
  • etcdctl en etcdutl binaries die overeenkomen met je draaiende etcd-versie (controleer met etcdctl version)
  • TLS-certificaten voor het etcd-endpoint. Op kubeadm-clusters staan deze op:
    • CA: /etc/kubernetes/pki/etcd/ca.crt
    • Cert: /etc/kubernetes/pki/etcd/server.crt
    • Key: /etc/kubernetes/pki/etcd/server.key
  • Bevestig certificaatpaden vanuit het static pod manifest: grep file /etc/kubernetes/manifests/etcd.yaml
  • Off-cluster opslagbestemming voor snapshots (S3, GCS, NFS)

Waarom etcd-backups een securitykwestie zijn

etcd slaat de volledige clusterstatus op in binair protobuf. Daar zitten ook alle Kubernetes Secrets bij. Secrets zijn standaard base64-gecodeerd, niet versleuteld. Een etcd-snapshot is dus een leesbare dump van elk databasewachtwoord, elke API-key, elk TLS-certificaat en elke service account token in het cluster.

Daarom verdienen etcd-backupbestanden dezelfde securityclassificatie als de gevoeligste secrets in je cluster. Het is ook de reden dat dit artikel in de security-subcategorie staat, niet in een generieke operationele handleiding. Voor een vergelijking van tools die secrets helemaal buiten etcd houden, zie Kubernetes secrets management: Sealed Secrets, ESO en Vault vergeleken.

etcdctl vs etcdutl: welk tool waarvoor

Dit onderscheid zorgt regelmatig voor verwarring, want veel tutorials tonen nog verouderde commando's.

Tool Doel Draaiende etcd nodig?
etcdctl Dagelijks gebruik: key/value-beheer, member list, health checks, snapshot save Ja
etcdutl Offline beheer: snapshot restore, snapshot status, defrag, datamigratie Nee

etcdctl snapshot restore is deprecated sinds etcd v3.5 en verwijderd in etcd v3.6. Gebruik altijd etcdutl snapshot restore. Hetzelfde geldt voor etcdctl snapshot status, dat ook is verwijderd in v3.6. Gebruik etcdutl snapshot status.

Alle restore-commando's in dit artikel gebruiken etcdutl. Als je een tutorial tegenkomt die etcdctl snapshot restore voorschrijft, is die verouderd.

Een snapshot maken

Een snapshot legt elk Kubernetes API-object vast op een specifiek moment: Pods, Deployments, Services, ConfigMaps, Secrets, RBAC-regels, CRDs en cluster membership metadata. Wat er niet in zit: persistent volume data, container images en systeemlogbestanden.

# Maak een snapshot met timestamp (kubeadm-certificaatpaden)
sudo etcdctl \
  --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 \
  snapshot save /opt/backup/etcd-$(date +%Y%m%d-%H%M%S).db

Het commando maakt verbinding met de draaiende etcd-instantie en schrijft een consistente snapshot naar disk. Zelfs op grote clusters duurt dit minder dan 10 minuten.

Een snapshot verifieren

Verifieer elke snapshot direct na het aanmaken. Een backup waarvan je tijdens een ramp ontdekt dat die corrupt is, is geen backup.

etcdutl snapshot status /opt/backup/etcd-20260409-140000.db --write-out=table

De output bevat een hash, revisienummer, totaal aantal keys en totale grootte. Een key count groter dan nul en een geldige hash bevestigen dat de snapshot gezond is.

Backups automatiseren met een CronJob

Handmatige snapshots zijn een begin. Geautomatiseerde, terugkerende snapshots die off-cluster worden opgeslagen, zijn wat je overeind houdt als het misgaat.

Aanbevolen intervallen:

Clustertype Interval
Productie, hoge wijzigingsfrequentie Elke 1 tot 4 uur
Productie, normaal Elke 6 uur
Staging of development Dagelijks

Deze CronJob draait op een control plane node, maakt elke 6 uur een snapshot, verifieert die en ruimt bestanden ouder dan 7 dagen op:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: etcd-backup
  namespace: kube-system
spec:
  schedule: "0 */6 * * *"   # elke 6 uur
  jobTemplate:
    spec:
      template:
        spec:
          hostNetwork: true  # nodig om etcd op 127.0.0.1:2379 te bereiken
          tolerations:
            - key: node-role.kubernetes.io/control-plane
              operator: Exists
              effect: NoSchedule
          nodeSelector:
            node-role.kubernetes.io/control-plane: ""
          containers:
            - name: etcd-backup
              image: bitnami/etcd:3.5  # pin op de etcd-versie van je cluster
              command:
                - /bin/sh
                - -c
                - |
                  TIMESTAMP=$(date +%Y%m%d-%H%M%S)
                  BACKUP_FILE=/backup/etcd-${TIMESTAMP}.db
                  ETCDCTL_API=3 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
                  etcdutl snapshot status ${BACKUP_FILE} --write-out=table
                  find /backup -name "etcd-*.db" -mtime +7 -delete
              volumeMounts:
                - name: etcd-certs
                  mountPath: /etc/kubernetes/pki/etcd
                  readOnly: true
                - name: backup-storage
                  mountPath: /backup
          volumes:
            - name: etcd-certs
              hostPath:
                path: /etc/kubernetes/pki/etcd
            - name: backup-storage
              persistentVolumeClaim:
                claimName: etcd-backup-pvc  # verwijs naar off-cluster opslag
          restartPolicy: OnFailure

Sla snapshots off-cluster op. Backups die alleen op de clusternodes staan, gaan verloren als het cluster verloren gaat. Push snapshots naar S3 (met SSE-KMS), GCS, Azure Blob of een NFS-server buiten het cluster. Versleutel voor of tijdens het uploaden: de snapshot bevat elk Secret in het cluster.

De adfinis/kubernetes-etcd-backup operator is een alternatief als je een dedicated controller prefereert boven een ruwe CronJob.

Restore: single-node control plane

Deze procedure is voor kubeadm-clusters met een enkele control plane node. Het doel: vervang de huidige etcd-datadirectory door de inhoud van de snapshot en herstart het control plane.

Stap 1: stop de API server en etcd.

Verplaats de static pod manifests uit kubelet's watch-directory. kubelet stopt de containers binnen 15 tot 30 seconden.

sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sudo mv /etc/kubernetes/manifests/etcd.yaml /tmp/

# Verifieer dat beide containers zijn gestopt
sudo crictl ps | grep -E 'etcd|apiserver'

Je zou geen output moeten zien. Als containers nog draaien, wacht even en controleer opnieuw.

Stap 2: maak een backup van de bestaande datadirectory.

sudo mv /var/lib/etcd /var/lib/etcd.bak

Bewaar deze tot je hebt bevestigd dat de restore is geslaagd.

Stap 3: herstel de snapshot.

sudo etcdutl snapshot restore /opt/backup/etcd-20260409-140000.db \
  --data-dir /var/lib/etcd \
  --bump-revision 1000000000 \
  --mark-compacted

De --bump-revision en --mark-compacted vlaggen worden uitgelegd in het revision-bump-probleem. Neem ze altijd mee bij Kubernetes-clusterrestores.

Stap 4: corrigeer bestandseigendom.

sudo chown -R etcd:etcd /var/lib/etcd

etcd draait als de etcd-gebruiker. Verkeerde eigendom voorkomt dat het opstart.

Stap 5: breng de static pods terug.

sudo mv /tmp/etcd.yaml /etc/kubernetes/manifests/
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/

Wacht 30 tot 60 seconden tot kubelet de containers opnieuw aanmaakt.

Stap 6: herstart kubelet en verifieer.

sudo systemctl restart kubelet

# Verifieer de clustergezondheid
kubectl get nodes
kubectl get pods --all-namespaces

# Verifieer etcd health
sudo etcdctl \
  --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 \
  endpoint health

Je weet dat de restore is geslaagd wanneer kubectl get nodes alle nodes als Ready toont en endpoint health rapporteert is healthy: true.

Veiligere variant: in plaats van /var/lib/etcd te overschrijven, restore je naar een nieuwe directory (bijvoorbeeld /var/lib/etcd-restored) en pas je volumes[etcd-data].hostPath.path aan in /tmp/etcd.yaml voordat je die terugplaatst. Zo overleeft de originele datadirectory als de restore mislukt.

Restore: multi-node HA cluster

Dit is het meest complexe scenario. Een multi-node restore maakt een nieuw logisch cluster aan met nieuwe member ID's en een nieuw cluster ID. Alle members moeten worden hersteld vanuit dezelfde snapshot voordat er ook maar een wordt gestart.

De volgorde is niet onderhandelbaar: stop alles, restore alles, start alles.

Als je een hersteld member start voordat de anderen klaar zijn, kan Raft geen quorum bereiken. Het cluster wordt niet gezond.

Stap 1: stop etcd en de API server op alle nodes.

Voer uit op elke control plane node:

sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sudo mv /etc/kubernetes/manifests/etcd.yaml /tmp/
sudo crictl ps | grep -E 'etcd|apiserver'  # bevestig dat ze gestopt zijn

Stap 2: distribueer de snapshot.

Kopieer hetzelfde snapshotbestand naar alle control plane nodes. Maak geen aparte snapshots op elke node.

scp /opt/backup/etcd-20260409-140000.db cp2.internal:/opt/backup/
scp /opt/backup/etcd-20260409-140000.db cp3.internal:/opt/backup/

Stap 3: restore op elke node met node-specifieke parameters.

Elke node gebruikt dezelfde snapshot en hetzelfde --initial-cluster-token, maar een eigen --name en --initial-advertise-peer-urls:

# Op cp1 (10.0.1.10):
sudo etcdutl snapshot restore /opt/backup/etcd-20260409-140000.db \
  --name m1 \
  --data-dir /var/lib/etcd \
  --initial-cluster m1=https://10.0.1.10:2380,m2=https://10.0.1.11:2380,m3=https://10.0.1.12:2380 \
  --initial-cluster-token etcd-cluster-restored \
  --initial-advertise-peer-urls https://10.0.1.10:2380 \
  --bump-revision 1000000000 \
  --mark-compacted

# Op cp2 (10.0.1.11):
sudo etcdutl snapshot restore /opt/backup/etcd-20260409-140000.db \
  --name m2 \
  --data-dir /var/lib/etcd \
  --initial-cluster m1=https://10.0.1.10:2380,m2=https://10.0.1.11:2380,m3=https://10.0.1.12:2380 \
  --initial-cluster-token etcd-cluster-restored \
  --initial-advertise-peer-urls https://10.0.1.11:2380 \
  --bump-revision 1000000000 \
  --mark-compacted

# Op cp3 (10.0.1.12):
sudo etcdutl snapshot restore /opt/backup/etcd-20260409-140000.db \
  --name m3 \
  --data-dir /var/lib/etcd \
  --initial-cluster m1=https://10.0.1.10:2380,m2=https://10.0.1.11:2380,m3=https://10.0.1.12:2380 \
  --initial-cluster-token etcd-cluster-restored \
  --initial-advertise-peer-urls https://10.0.1.12:2380 \
  --bump-revision 1000000000 \
  --mark-compacted

Het --initial-cluster-token moet anders zijn dan het token dat het oude cluster gebruikte. Dit voorkomt dat herstelde members per ongeluk communiceren met oude instanties die ergens nog draaien.

Stap 4: corrigeer eigendom op alle nodes.

sudo chown -R etcd:etcd /var/lib/etcd

Stap 5: start eerst etcd, dan de API servers.

Plaats het etcd-manifest terug op alle nodes. Wacht tot etcd quorum heeft (controleer logs met sudo crictl logs <etcd-container-id>). Plaats dan de API server manifests terug:

# Op alle nodes:
sudo mv /tmp/etcd.yaml /etc/kubernetes/manifests/

# Wacht op etcd-quorum, dan:
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/

Stap 6: herstart kubelet en verifieer.

sudo systemctl restart kubelet
kubectl get nodes
kubectl get pods --all-namespaces

sudo etcdctl \
  --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 \
  member list

Je weet dat het gelukt is wanneer member list alle drie de members toont en kubectl get nodes alle nodes als Ready rapporteert.

Het revision-bump-probleem

etcd revision is een monotoon stijgend getal. Kubernetes controllers en informers gebruiken het als resourceVersion om ordening en cacheversheid te bepalen. Na een restore valt de revision terug naar de waarde van de snapshot, die lager is dan wat controllers voor het laatst hebben gezien.

Het gevolg: verouderde watch-caches die denken dat ze up-to-date zijn, en fouten als etcdserver: mvcc: required revision has been compacted in de API server logs. De etcd-maintainers hebben twee vlaggen toegevoegd specifiek om dit op te lossen:

  • --bump-revision 1000000000 telt 1 miljard op bij de herstelde revision, waardoor die hoger wordt dan elke revision die controllers hebben gezien.
  • --mark-compacted markeert alle oude revisies als gecompacteerd. Dit dwingt elke openstaande watch om te stoppen en opnieuw te initialiseren vanuit de nieuwe staat, waardoor alle informer-caches worden geflusht.

De waarde 1.000.000.000 is een communityconventie gebaseerd op typische cluster revision rates. Het staat niet formeel in de etcd-docs, maar komt consistent voor in productie-restoreprocedures.

Beide vlaggen zijn beschikbaar op etcdutl snapshot restore in etcd v3.5 en later. Neem ze altijd mee bij het restoren van etcd voor een Kubernetes-cluster.

Snapshotbestanden beveiligen

Een etcd-snapshot is een volledige kopie van alle clusterstatus. Tenzij je encryption at rest hebt geconfigureerd via een EncryptionConfiguration-resource (AES-CBC, AES-GCM of een KMS-provider), is elk Secret leesbaar in het snapshotbestand.

Praktische regels:

  • Sla op op versleutelde opslag (S3 met SSE-KMS, een encrypted filesystem, of allebei).
  • Beperk toegang met IAM-policies, bestandspermissies of firewallregels.
  • Bewaar backupbestanden nooit alleen op de clusternodes.
  • Als je cluster een KMS-provider gebruikt voor encryption at rest, heb je toegang tot de KMS-sleutel nodig op het moment van restore. Raak je die sleutel kwijt, dan is elke snapshot onherstelbaar.

Voor achtergrond over waarom etcd secrets standaard onversleuteld opslaat en hoe workload identity het probleem omzeilt, zie Kubernetes workload identity.

Je backups testen

Een backup die je nooit hebt gerestored is een backup die je niet kunt vertrouwen.

Minimale testfrequentie:

  • Na elke backup: verifieer met etcdutl snapshot status. Een key count groter dan nul en een geldige hash bevestigen integriteit.
  • Elk kwartaal: volledige restore naar een staging- of wegwerpcluster vanuit een echte productiessnapshot. Doorloop de hele procedure, inclusief de health checks aan het eind.

Restore-verificatiechecklist:

  1. Snapshot restore is zonder fouten voltooid
  2. kubectl get nodes toont alle nodes als Ready
  3. kubectl get pods --all-namespaces toont pods in verwachte staat
  4. etcdctl endpoint health rapporteert healthy
  5. Applicatieniveau health checks slagen
  6. RBAC-regels en secrets zijn intact (steekproef op een bekend Secret)

Monitor CronJob-failures met Prometheus-alerts of door Kubernetes Job events in de gaten te houden. Een stilletjes falende backup-CronJob is erger dan helemaal geen automatisering, want het geeft vals vertrouwen.

Veelgemaakte fouten

Fout Gevolg Preventie
etcdctl snapshot restore gebruiken Faalt op etcd v3.6, deprecated op v3.5 Gebruik etcdutl snapshot restore
API server niet stoppen voor restore Risico op datacorruptie Stop eerst static pods
Backups alleen on-cluster opslaan Backup kwijt als cluster kwijt Off-cluster opslag (S3, NFS, GCS)
Snapshot niet verifieren Corrupt backup ontdekt tijdens ramp etcdutl snapshot status na elke backup
--bump-revision / --mark-compacted weglaten Verouderde watch-caches, controllerfouten Altijd meenemen bij Kubernetes-restores
Een HA member starten voor alle zijn hersteld Raft-quorum niet bereikt Stop alles, restore alles, start alles
Verschillende snapshots op verschillende nodes Split-brain, inconsistente staat Dezelfde snapshot naar alle nodes
Verkeerde bestandseigendom na restore etcd start niet op chown -R etcd:etcd /var/lib/etcd
kubelet en control plane niet herstarten Verouderde in-memory staat Herstart kubelet, verwijder scheduler/controller-manager pods indien nodig
Onversleutelde snapshots bewaren Elk Secret leesbaar in plaintext Versleutelde opslag + beperkte toegang

Wanneer escaleren

Als de restoreprocedure het probleem niet oplost, verzamel dan het volgende voordat je om hulp vraagt:

  • etcd-versie (etcdctl version)
  • Kubernetes-versie (kubectl version)
  • De exacte foutmelding van het restore-commando
  • etcd-containerlogs (sudo crictl logs <etcd-container-id>)
  • API server logs (sudo crictl logs <apiserver-container-id>)
  • Of het cluster encryption at rest gebruikt en welke KMS-provider
  • Of het een single-node of multi-node cluster is
  • De output van etcdctl member list en etcdctl endpoint status --write-out=table
  • Of je --bump-revision en --mark-compacted hebt gebruikt

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.