Backup & Restore: K8up
In diesem Artikel erfahren Sie, wie Sie Ihre Persistent Volumes mit K8up sichern und wiederherstellen.
Intro
K8up (ausgesprochen wie «Ketchup») ist ein Kubernetes-Backup-Operator auf Basis von restic. Er sichert Ihre PersistentVolumeClaims (PVCs) und Applikations-Dumps in einen S3-kompatiblen Bucket wie Xelon Object Storage und stellt sie bei Bedarf wieder her.
K8up wird über Kubernetes-Ressourcen gesteuert:
- Backup: Führt ein einmaliges Backup der PVCs in einem Namespace aus.
- Schedule: Führt Backups, Checks und Prunes regelmässig aus.
- Check: Überprüft die Integrität des Backup-Repositorys.
- Prune: Entfernt alte Snapshots gemäss Ihrer Aufbewahrungsrichtlinie.
- Restore: Stellt einen Snapshot in ein PVC oder einen S3-Bucket wieder her.
- Archive: Kopiert die neuesten Snapshots in einen zweiten Bucket, zum Beispiel für die Langzeitaufbewahrung.
Diese Anleitung basiert auf K8up v2.16 (Helm-Chart 4.10.x). Die offizielle Dokumentation finden Sie unter docs.k8up.io.
Voraussetzungen
-
Kubernetes-Cluster: Ein laufender Xelon Kubernetes Cluster und eine kubeconfig mit Zugriff darauf. Siehe "How to: create Kubernetes Clusters."
-
CLI-Tools: `kubectl` und `helm` (v3) sind auf Ihrem Rechner installiert. Die restic CLI ist optional. Wir empfehlen sie aber, um Backups manuell zu durchsuchen.
-
Object-Storage-Bucket: Ein Bucket in Xelon Object Storage (zum Beispiel `k8up`) und ein Access-Key/Secret-Key-Paar mit Lese- und Schreibrechten darauf. Wir empfehlen ein eigenes Schlüsselpaar, das ausschliesslich für Backups verwendet wird.
-
Repository-Passwort: Ein starkes Passwort für das restic-Repository. K8up verschlüsselt alle Backups mit diesem Passwort.
Bitte beachten Sie: Wenn Sie das Repository-Passwort verlieren, können Ihre Backups nicht wiederhergestellt werden. Bewahren Sie es in einem Passwort-Manager ausserhalb des Clusters auf.
K8up-Installation
1. Fügen Sie das K8up-Helm-Repository hinzu:
helm repo add k8up-io https://k8up-io.github.io/k8up
helm repo update
2. Erstellen Sie eine `values.yaml` mit Ihren Einstellungen:
k8up:
# Cron-Schedules werden in dieser Zeitzone interpretiert (Standard: Zeitzone des Clusters, meist UTC)
timezone: Europe/Zurich
# Auf true setzen, wenn nur PVCs mit der Annotation k8up.io/backup: "true" gesichert werden sollen
skipWithoutAnnotation: false
3. Installieren Sie die CRDs. Sie sind im Helm-Chart enthalten, Helm installiert CRDs aber nur einmal und aktualisiert sie nie. Deshalb empfehlen wir, sie bei jeder Installation und jedem Upgrade explizit anzuwenden:
kubectl apply --server-side -f https://github.com/k8up-io/k8up/releases/download/v2.16.1/k8up-crd.yaml
4. Installieren oder aktualisieren Sie den K8up-Operator:
helm upgrade --install k8up k8up-io/k8up \
--namespace k8up --create-namespace \
--version 4.10.1 \
-f values.yaml
Das Flag --version fixiert das Chart. So verwendet jede Installation Ihres Clusters dieselbe K8up-Version. Aktualisieren Sie bei einem späteren Upgrade die CRD-URL und die Chart-Version gemeinsam und prüfen Sie die Release Notes auf Breaking Changes.
Überprüfen der Installation
kubectl get pods -n k8up
kubectl get crds | grep k8up.io
Der Operator-Pod sollte den Status Running haben und die K8up-CRDs (backups.k8up.io, schedules.k8up.io, restores.k8up.io, …) sollten aufgelistet werden.
Backend konfigurieren
Jede K8up-Ressource hat einen backend-Block. Er legt fest, wo das restic-Repository liegt und wie K8up darauf zugreift. Die Zugangsdaten werden aus Kubernetes Secrets referenziert.
Gut zu wissen: K8up-Ressourcen arbeiten pro Namespace. Die Secrets, das Backup bzw. der Schedule und die zu sichernden PVCs müssen sich alle im selben Namespace wie Ihre Applikation befinden und nicht im Namespace k8up.
kubectl create secret generic backup-repo \Erstellen Sie das Secret mit Ihren Object-Storage-Zugangsdaten:
--namespace <namespace> \
--from-literal=password='<repository-password>'
kubectl create secret generic backup-credentials \
--namespace <namespace> \
--from-literal=access-key='<access-key>' \
--from-literal=secret-key='<secret-key>'
Best Practice: Committen Sie Secrets nicht als unverschlüsseltes YAML in Git. Verwenden Sie ein Tool wie Sealed Secrets, SOPS oder den External Secrets Operator, insbesondere wenn Sie mit ArgoCD deployen.
Der backend-Block, der in allen folgenden Beispielen verwendet wird, sieht so aus:
backend:
repoPasswordSecretRef:
name: backup-repo
key: password
s3:
endpoint: https://zh1-s3.xelon.ch
bucket: k8up/backup
accessKeyIDSecretRef:
name: backup-credentials
key: access-key
secretAccessKeySecretRef:
name: backup-credentials
key: secret-key
Der Wert bucket kann einen Pfad enthalten (<bucket>/<folder>). So können Sie mehrere Repositorys in einem Bucket ablegen, zum Beispiel einen Ordner pro Cluster oder Namespace.
Ihr erstes Backup
Eine Backup-Ressource startet sofort ein Backup. Sie sichert alle PVCs in ihrem Namespace (siehe Volumes auswählen).
backup.yaml:apiVersion: k8up.io/v1Wenden Sie sie an:
kind: Backup
metadata:
name: backup-test
namespace: <namespace>
spec:
failedJobsHistoryLimit: 2
successfulJobsHistoryLimit: 2
backend:
repoPasswordSecretRef:
name: backup-repo
key: password
s3:
endpoint: https://zh1-s3.xelon.ch
bucket: k8up/backup
accessKeyIDSecretRef:
name: backup-credentials
key: access-key
secretAccessKeySecretRef:
name: backup-credentials
key: secret-key
kubectl apply -f backup.yaml
K8up startet einen Backup-Job, der Ihre PVCs einbindet und deren Inhalt in den Bucket hochlädt. Beim ersten Lauf initialisiert K8up das restic-Repository automatisch.
Überprüfen des Backups
kubectl get backups -n <namespace>
kubectl get pods -n <namespace>
kubectl logs -n <namespace> -l k8up.io/type=backup
kubectl get snapshots -n <namespace>
Das Backup ist abgeschlossen, sobald sein Status Completed anzeigt. kubectl get snapshots listet einen Snapshot pro gesichertem PVC auf. Für einen Restore benötigen Sie die Snapshot-ID.
Geplante Backups
Für den produktiven Einsatz definieren Sie anstelle einzelner Backups einen Schedule. Ein Schedule erstellt Backup-, Check- und Prune-Jobs automatisch.
apiVersion: k8up.io/v1
kind: Schedule
metadata:
name: schedule
namespace: <namespace>
spec:
backend:
repoPasswordSecretRef:
name: backup-repo
key: password
s3:
endpoint: https://zh1-s3.xelon.ch
bucket: k8up/backup
accessKeyIDSecretRef:
name: backup-credentials
key: access-key
secretAccessKeySecretRef:
name: backup-credentials
key: secret-key
backup:
schedule: '@daily-random'
failedJobsHistoryLimit: 2
successfulJobsHistoryLimit: 2
check:
schedule: '@weekly-random'
prune:
schedule: '@weekly-random'
retention:
keepLast: 5
keepDaily: 14
keepWeekly: 8
keepMonthly: 6
- backup: Wie oft Ihre Daten gesichert werden. Verwenden Sie die Standard-Cron-Syntax (z. B.
'0 */6 * * *', siehe crontab.guru) oder einen zufälligen Wert. - check: Überprüft das Repository. Einmal pro Woche reicht in der Regel aus.
- prune: Entfernt Snapshots ausserhalb der
retention-Richtlinie. Im Beispiel oben behält K8up die letzten fünf Snapshots, einen pro Tag für 14 Tage, einen pro Woche für acht Wochen und einen pro Monat für sechs Monate.
Gut zu wissen: Bei zufälligen Schedules wie @hourly-random, @daily-random oder @weekly-random wählt K8up für Sie einen festen Zeitpunkt. Dieser wird aus dem Namespace und dem Namen des Schedules abgeleitet und bleibt deshalb gleich, wenn Sie den Schedule neu erstellen. So verteilt sich die Last auf Ihrem Cluster und im Object Storage. Ausserdem sperren sich Backup-, Check- und Prune-Jobs nicht gegenseitig das Repository.
Volumes auswählen
Standardmässig sichert K8up jedes PVC im Namespace des Backups bzw. Schedules. Sie können dies auf verschiedene Arten ändern:
1. Ein PVC ausschliessen: Fügen Sie die Annotationk8up.io/backup: "false" hinzu, zum Beispiel bei PVCs, die nur Caches oder Restore-Ziele enthalten.apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: cache
annotations:
k8up.io/backup: "false"
2. Nur Opt-in: Setzen Sie k8up.skipWithoutAnnotation: true in Ihren Helm-Values. K8up sichert dann nur noch PVCs mit k8up.io/backup: "true".
3. Label-Selektoren: Beschränken Sie ein Backup oder einen Schedule auf PVCs mit bestimmten Labels:
spec:
labelSelectors:
- matchLabels:
app: my-app
Bitte beachten Sie: Bei ReadWriteOnce-Volumes (RWO) startet K8up den Backup-Pod auf dem Node, auf dem das Volume gerade eingebunden ist. Das Volume muss deshalb während des Backups von einem laufenden Pod verwendet werden. ReadWriteMany-Volumes (RWX) können von jedem Node aus gesichert werden.
Applikationsbewusste Backups
Ein Backup auf Dateiebene einer laufenden Datenbank kann inkonsistent sein. Für Datenbanken empfehlen wir deshalb, K8up stattdessen einen Dump erstellen zu lassen. Fügen Sie dazu dem Pod-Template die Annotation k8up.io/backupcommand hinzu. K8up führt den Befehl im Pod aus und speichert dessen Ausgabe (stdout) im Repository.
Beispiel für PostgreSQL:
template:
metadata:
labels:
app: postgresql
annotations:
k8up.io/backupcommand: sh -c 'PGDATABASE="$POSTGRES_DB" PGUSER="$POSTGRES_USER" PGPASSWORD="$POSTGRES_PASSWORD" pg_dump --clean'
k8up.io/file-extension: .sql
k8up.io/backupcommand: Der Befehl, der den Dump nach stdout schreibt. Das Tool (hierpg_dump) muss im Container-Image vorhanden sein.k8up.io/file-extension: Die Dateiendung des Dumps im Snapshot.k8up.io/backupcommand-container: Optional. Gibt den Container an, in dem der Befehl ausgeführt wird, falls der Pod mehrere Container hat.
Damit die Datenbankdateien nicht doppelt gesichert werden, versehen Sie das PVC der Datenbank mit der Annotation k8up.io/backup: "false".
Bitte beachten Sie: Dumps können nicht mit einer Restore-Ressource wiederhergestellt werden. Verwenden Sie die restic CLI (restic dump, siehe Manueller Restore mit restic) und importieren Sie den Dump in Ihre Datenbank.
Restore
1. Listen Sie die verfügbaren Snapshots auf und wählen Sie die ID des Snapshots, den Sie wiederherstellen möchten:kubectl get snapshots -n <namespace> -o custom-columns=ID:.spec.id,DATE:.spec.date,PATHS:.spec.paths
Der Pfad zeigt, zu welchem PVC ein Snapshot gehört (/data/<pvc-name>).
2. Wählen Sie unten eine Restore-Methode.
Restore in ein PVC
Das Ziel-PVC muss existieren, bevor Sie den Restore starten. Handelt es sich um ein RWO-Volume, stellen Sie sicher, dass es von keinem Pod auf einem anderen Node verwendet wird.
apiVersion: k8up.io/v1
kind: Restore
metadata:
name: restore-to-pvc
namespace: <namespace>
spec:
snapshot: <snapshot-id>
podSecurityContext:
fsGroup: 65532
fsGroupChangePolicy: OnRootMismatch
restoreMethod:
folder:
claimName: <target-pvc>
backend:
# derselbe backend-Block wie in Ihrem Backup bzw. Schedule
Der Inhalt des Snapshots wird in das Stammverzeichnis des Ziel-PVCs geschrieben.
- snapshot: Die Snapshot-ID. Alternativ können Sie mit
restoreTimeFilter: "2026-11-04"(FormatYYYY-MM-DD hh:mm:ssoder ein beliebiger Anfang davon) den neuesten Snapshot wiederherstellen, der zu diesem Zeitpunkt passt. Passt kein Snapshot, verwendet K8up ohne Warnung den neuesten Snapshot. - podSecurityContext: Schlägt der Restore mit
permission deniedfehl, setzen SiefsGroup(und falls nötigrunAsUser) auf den Benutzer, unter dem Ihre Applikation läuft. Führen Sie den Restore nur als root aus, wenn Ihre Applikation dies erfordert.
Best Practice: Stellen Sie zuerst in ein neues PVC wieder her und prüfen Sie die Daten, bevor Sie ein produktives Volume ersetzen. Versehen Sie das Restore-PVC mit der Annotation k8up.io/backup: "false", damit es nicht selbst gesichert wird.
Restore nach S3
Statt in ein PVC können Sie einen Snapshot auch als Dateien in einen anderen Bucket wiederherstellen, zum Beispiel um die Daten weiterzugeben oder lokal zu prüfen:
apiVersion: k8up.io/v1
kind: Restore
metadata:
name: restore-to-s3
namespace: <namespace>
spec:
snapshot: <snapshot-id>
restoreMethod:
s3:
endpoint: https://zh1-s3.xelon.ch
bucket: k8up-restore
accessKeyIDSecretRef:
name: restore-credentials
key: access-key
secretAccessKeySecretRef:
name: restore-credentials
key: secret-key
backend:
# derselbe backend-Block wie in Ihrem Backup bzw. Schedule
Manueller Restore mit restic
Mit der restic CLI können Sie Backups direkt von Ihrem Rechner aus durchsuchen und wiederherstellen:
export RESTIC_REPOSITORY=s3:https://zh1-s3.xelon.ch/k8up/backup
export RESTIC_PASSWORD='<repository-password>'
export AWS_ACCESS_KEY_ID='<access-key>'
export AWS_SECRET_ACCESS_KEY='<secret-key>'
restic snapshots # list snapshots
restic restore <snapshot-id> --target ./restore # restore into a local folder
restic dump <snapshot-id> /<namespace>-<pod>-<container>.sql > dump.sql # extract an application dump
restic mount ./mnt # browse all snapshots as a folder
Mit restic ls <snapshot-id> sehen Sie die genauen Dateinamen in einem Snapshot.
Beispiel: Pod 1 sichern und in Pod 2 wiederherstellen
Diese Anleitung kopiert die Daten aus dem Volume eines Pods in das Volume eines anderen Pods. Sie eignet sich auch gut, um Ihren Restore-Prozess zu testen.
1. Erstellen Sie die beiden Pods mit je einem eigenen PVC. Versehen Sie das PVC vonpod-1 mit einem Label, damit das Backup es auswählen kann, und schliessen Sie das PVC von pod-2 vom Backup aus:apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pod-1-storage
labels:
app: pod-1
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: pod-1
spec:
containers:
- name: pod-1
image: alpine:3.20
command: ["/bin/sh", "-c", "sleep infinity"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: pod-1-storage
pod-2 ist gleich aufgebaut, mit dem Namen pod-2, dem PVC pod-2-storage und der Annotation k8up.io/backup: "false" auf seinem PVC.
kubectl apply -f pod1.yaml -f pod2.yaml2. Schreiben Sie Testdaten in das Volume von
pod-1:kubectl exec pod-1 -- sh -c 'echo "hello from pod-1" > /data/test.txt'3. Sichern Sie nur das Volume von
pod-1:apiVersion: k8up.io/v1
kind: Backup
metadata:
name: backup-pod-1
spec:
labelSelectors:
- matchLabels:
app: pod-1
backend:
# derselbe backend-Block wie oben
kubectl apply -f backup-pod1.yaml
kubectl get backup backup-pod-1 -w
Warten Sie, bis das Backup den Status Completed hat.
/data/pod-1-storage:kubectl get snapshots -o custom-columns=ID:.spec.id,DATE:.spec.date,PATHS:.spec.paths5. Stoppen Sie
pod-2. Sein RWO-Volume könnte sonst auf einem anderen Node eingebunden sein als der Restore-Pod:kubectl delete pod pod-26. Stellen Sie den Snapshot in
pod-2-storage wieder her:apiVersion: k8up.io/v1
kind: Restore
metadata:
name: restore-pod-1-to-pod-2
spec:
snapshot: <snapshot-id>
podSecurityContext:
fsGroup: 65532
fsGroupChangePolicy: OnRootMismatch
restoreMethod:
folder:
claimName: pod-2-storage
backend:
# derselbe backend-Block wie oben
kubectl apply -f restore-pod1-to-pod2.yaml7. Starten Sie
kubectl get restore restore-pod-1-to-pod-2 -w
pod-2 wieder und prüfen Sie die Daten:kubectl apply -f pod2.yaml
kubectl exec pod-2 -- cat /data/test.txt
Sie sollten hello from pod-1 sehen.
Archive
Ein Archive stellt den neuesten Snapshot jedes PVCs aus Ihrem Backup-Repository in einen zweiten Bucket wieder her. So erhalten Sie eine Kopie Ihrer Daten an einem anderen Standort oder für die Langzeitaufbewahrung.
apiVersion: k8up.io/v1
kind: Archive
metadata:
name: archive
namespace: <namespace>
spec:
# «schedule» weglassen, um das Archive einmalig auszuführen
schedule: '@monthly-random'
# Quelle: Ihr bestehendes Backup-Repository
backend:
repoPasswordSecretRef:
name: backup-repo
key: password
s3:
endpoint: https://zh1-s3.xelon.ch
bucket: k8up/backup
accessKeyIDSecretRef:
name: backup-credentials
key: access-key
secretAccessKeySecretRef:
name: backup-credentials
key: secret-key
# Ziel: hierhin werden die archivierten Dateien geschrieben
restoreMethod:
s3:
endpoint: <archive-endpoint>
bucket: k8up-archive
accessKeyIDSecretRef:
name: archive-credentials
key: access-key
secretAccessKeySecretRef:
name: archive-credentials
key: secret-key
Best Practice: Verwenden Sie für den Archive-Bucket eine andere Region (oder einen anderen Anbieter) und separate Zugangsdaten. Ein einzelner kompromittierter Schlüssel kann dann nicht beide Kopien löschen.
Monitoring
K8up stellt Prometheus-Metriken bereit, zum Beispiel die Anzahl fehlgeschlagener Backups pro Namespace. Wenn Sie den Prometheus Operator einsetzen, aktivieren Sie Folgendes in Ihrer values.yaml:
metrics:
serviceMonitor:
enabled: true
prometheusRule:
enabled: true
Die PrometheusRule enthält Standard-Alerts für fehlgeschlagene Jobs. Wir empfehlen, mindestens bei fehlgeschlagenen Backups zu alarmieren. Auf ein Backup, das Sie nicht überwachen, können Sie sich nicht verlassen.
Best Practices
- Bewahren Sie das Repository-Passwort ausserhalb des Clusters auf. Ohne das Passwort sind Ihre Backups wertlos.
- Befolgen Sie die 3-2-1-Regel. Bewahren Sie mindestens eine Kopie Ihrer Daten an einem anderen Standort auf, zum Beispiel mit einem Archive.
- Verwenden Sie Schlüssel mit minimalen Rechten. Nutzen Sie separate Object-Storage-Schlüssel für Backups, Restores und Archive.
- Testen Sie Ihre Restores regelmässig, zum Beispiel mit der Anleitung oben.
- Verwenden Sie applikationsbewusste Backups für Datenbanken statt Datei-Backups.
- Fixieren Sie die Versionen. Fixieren Sie das Helm-Chart und die CRD-Version und aktualisieren Sie beide gemeinsam.
- Halten Sie Secrets aus Git heraus oder verschlüsseln Sie sie.
Brauchen Sie Hilfe?
Wenn Sie Fragen zu K8up auf Xelon Kubernetes haben oder Unterstützung benötigen, kontaktieren Sie unser Support-Team.