secure k8 s

Backup und Restore auf Kubernetes

Summary: Konkrete Backup-/Restore-Strategie, wenn OpenBao im Helm-Chart auf Kubernetes läuft. Drei-Schichten-Modell (Raft-Snapshot + Seal-Material + Cluster-Manifests), volle Konfiguration des snapshotAgent-CronJobs aus dem Chart, Alternative als eigener CronJob, Restore-Prozedur im laufenden Cluster, Sandbox-Test-Restore und Monitoring. Komplementär zur konzeptionellen backups-Seite, die das Warum und Was beschreibt — diese Seite ist das K8s-spezifische Wie.

Sources: raw/docs/platform/k8s/helm/examples/snapshot-cronjob.md, raw/docs/commands/operator/raft.md, raw/docs/concepts/storage.md, raw/docs/configuration/storage/raft.md. Velero-, kube-state-metrics- und SealedSecrets-Schritte folgen den jeweiligen Upstream-Docs.

Last updated: 2026-05-20


0 — Die Drei-Schichten-Regel

Ein OpenBao-Backup auf Kubernetes besteht aus drei Artefakten. Wer nur eines davon sichert, hat im Ernstfall nichts.

SchichtWasWo speichernVerlustfolge
1 — Raft-SnapshotBarrier-verschlüsselte Cluster-Daten zu einem konsistenten Punkt-in-ZeitOff-Site-Bucket (S3 o. ä.)Alle Secrets, Policies, Tokens, Auth-Roles weg
2 — Seal-MaterialAuto-Unseal-Key (Cloud-KMS-Ref / Transit-Seal-Token) oder Shamir-Unseal-Keys + Recovery-KeysCloud-KMS (cross-region), HSM oder verschlüsseltes Offline-MediumSnapshot existiert, kann aber nie wieder entschlüsselt werden
3 — Cluster-Manifestsvalues-ha.yaml, cert-manager-Issuer, Namespace, ServiceAccount-Bindings, NetworkPoliciesGit-RepoOhne die Manifeste muss man die Cluster-Topologie aus dem Gedächtnis rekonstruieren

Hintergrund zur Trennung Daten vs. Seal: backups § “Was gesichert werden muss” und seal-unseal.

K8s-spezifischer Punkt zu Schicht 3: Auf VMs ist die Server-Konfig oft Teil eines Configuration-Management-Systems (Ansible, Salt). Auf K8s ist sie in der values-ha.yaml und in zusätzlichen Manifesten — beides muss zwingend in einem Git-Repo liegen, sonst geht beim Disaster-Restore die halbe Architektur verloren.

1 — Schicht 1, Variante A: Helm-Chart-snapshotAgent (empfohlen)

Seit Helm-Chart-Version 0.22.0 bringt der Chart einen fertigen CronJob mit (source: raw/docs/platform/k8s/helm/examples/snapshot-cronjob.md). Das ist der Default-Pfad — keine eigene Bastelei, weniger Wartung.

1.1 — S3-Credentials als Secret vorbereiten

Der Job lädt Snapshots per S3-API hoch. Credentials kommen aus einem Secret im selben Namespace wie OpenBao:

kubectl -n openbao create secret generic openbao-s3-credentials \
  --from-literal=AWS_ACCESS_KEY_ID=<key-id> \
  --from-literal=AWS_SECRET_ACCESS_KEY=<secret>

Wichtig: Dieses Secret ist sensibel. In Production statt kubectl create secret lieber SealedSecrets oder ExternalSecrets nehmen — damit landet das Klartext-Geheimnis nie im Git-Repo. Schicht 3 (Manifests in Git) ist nur sinnvoll, wenn die Secrets darin verschlüsselt sind.

IAM-Empfehlung für den S3-Bucket-Schlüssel:

  • Nur PutObject + ListBucket (kein DeleteObject — schützt vor Ransomware, die alte Snapshots überschreibt)
  • Bucket-Policy mit aws:SecureTransport=true
  • Bucket-Verschlüsselung at-rest mit eigenem KMS-Key
  • Object-Lock mit Compliance-Mode für Snapshots, die mind. 30 Tage unveränderlich bleiben sollen
  • Lifecycle-Rule: Standard → Glacier nach 30 Tagen → Löschung nach 365 Tagen

1.2 — OpenBao-Policy und Kubernetes-Auth-Role

Der Snapshot-Endpoint liegt auf sys/storage/raft/snapshot und braucht nur read:

kubectl -n openbao exec -ti openbao-0 -- /bin/sh -c '
  cat <<EOF | bao policy write snapshot -
path "sys/storage/raft/snapshot" {
  capabilities = ["read"]
}
EOF
'

Kubernetes-Auth-Method aktivieren (falls noch nicht):

kubectl -n openbao exec -ti openbao-0 -- bao auth enable kubernetes

Eine Auth-Role, die ServiceAccount + Namespace des CronJobs an diese Policy bindet:

kubectl -n openbao exec -ti openbao-0 -- bao write auth/kubernetes/role/snapshot \
  bound_service_account_names=openbao-snapshot-agent \
  bound_service_account_namespaces=openbao \
  policies=snapshot \
  ttl=1h

(source: raw/docs/platform/k8s/helm/examples/snapshot-cronjob.md)

Warum ttl=1h: der Snapshot-Job läuft nur wenige Sekunden. Eine kurze TTL minimiert den Schaden, falls das Token leakt.

1.3 — values-ha.yaml um snapshotAgent erweitern

snapshotAgent:
  enabled: true
  schedule: "*/15 * * * *"
  s3CredentialsSecret: "openbao-s3-credentials"
  config:
    s3Host: "s3.eu-central-1.amazonaws.com"
    s3Bucket: "openbao-snapshots"
    s3Uri: "s3://openbao-snapshots"
    s3ExpireDays: "14"
    baoAuthPath: "kubernetes"
    baoRole: "snapshot"

(angelehnt an: raw/docs/platform/k8s/helm/examples/snapshot-cronjob.md)

Begründungen:

  • schedule: "*/15 * * * *": alle 15 Minuten. Faustregel: RPO = Schedule-Intervall. Wer keine 15 Minuten verlieren darf, geht auf */5. Wer Cluster mit hoher Schreiblast hat, kann */30 oder 0 * * * * nehmen — Snapshots sind günstig, aber jeder produziert ein Bucket-Object und einen Job-Pod.
  • s3ExpireDays: "14": 14 Tage Retention auf dem Bucket. Das ist die kurzfristige Retention. Für mittel-/langfristige Retention (z. B. monatliche Snapshots ein Jahr lang) eine zusätzliche Bucket-Lifecycle-Rule auf S3-Seite bauen, die ausgewählte Snapshots in Glacier kopiert.
  • baoAuthPath + baoRole: zeigen auf die Auth-Method und Role aus § 1.2.

helm upgrade ausführen:

helm upgrade openbao openbao/openbao -n openbao -f values-ha.yaml

1.4 — Verifikation

CronJob existiert?

kubectl -n openbao get cronjob

Erwartet: openbao-snapshot-agent mit Schedule */15 * * * *.

Erster Lauf-Trigger (ohne 15 Min zu warten):

kubectl -n openbao create job --from=cronjob/openbao-snapshot-agent first-snapshot-test
kubectl -n openbao logs job/first-snapshot-test -f

Erfolgsmeldung im Log: typisch snapshot saved to s3://openbao-snapshots/openbao-snapshot-<timestamp>.snap o. ä.

Im Bucket nachsehen:

aws s3 ls s3://openbao-snapshots/

Erwartet: eine .snap-Datei im niedrigen 10-MB-Bereich (für einen frischen Cluster) bis hin zu mehreren 100 MB (für Cluster mit vielen Secrets/Policies).

2 — Schicht 1, Variante B: eigener CronJob (für Sonderfälle)

Manchmal reicht der eingebaute snapshotAgent nicht — etwa wenn:

  • Snapshots auf einen Nicht-S3-Endpoint (NFS, GCS, on-prem Object Storage ohne S3-API) sollen.
  • Zusätzliche Verschlüsselung mit age oder gpg vor dem Upload nötig ist (Compliance).
  • Snapshots in mehrere Buckets parallel gehen sollen.
  • Der OpenBao-Auth-Pfad nicht kubernetes ist (z. B. AppRole für besseren Trail).

Dann legt man einen eigenen CronJob an:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: openbao-snapshot
  namespace: openbao
spec:
  schedule: "*/15 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          serviceAccountName: openbao-snapshot-agent
          restartPolicy: OnFailure
          containers:
            - name: snapshot
              image: openbao/openbao:2.0.1
              env:
                - name: BAO_ADDR
                  value: https://openbao-active.openbao.svc:8200
                - name: BAO_CACERT
                  value: /tls/ca.crt
              command:
                - /bin/sh
                - -c
                - |
                  set -euo pipefail
                  JWT=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
                  TOKEN=$(bao write -field=token auth/kubernetes/login \
                    role=snapshot jwt="$JWT")
                  export BAO_TOKEN="$TOKEN"

                  TIMESTAMP=$(date -u +%Y%m%dT%H%M%SZ)
                  SNAP=/tmp/snapshot-${TIMESTAMP}.snap
                  bao operator raft snapshot save "$SNAP"

                  aws s3 cp "$SNAP" \
                    "s3://openbao-snapshots/openbao-snapshot-${TIMESTAMP}.snap" \
                    --sse aws:kms --sse-kms-key-id alias/openbao-backup
              envFrom:
                - secretRef:
                    name: openbao-s3-credentials
              volumeMounts:
                - name: tls
                  mountPath: /tls
                  readOnly: true
          volumes:
            - name: tls
              secret:
                secretName: openbao-server-tls
                items:
                  - key: ca.crt
                    path: ca.crt

Begründungen der kritischen Felder:

  • concurrencyPolicy: Forbid: wenn ein Snapshot länger als das Intervall dauert (große Cluster, langsames S3), würde sonst der nächste Job parallel starten — beide bekämen 403 vom Auth-Lease-TTL oder produzierten korrupte Files. Forbid wartet stattdessen.
  • backoffLimit: 2: zwei Retry-Versuche, dann lässt der Job es. Höher führt zu „Endless-Loop-Spam”, wenn z. B. die Auth-Role kaputt ist.
  • successfulJobsHistoryLimit: 3 / failedJobsHistoryLimit: 5: behält die letzten Jobs als Pods im Cluster — wichtig fürs Debugging nach Failures, ohne den etcd zuzumüllen.
  • BAO_ADDR=…openbao-active.openbao.svc:8200: spricht den Leader direkt an (Hintergrund: kubernetes-service-registration). Der Snapshot-Endpoint ist Write-Forwarding-fähig, aber direkter Leader-Hit spart einen Hop.
  • --sse aws:kms --sse-kms-key-id: Verschlüsselung at-rest mit Customer-Managed-Key. Pflicht bei jedem ernst gemeinten Setup.

Für diesen CronJob braucht es zusätzlich:

  • Eine ServiceAccount openbao-snapshot-agent im Namespace openbao (manifest mitliefern oder per Helm chart anlegen, falls dort vorhanden).
  • Die Auth-Role aus § 1.2 — bindet diese ServiceAccount an die Policy.

3 — Schicht 2: Seal-Material backupen

Je nach Seal-Mechanismus (siehe seal-unseal) sieht das anders aus:

3.1 — Auto-Unseal mit Cloud-KMS (AWS / GCP / Azure)

Der eigentliche Master-Key wird vom Cloud-KMS verwaltet — OpenBao hat ihn nie im Klartext. Backup-Anforderungen:

  • Cross-Region-Replication: KMS-Key in einer zweiten Region replizieren. Bei AWS: Multi-Region-Key mit Primary in eu-central-1 und Replica in eu-west-1. Wenn die erste Region offline ist, kann der Cluster trotzdem unsealen.
  • Recovery Keys sichern: Auto-Unseal generiert beim bao operator init zusätzlich Recovery Keys (Shamir-split). Die braucht man für operator generate-root und Seal-Migrationen. Genauso schützen wie klassische Shamir-Keys (siehe nächster Abschnitt). Dürfen niemals in K8s-Secrets oder Git landen.
  • IAM-Policy für den KMS-Key dokumentieren: wer welche kms:Decrypt/kms:Encrypt-Rechte hat. Im Disaster-Restore-Cluster wird derselbe IAM-Setup gebraucht.

3.2 — Transit-Seal (zweiter OpenBao-Cluster)

Das „Seal-Material” ist hier das Auth-Token, das der primäre Cluster gegen den zweiten OpenBao verwendet, plus die Transit-Key-ID. Backup:

  • Auth-Token als verschlüsseltes File offline (z. B. age-encrypted auf einem Yubikey).
  • Transit-Cluster seinerseits backupen — er ist genauso disaster-recovery-relevant.

3.3 — Shamir-Unseal (PoC, nicht für Production)

Hier sind die Unseal-Keys aus bao operator init selbst das Seal-Material. Backup:

  • 5 Shards auf 5 verschiedene Personen verteilen (3/5-Threshold).
  • Niemals digital zusammenführen.
  • Niemals in K8s-Secrets, ConfigMaps oder Git.
  • Schwer-Tresor + Yubikey-verschlüsselt + Notar — die übliche Belt-and-Suspenders-Routine.

4 — Schicht 3: Manifeste und Konfiguration backupen

Drei Bestandteile:

4.1 — Git-Repo

Folgendes muss versioniert sein:

  • values-ha.yaml für das OpenBao-Chart.
  • cert-manager-Manifeste (Issuer, Certificate) — siehe k8s-ha-from-scratch § 10 bzw. rke2-ha-setup § 10 / k3s-ha-setup § 10.
  • NetworkPolicies (Cilium oder vanilla).
  • StorageClass-Definitionen (falls custom).
  • Snapshot-Agent-Config (falls eigener CronJob aus § 2).
  • Auth-Role-Manifeste (als kubectl exec-Skript oder Terraform).

Sensible Werte (S3-Credentials, KMS-Key-IDs, Recovery-Keys) niemals im Klartext — entweder SealedSecrets oder ExternalSecrets-Pointer.

4.2 — Velero als zweite Sicherheits-Schicht (optional)

Velero sichert ganze Namespaces inkl. Resources, PVCs und Secrets. Nicht als Ersatz für den Raft-Snapshot — Velero kennt die Barrier-Konsistenz nicht und kann keinen konsistenten Snapshot der OpenBao-Daten machen. Aber als zweite Schicht für die K8s-Resources nützlich:

velero install --provider aws --bucket openbao-velero ...

velero schedule create openbao-daily \
  --schedule="0 2 * * *" \
  --include-namespaces openbao \
  --exclude-resources persistentvolumeclaims,persistentvolumes

Wichtig: PVCs explizit ausschließen — sonst landet ein nicht-konsistenter Stand der Raft-Volumes im Velero-Backup, der beim Restore Schaden anrichtet. Velero ist hier nur fürs Manifest-Re-Apply.

4.3 — etcd-Snapshots der K8s-Distribution

Orthogonal zum OpenBao-Backup, aber wichtig: K8s selbst muss auch wiederherstellbar sein.

  • RKE2 / k3s: schreiben automatisch etcd-Snapshots nach /var/lib/rancher/{rke2,k3s}/server/db/snapshots/ (alle 12 h, 5 Snapshots Default). Diese auf S3 oder NFS wegspiegeln.
  • kubeadm: etcdctl snapshot save als CronJob auf einem Control-Plane-Node, ebenfalls nach S3.

5 — NetworkPolicy für den Snapshot-Pfad

Wenn im openbao-Namespace eine Default-Deny-Egress-NetworkPolicy aktiv ist (empfohlen), muss der Snapshot-CronJob expliziten Egress bekommen.

Mit Cilium:

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: openbao-snapshot-egress
  namespace: openbao
spec:
  endpointSelector:
    matchLabels:
      app.kubernetes.io/name: openbao-snapshot-agent
  egress:
    # zum OpenBao-Service (für bao snapshot save)
    - toEndpoints:
        - matchLabels:
            app.kubernetes.io/name: openbao
            component: server
      toPorts:
        - ports:
            - port: "8200"
              protocol: TCP
    # zur K8s-API (für ServiceAccount-Token-Review beim Login)
    - toEntities:
        - kube-apiserver
    # zu DNS
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
    # zu S3 (über CIDR oder FQDN-Match)
    - toFQDNs:
        - matchPattern: "*.amazonaws.com"
      toPorts:
        - ports:
            - port: "443"
              protocol: TCP

Warum jeder Block einzeln aufgeführt ist und nicht ein generisches “allow all egress”: jede Lücke ist ein potenzieller Exfiltrations-Pfad für ein kompromittiertes Snapshot-Token. Der CronJob darf zum OpenBao-Service hin, zur K8s-API für den Token-Review, zu DNS und zu S3 — und nichts anderes.

6 — Restore-Verfahren im laufenden Cluster

Restore ist eine seltene, folgenschwere Operation. Vor dem Ernstfall mindestens einmal in einer Sandbox üben (§ 7).

Allgemeines Verfahren (source: raw/docs/commands/operator/raft.md):

6.1 — Snapshot-File holen

TIMESTAMP=20260520T103015Z
aws s3 cp s3://openbao-snapshots/openbao-snapshot-${TIMESTAMP}.snap ./restore.snap

6.2 — In einen OpenBao-Pod kopieren

kubectl -n openbao cp ./restore.snap openbao-0:/tmp/restore.snap

6.3 — Restore auf dem Leader

Erst sicherstellen, dass openbao-0 der Leader ist:

kubectl -n openbao get pods -l openbao-active=true

Wenn ein anderer Pod Leader ist, dorthin kopieren und dort ausführen.

kubectl -n openbao exec -ti openbao-0 -- bao login <ROOT_TOKEN>
kubectl -n openbao exec -ti openbao-0 -- bao operator raft snapshot restore /tmp/restore.snap

Was hier passiert: der Restore überschreibt den aktuellen Cluster-State mit dem Inhalt des Snapshots. Es ist eine Hard-Replace-Operation, kein Merge. Die anderen Voter synchronisieren sich automatisch über Raft auf den neuen State (source: raw/docs/commands/operator/raft.md).

6.4 — Verifikation

kubectl -n openbao exec -ti openbao-0 -- bao status
kubectl -n openbao exec -ti openbao-0 -- bao operator raft list-peers
kubectl -n openbao exec -ti openbao-0 -- bao secrets list

Erwartet: Cluster unsealed, Leader-Wahl stabil, Secrets-Mounts auf dem Stand vom Snapshot-Zeitpunkt.

6.5 — Falls der Cluster komplett tot ist (Disaster Recovery)

Ablauf wenn keine drei OpenBao-Pods mehr existieren — z. B. weil die PVCs unwiederbringlich weg sind:

  1. K8s-Cluster ist da (aus etcd-Snapshot wiederhergestellt oder neu aufgesetzt — siehe k8s-ha-from-scratch / rke2-ha-setup / k3s-ha-setup).
  2. cert-manager-PKI restoren aus Git — neue Certs werden ausgestellt, weil cert-manager die Issuer-Manifeste sieht.
  3. Seal-Material verfügbar machen: KMS-Key zugänglich (gleiche Region/Account oder Cross-Region-Replica), oder Recovery Keys / Shamir Shards bereitstellen.
  4. Helm-Install mit values-ha.yaml aus Git. Drei Pods kommen hoch, alle sealed.
  5. Nur openbao-0 unsealen — die anderen lassen wir bewusst sealed.
  6. Snapshot in openbao-0 kopieren, dann bao operator raft snapshot restore.
  7. openbao-0 neu unsealen falls der Restore den Seal-Status zurückgesetzt hat.
  8. openbao-1 und openbao-2 unsealen — sie joinen automatisch via retry_join und ziehen den State von openbao-0.
  9. Verifikation wie in § 6.4.

7 — Test-Restore in einer Sandbox

Regel: ungetesteter Restore = Hoffnungswert, kein Backup. Mindestens einmal pro Quartal in einem isolierten Sandkasten durchspielen.

Wichtig: das Test-Cluster darf keinen Netzzugriff auf Produktiv-Systeme haben (DBs, Cloud-APIs). Sonst rotiert das gerestorete Cluster Credentials, die das echte Produktiv-Cluster gerade noch benutzt — Production-Outage mit Anlauf.

Konkretes Sandbox-Setup:

# values-restore-sandbox.yaml — Modifikation gegenüber Production:
global:
  tlsDisable: false

server:
  # eigener Namespace, kein Konflikt mit Production
  # (kubectl create namespace openbao-restore-test)

  ha:
    enabled: true
    replicas: 3
    raft:
      enabled: true
      config: |
        # exakt dieselbe Config wie Production — sonst klappt
        # der TLS-Handshake mit dem gerestoreten State nicht
        ...

  # zwei Sicherungen gegen Cred-Rotation:
  # 1) NetworkPolicy default-deny egress (Sandbox-only)
  # 2) Mock-Endpoints für Database-Mounts in der Config

Im Sandbox-Namespace:

  1. Identisches Seal-Setup wie Production (Cross-Region-KMS-Key oder kopierte Recovery-Keys).
  2. Cilium-NetPol mit default deny all egress.
  3. OpenBao-Pods hochfahren, openbao-0 mit dem Production-Snapshot restoren.
  4. Aktiver Check: bao read database/roles/... darf keine echten DB-Credentials erzeugen. Die NetPol sollte die ausgehende DB-Connection blocken — wenn doch eine durchgeht, ist die Isolation kaputt.
  5. Smoketest: bao kv get secret/foo aus einem Test-Pod heraus.
  6. Sandbox wieder abreißen — kubectl delete namespace openbao-restore-test.

Skript-Vorschlag für die Quartals-Übung: ein Job, der die ganze Sequenz automatisiert und in einen separaten restore-drill-Namespace deployt, am Ende einen Report ins Slack postet.

8 — Monitoring und Alerting

Ein silent-failing CronJob ist schlimmer als gar kein Backup — er gibt eine falsche Sicherheit. Was zu monitoren ist:

8.1 — CronJob-Failures via kube-state-metrics

Prometheus-Query, die einen Alarm auslöst, wenn 2 Stunden lang kein erfolgreicher Snapshot-Job gelaufen ist:

time() - kube_cronjob_status_last_successful_time{cronjob="openbao-snapshot-agent",namespace="openbao"} > 7200

Alert-Regel:

- alert: OpenBaoSnapshotStale
  expr: time() - kube_cronjob_status_last_successful_time{cronjob="openbao-snapshot-agent",namespace="openbao"} > 7200
  for: 5m
  labels:
    severity: critical
  annotations:
    summary: "OpenBao snapshot CronJob hat seit >2h keinen erfolgreichen Lauf"
    runbook: "https://wiki.example.com/runbooks/openbao-snapshot-stale"

8.2 — Snapshot-Größen-Anomalien

Plötzlich deutlich kleinere Snapshots sind ein Warnsignal — meist Datenverlust durch Bug, manchmal ein kaputter Backup-Pfad. Bucket-Object-Size als Prometheus-Metrik per Sidecar oder per CloudWatch:

(s3_object_size_bytes{bucket="openbao-snapshots"}
 / s3_object_size_bytes{bucket="openbao-snapshots"} offset 24h)
 < 0.5

Schlägt an, wenn der heutige Snapshot weniger als 50 % der Größe von vor 24 h hat.

8.3 — Bucket-Verfügbarkeit

probe_success{job="blackbox-s3", target="openbao-snapshots"} == 0

Über Prometheus Blackbox Exporter oder cloud-natives Monitoring (CloudWatch S3-Metriken).

8.4 — OpenBao Audit-Trail-Bestätigung

Jeder Snapshot-Pull erzeugt einen Audit-Log-Eintrag (sys/storage/raft/snapshot read). Wenn der Audit-Log diesen Eintrag nicht zeigt, hat zwar ein Job gelaufen, aber er hat keinen Snapshot gezogen — z. B. wegen Auth-Fehler. Audit-Logging ist ohnehin Pflicht (siehe audit) — die Snapshot-Calls beobachten ist freie Zusatz-Visibility.

9 — Antipatterns

  • Snapshot ins PVC schreiben. Wenn der CronJob das .snap-File auf ein OpenBao-eigenes PVC legt, ist es bei Volume-Loss genauso weg wie alles andere. Snapshot muss nach off-cluster.
  • S3-Credentials als plain kubectl create secret ohne Sealed-/External-Secrets-Schicht. Klartext im etcd, Klartext in jedem Backup, Klartext in jedem Cluster-Export.
  • CronJob ohne concurrencyPolicy: Forbid. Bei großen Clustern überlappen Jobs, beide bekommen Token TTL exceeded oder produzieren halbe Files. Default ist Allow — explizit auf Forbid setzen.
  • Recovery-Keys / Shamir-Shards in einem K8s-Secret. Wenn der Cluster weg ist, sind sie auch weg. Die müssen außerhalb des K8s-Bestands liegen.
  • Velero allein als Backup. Velero sichert Manifeste und PVCs, kennt aber keine Barrier-Konsistenz. Ein gerestoretes PVC kann mitten in einem Raft-Write korrupt sein. Velero ist Sekundär, nicht Primär.
  • Snapshot-Pfad direkt auf den Standby-Service zeigen. Auch wenn bao operator raft snapshot save Request-Forwarding kann — auf den openbao-active-Service zeigen (kubernetes-service-registration), sonst hat man bei Failover-Race-Conditions Snapshots, die mitten im Leader-Wechsel erstellt wurden.
  • Snapshot-Schedule = Production-Schreibfrequenz. Wer alle 30 s in OpenBao schreibt und alle 15 min snapshottet, akzeptiert implizit 15 Minuten Datenverlust-Risiko. Schedule am RPO-Ziel ausrichten, nicht am „klingt vernünftig”.
  • Kein Test-Restore. Siehe § 7. Ohne geübten Restore ist die Backup-Strategie eine Annahme, kein System.
  • Snapshot-Bucket im selben AWS-Account wie der Cluster. Wenn ein kompromittiertes Cluster-Token Bucket-Zugriff bekommt (oder ein gelöschter IAM-User Bucket-Policies cascadiert), ist alles weg. Snapshots gehören in einen separaten Account / Subscription / Project.

10 — Zusammenfassung: Minimale Production-Ready-Konfiguration

Was zwingend stehen muss, bevor ein OpenBao-K8s-Cluster Production-tauglich ist:

  • snapshotAgent (oder eigener CronJob) läuft mit Schedule ≤ RPO-Ziel.
  • S3-Bucket im separaten Account, mit KMS-Verschlüsselung at-rest, ohne Delete-Recht für den Cluster-Schlüssel.
  • Bucket-Lifecycle: kurz/mittel/lang gestaffelte Retention.
  • Auto-Unseal mit Cross-Region-KMS-Key.
  • Recovery Keys offline abgelegt.
  • values-ha.yaml + alle Manifeste in Git (Secrets via SealedSecrets oder ExternalSecrets).
  • etcd-Snapshots der K8s-Distribution nach extern.
  • Audit-Devices aktiv (siehe audit).
  • Prometheus-Alerts auf stale CronJob und Snapshot-Size-Anomalien.
  • Restore in Sandbox erfolgreich durchgeführt — innerhalb der letzten 90 Tage.
  • backups — konzeptionelle Grundlage: warum, was, Snapshot-Mechanik, Offline-vs.-Online
  • raft — Snapshot-Internals, Konsens-Mechanik, Recovery
  • seal-unseal — Auto-Unseal, Recovery-Keys, Shamir
  • k8s-ha-setup — kompakter Helm-Bring-Up (Snapshot-Agent erwähnt)
  • k8s-ha-from-scratch — kubeadm-End-to-End-Variante (Snapshot-Hinweis im Hardening-Teil)
  • rke2-ha-setup — RKE2-Variante mit etcd-Snapshots als zusätzlicher Layer
  • k3s-ha-setup — k3s-Variante mit eingebauten etcd-Snapshots
  • audit — Audit-Trail für Snapshot-Calls
  • upgrading — Snapshot vor jedem Upgrade als Pflicht
  • kubernetes-service-registration — warum der Snapshot-Pfad auf openbao-active zeigen sollte