--- # Velero — off-cluster backup/DR of cluster resources + PVC data (ADR-0003, R-STATE-03). LIVE. # # TARGET: the MinIO S3 endpoint ${S3_BACKUP_ENDPOINT} (cluster-vars; dev http://192.168.1.9:9000 on # Proxmox — plain HTTP, path-style), bucket # `velero`. region us-east-1 is a placeholder MinIO ignores (S3 clients still require one). # # WHY THIS SHAPE: # - provider aws + velero-plugin-for-aws (init container) drives the S3-compatible MinIO target. # - File System Backup (kopia) via the node-agent DaemonSet — storage-agnostic PVC backup that does # NOT depend on CSI VolumeSnapshots or a data-mover (the simplest path that still captures the # Longhorn PVC *data*). snapshotsEnabled:false → no VolumeSnapshotLocation is created/needed. # - Credentials: the `cloud` INI key of the SOPS Secret velero-cloud-credentials (ns velero) — the # SCOPED MinIO key limited to the velero+cnpg buckets. Kept in sync with the staging source of # truth platform/backup/minio-credentials.sops.yaml and the Proxmox host ansible-vault. # # VOLUME OPT-OUT (per the sizing decision) is implemented with TWO schedules (a single Backup has one # cluster-wide defaultVolumesToFsBackup, so per-namespace volume policy needs two): see `schedules`. # # NON-GATING TIER: reconciled by clusters/base/backup.yaml (dependsOn platform-config, wait:false). # A backup controller must NEVER gate the platform graph (mirror headlamp/oauth2-proxy). apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: velero namespace: velero spec: interval: 30m chart: spec: chart: velero version: ">=12.0.0 <13.0.0" # Velero v1.18.x line (k8s-1.36 catch-up). CRDs pre-applied server-side (upgradeCRDs:false). Renovate maintains sourceRef: kind: HelmRepository name: vmware-tanzu namespace: velero install: remediation: retries: 3 upgrade: remediation: retries: 3 values: # --- CRD install strategy. The chart's pre-install `velero-upgrade-crds` HOOK job runs # docker.io/bitnami/kubectl, whose public Docker Hub catalog Bitnami REMOVED (Aug 2025) — # `docker.io/bitnami/kubectl:1.31` now 404s, leaving the hook in Init:ImagePullBackOff so Velero # never installs. The hook exists ONLY to UPDATE CRDs on chart upgrades; it is NOT how CRDs are # first installed. The chart ships all 13 Velero CRDs (backups/restores/backupstoragelocations/ # schedules/podvolumebackups/…) in its native `crds/` directory, which Helm applies # UNCONDITIONALLY on install — before hooks — regardless of this flag. # Disabling the hook therefore drops the fragile external-kubectl-image dependency entirely while # the CRDs still install via Helm's crds/ handling. Trade-off: Helm never manages crds/ after the # initial install, so CRD *schema upgrades* on future chart bumps become a manual/Renovate step — # acceptable: Velero's CRDs are stable, and this is far less fragile than pinning a shell-bearing # kubectl mirror (rancher/kubectl is distroless → no /bin/sh for the hook's copy-binary step). --- upgradeCRDs: false # --- AWS object-store plugin (BSL provider=aws → MinIO S3-compatible), loaded as an init container. # Image minor MUST track the Velero minor (chart 12.x = Velero 1.18 → plugin v1.14.x). --- initContainers: - name: velero-plugin-for-aws image: velero/velero-plugin-for-aws:v1.14.2 imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /target name: plugins # --- File System Backup (kopia): the node-agent DaemonSet mounts each pod's volumes and copies # the data into S3. Storage-agnostic; no CSI-snapshot / data-mover wiring. --- deployNodeAgent: true snapshotsEnabled: false # no VolumeSnapshotLocation — kopia FSB is the volume-data path configuration: uploaderType: kopia backupStorageLocation: - name: default provider: aws bucket: velero # PER-ENVIRONMENT PREFIX (cluster-vars). Empty on dev = the bucket root it already uses; # `prod` on prod. Without it both clusters share one BackupStorageLocation — and Velero # SYNCS backups it finds there into itself as Backup CRs and GARBAGE-COLLECTS them when # their TTL expires, so prod would adopt and then delete dev's backups. Both clusters also # run schedules named `daily`/`daily-resources-only`, so even the object keys would collide. prefix: ${VELERO_BACKUP_PREFIX} default: true config: # Region is a knob, not a constant: S3 clients require SOME region, MinIO ignores whatever # it is, and Cloudflare R2 requires exactly `auto`. Hardcoding `us-east-1` silently broke # the moment the target stopped being MinIO. region: ${S3_BACKUP_REGION} s3Url: ${S3_BACKUP_ENDPOINT} # cluster-vars; R2 (https) or a MinIO/versitygw (http) s3ForcePathStyle: "true" # MinIO is path-style; R2 accepts path-style too # *** REQUIRED FOR CLOUDFLARE R2 — DO NOT DROP THIS LINE. *** # velero-plugin-for-aws v1.14+ bundles an AWS SDK that sends `x-amz-sdk-checksum-algorithm` # / `x-amz-checksum-crc32` on every PUT. R2 REJECTS those headers (HTTP 400 # "InvalidArgument"), so every backup and every kopia blob upload fails. Setting this to the # empty string disables the SDK's flexible-checksum behaviour. It is harmless against MinIO, # which simply ignores it — so it is stated unconditionally rather than per-environment. # If backups start failing with InvalidArgument after a plugin bump, look here first. checksumAlgorithm: "" credentials: existingSecret: velero-cloud-credentials # SOPS Secret carrying the `cloud` INI key (ns velero) # --- Daily backups. TWO schedules implement the opt-out volume policy: capture every PVC's data # by default (opt-out), but skip the two reconstructable heavy volumes' DATA while still # capturing their Kubernetes resources. --- schedules: # (1) The whole cluster EXCEPT monitoring+unifi: all resources + all pod-volume data via kopia. # defaultVolumesToFsBackup:true is the OPT-OUT model — nothing with real data is forgotten. daily: disabled: false schedule: "${VELERO_BACKUP_SCHEDULE}" useOwnerReferencesInBackup: false template: ttl: "${VELERO_RETENTION}" storageLocation: default snapshotVolumes: false defaultVolumesToFsBackup: true excludedNamespaces: - monitoring # volume DATA excluded here; resources captured by schedule (2) - unifi # (2) monitoring + unifi: RESOURCES ONLY (defaultVolumesToFsBackup:false → no volume data). Their # PVCs are reconstructable and excluded from data backup per the sizing decision: # - monitoring: the Prometheus TSDB PVC (ephemeral/reconstructable) and the Loki 72h-log PVC # (Grafana/Alertmanager are emptyDir — no PVC). Grafana dashboards are GitOps-provisioned. # - unifi: the ~7.4 GiB KubeVirt VM disk (huge + reconstructable; UniFi is protected by its # own config export, not this disk). # Backing up their K8s objects (VM/DataVolume/Services/HelmRelease-owned CRs) still aids DR. daily-resources-only: disabled: false schedule: "${VELERO_BACKUP_SCHEDULE}" useOwnerReferencesInBackup: false template: ttl: "${VELERO_RETENTION}" storageLocation: default snapshotVolumes: false defaultVolumesToFsBackup: false includedNamespaces: - monitoring - unifi # Modest home-dev footprint (mirror the other platform components). resources: requests: cpu: 50m memory: 128Mi limits: memory: 512Mi nodeAgent: resources: requests: cpu: 50m memory: 128Mi limits: memory: 512Mi