- Authors

- Name
- Phillip Pham
- @ddppham
Kubernetes Backup für Datenbanken: Velero, Postgres, MySQL
TL;DR
- Velero sichert Kubernetes-Ressourcen und Persistent Volumes. Beides ist notwendig, für eine Datenbank reicht keines allein.
- Ein Volume-Snapshot einer laufenden Postgres- oder MySQL-Instanz kann inkonsistent sein. Vor dem Backup einen Dump per Hook erzeugen.
- Backups ohne regelmäßige Restore-Tests sind wertlos. Mindestens monatlich in einem separaten Namespace prüfen.
- Kritische Datenbanken stündlich, alles andere täglich. Das Intervall folgt dem RPO, nicht dem Gefühl.
- Backup-Ziel: S3-kompatibler Speicher außerhalb des Clusters, sonst fällt die Sicherung mit dem Rechenzentrum aus.
Warum ein Volume-Backup die Datenbank nicht rettet
Ein Kubernetes-Cluster hat zwei Datenarten: den API-Zustand (Deployments, Services, Secrets, CRDs) und die Persistent Volumes. Viele Teams sichern nur eines und merken es beim Restore.
Für Postgres und MySQL kommt eine dritte Schicht dazu. Ein Dateisystem-Snapshot mitten in einem Write kann eine kaputte Datendatei liefern, die der Server nicht öffnet. Velero löst den Cluster-Teil: API-Export plus Volume über Kopia oder einen Storage-Snapshot. Den konsistenten Dump muss ein Pre-Hook erzeugen, bevor Velero das Volume liest.
Klassische VM-Snapshots helfen nicht. Ohne etcd-Zustand ist der Node nutzlos, und ohne Dump ist das Volume eine Wette.
Velero installieren
Über die CLI, mit S3-kompatiblem Backend:
curl -fsSL https://github.com/vmware-tanzu/velero/releases/download/v1.15.0/velero-v1.15.0-linux-amd64.tar.gz | tar xz
sudo mv velero-v1.15.0-linux-amd64/velero /usr/local/bin/
cat <<EOF > /tmp/credentials-velero
[default]
aws_access_key_id=IHRE_ACCESS_KEY
aws_secret_access_key=IHRE_SECRET_KEY
EOF
velero install \
--provider aws \
--plugins velero/velero-plugin-for-aws:v1.11.0 \
--bucket kubernetes-backups \
--secret-file /tmp/credentials-velero \
--backup-location-config region=eu-central-1,s3ForcePathStyle=true,s3Url=https://s3.eu-central-1.amazonaws.com \
--use-node-agent \
--default-volumes-to-fs-backup
kubectl get pods -n velero
velero version
--use-node-agent schaltet das Dateisystem-Backup ein. Das brauchen Volumes ohne nativen Snapshot. Für die Datenbank ist das der zweite Schritt, nach dem Dump.
Frequenz nach RPO
| Workload | RPO | Frequenz | Aufbewahrung |
|---|---|---|---|
| Datenbanken | 1 Stunde | stündlich | 7 Tage |
| Stateful Services | 4 Stunden | 4× täglich | 14 Tage |
| Stateless APIs | 24 Stunden | täglich | 7 Tage |
| Cluster-Config | 24 Stunden | täglich | 30 Tage |
velero schedule create production-hourly \
--schedule="0 * * * *" \
--include-namespaces production \
--ttl 168h
velero schedule create cluster-daily \
--schedule="0 2 * * *" \
--include-cluster-resources=true \
--ttl 720h
Datenbank-Backup: Dump vor dem Volume
Postgres, konsistenter Dump in ein Verzeichnis auf dem Volume, das Velero danach mitnimmt:
apiVersion: v1
kind: Pod
metadata:
name: postgres-0
annotations:
pre.hook.backup.velero.io/command: '["/bin/bash", "-c", "PGPASSWORD=$POSTGRES_PASSWORD pg_dump -U postgres -Fc -d myapp > /backup/dump.dump"]'
pre.hook.backup.velero.io/timeout: "120s"
MySQL analog:
annotations:
pre.hook.backup.velero.io/command: '["/bin/bash", "-c", "mysqldump -u root -p$MYSQL_ROOT_PASSWORD --single-transaction --all-databases > /backup/dump.sql"]'
pre.hook.backup.velero.io/timeout: "300s"
--single-transaction bei InnoDB, sonst sperrt der Dump die Tabellen. Den Dump nicht nach dem Backup löschen, bevor ein Restore ihn einmal benutzt hat. Ein Hook, der die Datei sofort entfernt, sichert eine leere Hülle, sobald Velero das Volume erst nach dem Post-Hook liest. Reihenfolge prüfen: Dump schreiben, Velero lesen, erst dann aufräumen.
Große Datenbanken sprengen das Hook-Timeout. Dann läuft der Dump als CronJob in ein Objektstorage, und Velero sichert nur noch Manifeste und das leere Claim. Zwei Sicherungen, eine davon ist die, die Sie zurückspielen.
Restore testen
velero restore create test-restore \
--from-backup production-hourly-20260210120000 \
--namespace-mappings production:restore-test
velero restore describe test-restore --details
kubectl get pods -n restore-test
Danach die Datenbank öffnen und eine Zeile lesen, die nach dem letzten Dump existiert haben muss. kubectl get pods sagt nur, dass der Container startet. Wenn der Restore länger dauert als das RTO, ist die Frequenz egal.
RPO, RTO, zweites Ziel
| Metrik | Bedeutung | Woran Sie es sehen |
|---|---|---|
| RPO | Wie viel Datenverlust Sie akzeptieren | Abstand zwischen letztem Dump und Ausfall |
| RTO | Wie lange der Dienst weg sein darf | Zeit bis die Anwendung wieder schreibt |
| Erfolgsquote | Ob der Job wirklich lief | Alert, nicht das Dashboard von letzter Woche |
Das Backup-Ziel darf nicht im selben Rechenzentrum sterben wie der Cluster. Ein Bucket in derselben Region für schnelle Restores, ein zweiter bei einem anderen Anbieter für den Ausfall der Region. Velero kann mehrere BackupStorageLocations. Eine reicht nicht.
Was Velero nicht ersetzt
| Velero | etcd-Snapshot | Dump (pg_dump, mysqldump) | |
|---|---|---|---|
| API-Objekte | ausgewählte Namespaces | gesamter Cluster-Zustand | nein |
| Volume-Dateien | ja, mit Node-Agent | nein | nein |
| Konsistente Datenbank | nur mit Hook | nein | ja |
| Managed Kubernetes | ja | der Anbieter sichert etcd | ja, Ihre Daten |
| Self-managed | ja | zusätzlich | ja |
Auf AKS, EKS und GKE brauchen Sie kein eigenes etcd-Backup. Den Datenbank-Dump brauchen Sie trotzdem. Auf kubeadm, k3s oder RKE2 beide: Velero für die Anwendung, etcd-Snapshot für den Worst Case der Control Plane.
Vier Fehler, die das Backup wertlos machen
- Nur Manifeste, keine Volumes. Ohne
--default-volumes-to-fs-backupfehlen die Dateien. - Kein Alert, wenn der Job fehlschlägt. Velero legt Metriken auf Port 8085. Ein stiller Fehler ist schlimmer als kein Backup.
- Restore nie geprobt. Das ist der teure Fall, und er fällt erst im Incident auf.
- Aufbewahrung kürzer als die Zeit, in der Sie einen korrupten Stand bemerken. Sieben Tage reichen nicht, wenn der Fehler in der Anwendung eine Woche unbemerkt bleibt.
Secrets liegen im Backup. Bucket nicht öffentlich, serverseitig verschlüsselt, Object Lock gegen Löschen. Der Velero-Account darf nur diesen Bucket.
Wenn Pods nicht starten
Operative Stoerungen (CrashLoop, ImagePull, Pending) klaeren Sie Schritt fuer Schritt im Guide Pod startet nicht – von dort verzweigen Exit-Codes, RBAC und Netzwerk-Checks.
Fazit
Kubernetes-Backup für eine Datenbank ist Dump plus Volume plus Restore-Probe. Velero übernimmt Cluster und Volume. Postgres und MySQL brauchen den Hook oder einen eigenen CronJob. Ohne monatlichen Test wissen Sie nicht, ob davon etwas zurückkommt.
Wenn die bestehende Installation geprüft werden soll: Kontakt. Die Härtung des Clusters daneben steht in der Security-Checkliste.
📖 Verwandte Artikel
Weitere interessante Beiträge zu ähnlichen Themen
PVC Pending: StorageClass und Provisioner
PVC Pending beheben: Default-StorageClass, Provisioner, WaitForFirstConsumer und Zonen. Wann Pending normal ist und wann der Claim nie auf Bound geht.
kubectl connection refused 6443 beheben
kubectl connection refused auf Port 6443: der API-Server antwortet nicht. kubeconfig, Kontext, Firewall, API-Prozess. Der Pod-Status kommt erst danach.
Error from server Forbidden: RBAC-Recht
kubectl Forbidden: Error from server (Forbidden) nennt User, Verb und Ressource. kubectl auth can-i zeigt das Recht, bevor jemand cluster-admin vergibt.
ContainerCreating hängt: Volume, Sandbox, CNI
ContainerCreating hängt: der Pod hat einen Node, der Container startet nicht. FailedMount, Pod-Sandbox und CNI stehen in den Events, nicht in den Logs.
CreateContainerConfigError: Secret und ConfigMap
CreateContainerConfigError: fehlendes Secret, falscher Key oder Namespace. Der Container startet nicht, logs --previous bleibt leer. Das Event nennt das Objekt.
Kubernetes DNS fehlgeschlagen: CoreDNS, ndots
Kubernetes DNS fehlgeschlagen: CoreDNS-Pods, das Service-ClusterIP und ndots:5. Wann ein Name im Cluster auflöst und wann die NetworkPolicy UDP 53 schluckt.
Exit Code 1: die erste Zeile im Previous-Log
Exit Code 1 auf Kubernetes: die Anwendung beendet sich selbst. kubectl logs --previous zeigt die Zeile. Speicher, Image-Pull und SIGKILL sind andere Codes.
Exit Code 143: SIGTERM und Graceful Shutdown
Exit Code 143 ist SIGTERM: 128 plus 15. Beim Rollout ist das normal. Im Loop fehlt ein Handler oder die Grace-Period ist kürzer als der Stopp der Anwendung.