Inhoudsopgave
- Voordat je begint
- Waarom etcd-backups een securitykwestie zijn
- etcdctl vs etcdutl: welk tool waarvoor
- Een snapshot maken
- Een snapshot verifieren
- Backups automatiseren met een CronJob
- Restore: single-node control plane
- Restore: multi-node HA cluster
- Het revision-bump-probleem
- Snapshotbestanden beveiligen
- Je backups testen
- Veelgemaakte fouten
- Wanneer escaleren
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
etcdctlenetcdutlbinaries die overeenkomen met je draaiende etcd-versie (controleer metetcdctl 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
- CA:
- 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 1000000000telt 1 miljard op bij de herstelde revision, waardoor die hoger wordt dan elke revision die controllers hebben gezien.--mark-compactedmarkeert 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:
- Snapshot restore is zonder fouten voltooid
kubectl get nodestoont alle nodes als Readykubectl get pods --all-namespacestoont pods in verwachte staatetcdctl endpoint healthrapporteert healthy- Applicatieniveau health checks slagen
- 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 listenetcdctl endpoint status --write-out=table - Of je
--bump-revisionen--mark-compactedhebt gebruikt