Velero Backup Failures
Problem
Velero backups failing with PartiallyFailed status, showing errors like:
- apiserver not ready
- dial tcp 10.43.0.1:443: connect: connection refused
- runtime core not ready
ZFSBackup CRs stuck in Init state with stale backupDest addresses. Orphaned ZFS snapshots consuming space.
Root Cause
Multiple failure modes:
-
Unattended-upgrades restarting k3s: systemd package upgrades trigger daemon-reexec, causing k3s to restart during backup windows. This breaks the API server connection mid-backup.
-
OpenEBS ZFS backup controller wedged: The ZFS node agent's watch can become stale, leaving ZFSBackup CRs stuck in
Initstate. The ephemeral backup receiver (port 9011) on the Velero pod is temporary and disappears when backups fail, making stuck CRs unrecoverable. -
Stale zfs send processes: Failed backups can leave
zfs sendprocesses running, holding snapshots open and preventing cleanup. These processes appear as:/sbin/zfs send datasets/openebs/<pvc>@<backup-name> -
Backup-sync controller loop: When backup data exists in the bucket but the Backup CR is deleted, the sync controller recreates it every 2 minutes, creating a loop of stuck ZFSBackup CRs.
How to Diagnose
Check for failed backups:
kubectl --context=grigri get backups -n velero | grep -E "InProgress|PartiallyFailed|Failed"
Check for stuck ZFSBackup CRs:
kubectl --context=grigri get zb -n zfs-localpv | grep -v Done
Check for stale zfs send processes:
ssh <node> 'ps aux | grep "zfs send" | grep -v grep'
Check for orphaned ZFS snapshots:
ssh <node> 'zfs list -H -t snapshot -o name,used | grep -E "retain-weekly|retain-quaterly" | grep -v "$(kubectl get zb -n zfs-localpv -o jsonpath="{.items[*].spec.snapName}" | tr " " "\n" | sort -u)"'
Check if unattended-upgrades caused k3s restart:
journalctl -u k3s --since "1 hour ago" | grep -i "stopped\|started"
journalctl -u unattended-upgrades --since "1 hour ago" | grep -i "systemd"
Fix / Workaround
Prevention: Maintenance Window
Configure unattended-upgrades to run in a broad window that doesn't collide with backups:
# /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf
[Timer]
OnCalendar=
OnCalendar=Mon,Wed..Sun *-*-* 08:00:00
RandomizedDelaySec=10h
This runs upgrades Monday + Wednesday–Sunday, 08:00–18:00, avoiding: - Velero weekly/quarterly backups (Tuesday 02:30) - Velero remote syncs (Thursday/Friday 02:30) - Kanidm backups (daily 03:00)
Do not blacklist systemd packages — this prevents security updates and is excessive for a first-time issue.
Cleanup: Stuck Backups
-
Delete failed Velero backup:
kubectl --context=grigri delete backup <backup-name> -n velero -
Delete stuck ZFSBackup CRs:
kubectl --context=grigri delete zb -n zfs-localpv \ <pvc-uuid>.<backup-name> \ <pvc-uuid>.<backup-name> -
Remove backup data from bucket (if sync controller loops):
AWS_ACCESS_KEY_ID=velero AWS_SECRET_ACCESS_KEY=<secret> \ aws --endpoint-url=https://s3.internal.grigri.cloud \ s3 rm --recursive s3://velero/backups/<backup-name>/ -
Restart velero pod to clear sync controller state:
kubectl --context=grigri delete pod -n velero -l app.kubernetes.io/name=velero
Cleanup: Stuck ZFS Snapshots
-
Kill stale zfs send processes:
ssh <node> 'sudo pkill -9 -f "zfs send.*<backup-name>"' -
Destroy orphaned snapshots:
ssh <node> 'sudo zfs destroy datasets/openebs/<pvc>@<backup-name>'
Recovery: Wedged ZFS Node Agent
If ZFSBackup CRs are stuck in Init and the node agent isn't processing them:
kubectl --context=grigri delete pod -n zfs-localpv -l app=openebs-zfs-node
The DaemonSet will recreate the pod with a fresh watch.
References
- Velero schedules:
platform/velero/templates/schedule-retain-weekly.yaml,schedule-retain-quaterly.yaml - Unattended-upgrades config:
metal/roles/prepare/files/50unattended-upgrades - Timer override:
metal/roles/prepare/files/apt-daily-upgrade-override.conf - Ansible role:
metal/roles/prepare/tasks/unattended-upgrades.yml