VolSync Resource Flow and Cleanup Guide
Reference for the VolSync resource hierarchy and the cleanup procedures used when making storage/infrastructure changes.
Overview
VolSync creates a complex chain of Kubernetes resources for backup and replication. Understanding this flow is critical when making infrastructure changes to avoid orphaned resources or provisioning conflicts.
Resource Hierarchy
Flux Kustomization (volsync)
└── App Kustomization (e.g., archon)
├── ReplicationSource (archon)
│ ├── Snapshot PVC (volsync-archon-src)
│ ├── Cache PVC (volsync-src-archon-cache) [RBD]
│ ├── Repository PVC (volsync-archon-repo)
│ │ └── Repository PV (volsync-archon-repo-pv) [Static CephFS]
│ └── Mover Pod (volsync-src-archon-xxxxx)
│
├── ReplicationSource (archon-r2) [Remote/R2]
│ ├── Snapshot PVC (volsync-archon-r2-src)
│ ├── Cache PVC (volsync-src-archon-r2-cache) [RBD]
│ └── Mover Pod (volsync-src-archon-r2-xxxxx)
│
└── ReplicationDestination (archon-dst)
├── Destination PVC (volsync-archon-dst-dest)
├── Cache PVC (volsync-dst-archon-dst-cache) [RBD]
├── Repository PVC (volsync-archon-repo)
│ └── Repository PV (volsync-archon-repo-pv) [Static CephFS]
└── Mover Pod (volsync-dst-archon-dst-xxxxx)
Resource Types Explained
Static Resources (Pre-created via Kustomize)
-
Repository PV (
volsync-${APP}-repo-pv)- Type: Static CephFS PersistentVolume
- Points to:
/k8s-backups/volsync/${APP}on external Ceph cluster - Purpose: Houses restic repository data
- Created by:
flux/components/volsync/repository-pv.yamltemplate - Key attributes:
volumeAttributes: clusterID: rook-ceph fsName: cephfs staticVolume: "true" pool: k8s-backups rootPath: "/k8s-backups/volsync/${APP}"
-
Repository PVC (
volsync-${APP}-repo)- Type: PersistentVolumeClaim
- Binds to: Static PV above via
volumeNamefield - Created by:
flux/components/volsync/repository-pvc.yamltemplate
Dynamic Resources (Created by VolSync Operator)
-
ReplicationSource/Destination
- Type: VolSync CRDs
- Created by: App kustomizations (when volsync component is included)
- Manages: Backup/restore jobs
-
Cache PVCs (e.g.,
volsync-src-${APP}-cache,volsync-dst-${APP}-dst-cache)- Type: Dynamic RBD PersistentVolumeClaims
- Created by: VolSync operator (from ReplicationSource/Destination specs)
- Purpose: Temporary staging for restic operations
- Storage Class:
ceph-rbd(block storage)
-
Snapshot PVCs (e.g.,
volsync-${APP}-src)- Type: Dynamic CephFS PersistentVolumeClaims
- Created by: VolSync operator from source data snapshots
- Storage Class:
cephfs-shared
-
Mover Pods
- Type: Kubernetes Pods
- Created by: VolSync operator
- Purpose: Execute restic backup/restore operations
- Mounts: Source/destination PVCs, cache PVCs, repository PVC
Common Cleanup Scenarios
Scenario 1: Changing Repository PV Configuration
When: Modifying repository-pv.yaml (path, pool, monitors, etc.)
Why cleanup needed: Static PVs are immutable once created. Changes require delete/recreate.
Cleanup Order:
# 1. Suspend the volsync kustomization to prevent recreation
flux suspend kustomization volsync -n storage
# 2. Scale down volsync operator
kubectl scale deployment volsync -n storage --replicas=0
# 3. Delete ReplicationSources
kubectl get replicationsource -A -o json | \
jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete replicationsource -n $0 $1'
# 4. Delete ReplicationDestinations
kubectl get replicationdestination -A -o json | \
jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete replicationdestination -n $0 $1'
# 5. Delete Repository PVCs (unbinds from PVs)
kubectl get pvc -A -o json | \
jq -r '.items[] | select(.metadata.name | test("volsync-.*-repo$")) | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete pvc -n $0 $1'
# 6. Delete Repository PVs
kubectl get pv -o name | \
grep "persistentvolume/volsync-.*-repo-pv$" | \
xargs kubectl delete
# 7. Wait for cleanup to complete
sleep 10
# 8. Commit and push your repository-pv.yaml changes
git add kubernetes/flux/components/volsync/repository-pv.yaml
git commit -m "fix(volsync): update repository PV configuration"
git push
# 9. Resume volsync kustomization (recreates PVs/PVCs with new config)
flux resume kustomization volsync -n storage
# 10. Scale volsync operator back up
kubectl scale deployment volsync -n storage --replicas=1
# 11. Watch for recreation
watch kubectl get pv,pvc -A | grep volsync
Scenario 2: Stuck Cache PVC Provisioning
When: Cache PVCs stuck in Pending with "Volume ID already exists" errors
Why this happens: Orphaned provisioning operations in Ceph RBD from previous deletion attempts
Cleanup Order:
# 1. Delete stuck cache PVCs
kubectl get pvc -A -o json | \
jq -r '.items[] | select(.metadata.name | test("cache")) | select(.status.phase == "Pending") | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete pvc -n $0 $1'
# 2. Wait for VolSync operator to recreate with fresh UIDs
# Cache PVCs are owned by ReplicationSource/Destination resources
# and will auto-recreate with new UIDs that don't conflict
Scenario 3: Full VolSync Reset
When: Major infrastructure changes, migrating storage backends, etc.
Cleanup Order:
# Complete reset - destroys all VolSync resources but preserves data in /k8s-backups/volsync/
# 1. Suspend volsync
flux suspend kustomization volsync -n storage
# 2. Scale down operator
kubectl scale deployment volsync -n storage --replicas=0
# 3. Delete all ReplicationSources
kubectl get replicationsource -A -o json | \
jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete replicationsource -n $0 $1'
# 4. Delete all ReplicationDestinations
kubectl get replicationdestination -A -o json | \
jq -r '.items[] | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete replicationdestination -n $0 $1'
# 5. Delete all cache PVCs
kubectl get pvc -A -o json | \
jq -r '.items[] | select(.metadata.name | test("cache")) | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete pvc -n $0 $1'
# 6. Delete all snapshot PVCs
kubectl get pvc -A -o json | \
jq -r '.items[] | select(.metadata.name | test("volsync-.*-src$|volsync-.*-dst-dest$")) | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete pvc -n $0 $1'
# 7. Delete repository PVCs
kubectl get pvc -A -o json | \
jq -r '.items[] | select(.metadata.name | test("volsync-.*-repo$")) | "\(.metadata.namespace) \(.metadata.name)"' | \
xargs -n 2 sh -c 'kubectl delete pvc -n $0 $1'
# 8. Delete repository PVs
kubectl get pv -o name | \
grep "persistentvolume/volsync-.*-repo-pv$" | \
xargs kubectl delete
# 9. Resume after changes
flux resume kustomization volsync -n storage
kubectl scale deployment volsync -n storage --replicas=1
Critical Configuration Details
Static CephFS PV Requirements
For static PVs pointing to existing CephFS directories:
volumeAttributes:
clusterID: rook-ceph # References mon-endpoints ConfigMap
fsName: cephfs # CephFS filesystem name
staticVolume: "true" # REQUIRED for static PVs
pool: k8s-backups # Data pool (doesn't affect mount path)
rootPath: "/k8s-backups/volsync/${APP}" # FULL path in filesystem
Important Notes:
staticVolume: "true"is mandatory - without it, CSI driver won't properly discover monitorspoolparameter specifies which data pool stores the data, NOT the mount pathrootPathmust be the full filesystem path where data existsmonitorsfield is NOT required - auto-discovered fromrook-ceph-mon-endpointsConfigMap whenstaticVolume: "true"
Why Pool Doesn't Change Mount Path
The pool parameter in CephFS tells Ceph which data pool to use for new data storage, but:
- Mount paths are at the filesystem level, not pool level
- All pools share the same namespace/directory structure
- Path
/k8s-backups/volsync/archonexists in the filesystem regardless of pool - The
poolonly matters for data placement on OSDs
Example:
Filesystem: cephfs
├── /volumes/csi/... (data in cephfs_data pool)
├── /k8s-backups/volsync/... (data in k8s-backups pool)
└── /media/... (data in cephfs_media pool)
Troubleshooting
Mount Error: "No such file or directory"
mount error 2 = No such file or directory
Cause: rootPath doesn't exist in CephFS filesystem
Solution: Verify path exists: ls /mnt/cephfs/k8s-backups/volsync/${APP}
Fix: Update rootPath in repository-pv.yaml to match actual filesystem path
DNS SRV Lookup Warnings
unable to get monitor info from DNS SRV with service name: ceph-mon
Impact: Warning only - mount proceeds with explicit monitors from ConfigMap Cause: Ceph client tries DNS discovery before using explicit monitors Action: None required if mount succeeds
Cache PVC: "Volume ID already exists"
rpc error: code = Aborted desc = an operation with the given Volume ID already exists
Cause: Orphaned provisioning operation in RBD from previous PVC with same UID Solution: Delete the PVC - VolSync will recreate with fresh UID
Data Safety
What Gets Deleted
When following cleanup procedures:
- ✅ Kubernetes PVs/PVCs (metadata only)
- ✅ ReplicationSource/Destination CRDs
- ✅ Temporary cache volumes
- ✅ Snapshot PVCs
What Gets Preserved
The cleanup procedures DO NOT delete:
- ✅ Actual restic repository data in
/k8s-backups/volsync/${APP}/ - ✅ Application data in source PVCs
- ✅ R2/remote backup data
Recovery: Once resources are recreated, VolSync reconnects to existing restic repositories automatically.
Monitoring Recovery
After cleanup and resume:
# Watch PVs being created
watch kubectl get pv | grep volsync
# Watch PVCs binding
watch kubectl get pvc -A | grep volsync
# Watch ReplicationSources
watch kubectl get replicationsource -A
# Check mover pod logs
kubectl logs -n <namespace> <volsync-src-pod> -f
# Verify mounts succeeded
kubectl get events -A --sort-by='.lastTimestamp' | grep -i mount | grep volsync
Quick Reference Commands
# Count resources
echo "PVs: $(kubectl get pv | grep 'volsync-.*-repo-pv' | wc -l)"
echo "Repo PVCs: $(kubectl get pvc -A | grep 'volsync.*repo' | wc -l)"
echo "Cache PVCs: $(kubectl get pvc -A | grep 'cache' | wc -l)"
echo "Sources: $(kubectl get replicationsource -A --no-headers | wc -l)"
echo "Destinations: $(kubectl get replicationdestination -A --no-headers | wc -l)"
# Suspend/Resume
flux suspend kustomization volsync -n storage
flux resume kustomization volsync -n storage
# Scale operator
kubectl scale deployment volsync -n storage --replicas=0
kubectl scale deployment volsync -n storage --replicas=1
# Check volsync kustomization status
kubectl get ks volsync -n storage
Known Failure Modes
Mount failure: "missing required field monitors"
Cause: Missing staticVolume: "true" attribute in the PV spec — the CSI driver can't discover monitors from the ConfigMap without it.
Fix: Add staticVolume: "true" to volumeAttributes.
Mount failure: "No such file or directory"
Cause: rootPath doesn't match the actual filesystem path (e.g. /volsync/${APP} vs /k8s-backups/volsync/${APP}).
Fix: Set rootPath to the full path where the data exists.
Cache PVCs stuck provisioning
Cause: Orphaned RBD volume IDs from rapid delete/create cycles ("Volume ID already exists"). Fix: Delete the stuck PVCs and let VolSync recreate them with fresh UIDs.
Persistent "Volume ID already exists" after mass PVC deletion
Cause: When many PVCs are deleted at once, their PVs linger in Released state and keep holding the Ceph RBD volume IDs. New PVCs reuse those IDs and fail to provision.
Resolution steps:
# 1. Identify the deadlock
kubectl get pvc -A | grep Pending # PVCs stuck
kubectl get pods -A | grep volsync-dst | grep Pending # Mover pods can't start
kubectl logs -n rook-ceph deploy/csi-rbdplugin-provisioner -c csi-rbdplugin --tail=50 | grep "already exists"
# 2. Break the deadlock - delete ReplicationDestinations
while read namespace app; do
kubectl delete replicationdestination -n "$namespace" "${app}-dst" --wait=false
done < apps_list.txt
# 3. Wait for mover pods and PVCs to clean up
sleep 10
# 4. Delete all Released PVs (THE KEY STEP!)
kubectl get pv -o json | jq -r '.items[] | select(.spec.csi.driver == "rook-ceph.rbd.csi.ceph.com") | select(.status.phase == "Released") | .metadata.name' | xargs -r kubectl delete pv
# 5. Optionally restart RBD provisioner for fresh state
kubectl rollout restart deployment -n rook-ceph csi-rbdplugin-provisioner
# 6. Reconcile apps to recreate everything
flux reconcile kustomization -n <namespace> <app>
Why this worked:
- PVCs were deleted → PVs became "Released" but not deleted (reclaimPolicy: Delete doesn't apply immediately)
- Released PVs held references to Ceph RBD volume IDs
- New PVCs got same UIDs, tried to create volumes with existing IDs
- Deleting Released PVs freed the volume IDs in Ceph RBD
- Fresh provisioning succeeded with clean slate
Key Insight: When doing mass PVC migrations, always check for and clean up Released PVs immediately!