<img src="https://secure.leadforensics.com/807076.png" style="display:none;">
Zu Content springen
Deutsch
  • Es gibt keine Vorschläge, da das Suchfeld leer ist.

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

  1. Kubernetes-Cluster: Ein laufender Xelon Kubernetes Cluster und eine kubeconfig mit Zugriff darauf. Siehe "How to: create Kubernetes Clusters."

  2. 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.

  3. 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.

  4. 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.

Erstellen Sie das Secret mit dem Repository-Passwort:
kubectl create secret generic backup-repo \
  --namespace <namespace> \
  --from-literal=password='<repository-password>'
Erstellen Sie das Secret mit Ihren Object-Storage-Zugangsdaten:
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).

Erstellen Sie backup.yaml:
apiVersion: k8up.io/v1
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
Wenden Sie sie an:
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 Annotation k8up.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 (hier pg_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" (Format YYYY-MM-DD hh:mm:ss oder 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 denied fehl, setzen Sie fsGroup (und falls nötig runAsUser) 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 von pod-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.yaml
2. 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.

4. Ermitteln Sie die ID des neuen Snapshots. Sein Pfad ist /data/pod-1-storage:
kubectl get snapshots -o custom-columns=ID:.spec.id,DATE:.spec.date,PATHS:.spec.paths
5. Stoppen Sie pod-2. Sein RWO-Volume könnte sonst auf einem anderen Node eingebunden sein als der Restore-Pod:
kubectl delete pod pod-2
6. 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.yaml
kubectl get restore restore-pod-1-to-pod-2 -w
7. Starten Sie 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.