Files
home-network-k3s-impl/platform/velero
jakman85andClaude Opus 5 d99d47494c fix(velero): nightly backup PartiallyFailed on Velero's own maintenance Jobs
VeleroNoRecentBackup has been firing CRITICAL on prod over a backup that was
actually protecting everything.

velero-daily-20260919030041: PartiallyFailed, 3079/3079 items backed up, exactly
2 errors — both PodVolumeBackups for the `plugins` and `scratch` volumes of
homepage-default-kopia-maintain-job-1789786837269-qhsvh. Four such PVB failures
had accumulated.

Velero runs transient kopia repository-maintenance Jobs in its own namespace, one
per BackupRepository, each with emptyDir `plugins` and `scratch` volumes. Schedule
(1) runs defaultVolumesToFsBackup:true — the opt-out model — so it attempts
fs-backup on those volumes too. The Job finishes and its pod disappears
mid-backup, and the PodVolumeBackup fails.

Nothing real was ever missed. But PartiallyFailed does not advance
velero_backup_last_successful_timestamp, so the alert that exists to catch a
genuine backup outage fires every night over Velero cleaning up after itself. An
alert that cries wolf nightly is how a real failure gets ignored — and this one
guards R-STATE-03.

Fix uses the pattern already established for monitoring+unifi: exclude the
namespace's volume DATA from schedule (1), keep its RESOURCES in schedule (2).
Safe by inspection — ns velero holds no PVCs, so there is no volume data to lose,
while the BSL, credentials and Backup CRs that DR actually needs are still
captured.

Deliberately narrower than the general rule. The general fix is a volume policy
skipping emptyDir by type, which would also cover a transient Job in any other
namespace, but it needs Velero's `volumeTypes` policy condition — unproven in this
repo on 1.18.1, and a policies.yaml Velero cannot parse breaks EVERY backup. That
is a much worse failure than the one being fixed, and all four observed failures
were kopia-maintain pods in ns velero. Rationale recorded inline so the next
person can widen it on purpose rather than by accident.

Gated: L0 whole-repo 21 cases, 0 failures, 0 skips, 0 unwired.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 23:55:57 -05:00
..

platform/velero

Velero — off-cluster backup and DR of Kubernetes resource state + PVC data.

What this is (LIVE)

Flux HelmRelease (Velero chart 12.x / Velero 1.18.x) wired to the on-Proxmox MinIO S3 target:

  • Endpoint: http://192.168.1.9:9000 (plain HTTP, path-style, region us-east-1 placeholder)
  • BucketStorageLocation default: provider aws, bucket velero, s3ForcePathStyle=true.
  • AWS plugin (velero/velero-plugin-for-aws) loaded as an init container.
  • File System Backup (kopia): deployNodeAgent=true, uploaderType=kopia, snapshotsEnabled=false — storage-agnostic PVC backup, no CSI snapshot / data-mover.
  • Credentials: velero-cloud-credentials — a SOPS Secret (velero-cloud.sops.yaml) holding the cloud INI key, 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.

Reconciled by its own non-gating Flux Kustomization clusters/base/backup.yaml (dependsOn: platform-config, wait:false) — a backup controller must never gate the platform graph.

Backup scope (what's included vs excluded)

Two daily Schedules (cadence ${VELERO_BACKUP_SCHEDULE}, retention ${VELERO_RETENTION}):

Schedule Namespaces Volumes (kopia FSB)
daily ALL except monitoring, unifi all PVC data (defaultVolumesToFsBackup:true, opt-out)
daily-resources-only monitoring, unifi none (defaultVolumesToFsBackup:false) — resources only

Excluded volume data (reconstructable, per the sizing decision):

  • monitoring — Prometheus TSDB PVC (ephemeral) + Loki 72h-log PVC. Grafana/Alertmanager are emptyDir (no PVC); Grafana dashboards are GitOps-provisioned.
  • unifi — the ~7.4 GiB KubeVirt VM disk (huge; UniFi is protected by its own config export).

Their Kubernetes resources are still captured (by daily-resources-only) to aid DR.

Included (the irreplaceable ~6 GiB): every other namespace's PVC data via kopia — Nextcloud data, Gitea, Plex/*arr config, PiHole, and the CNPG Postgres PVCs. (CNPG DBs additionally have native Barman PITR to the cnpg bucket — see the DB manifests.)

Post-deploy (PM)

  • velero backup-location get → default Available.
  • velero backup create --from-schedule daily → objects land under s3://velero/.
  • DR drill (R-STATE-03, Phase-4 gate): velero restore create --from-backup <name> into a scratch namespace and verify PVC data restores via the node-agent.