Node Agent and Kopia
The node-agent DaemonSet provides file-level volume backup/restore using
Kopia. This is the recommended path for workloads where CSI snapshots are
unavailable or too coarse-grained.
Architecture¶
node-agent runs on every node. When a DataUpload/DataDownload CRD is
created, velero-server selects the node-agent instance running on the
same node as the pod whose PVC needs backing up.
velero-server
│ creates DataUpload CRD (spec.node = "node-A")
│
▼
node-agent pod on node-A
│ DataUploadController reconciles
│ mounts PVC via hostPath: /var/lib/kubelet/pods/.../volumes/...
│
▼ (Kopia client)
Kopia Repository on ObjectStore (BSL)
node-agent must run on the same node as the pod whose PVC it's backing up,
because it accesses the PVC via hostPath through the kubelet volume manager
paths (typically /var/lib/kubelet/pods/<pod-uid>/volumes/<plugin>/<volume-name>).
The node-agent DaemonSet mounts /host_pods pointing at the kubelet pods
directory.
Kopia fundamentals¶
Kopia is a content-addressed, deduplicated backup engine.
Velero embeds Kopia as a Go library (not as an external binary, unlike the
old Restic integration which shelled out to a
restic binary).
Here are some key concepts:
Repository: The central storage entity. Velero creates one Kopia repository
per BSL, shared by all node-agent instances. Repository access requires a
password stored in a Kubernetes Secret (velero-repo-credentials by default).
Snapshot: Each DataUpload creates one Kopia snapshot: a versioned tree of
content hashes. Deduplication happens content-addressably across snapshots:
unchanged blocks are never re-uploaded.
Content-Addressable Storage: File data is chunked, hashed (BLAKE3 or SHA256), and stored by hash. If two pods have identical files, those bytes are stored once. This makes incremental backups very efficient for large PVCs with small change sets.
Repository maintenance: Kopia repositories accumulate unreferenced content
when old snapshots are deleted. maintenance run (full/quick) prunes this.
Velero runs maintenance automatically via BackupRepositoryController, but
you need to understand it for large-scale deployments where maintenance can lag.
DataUpload and DataDownload CRDs¶
These CRDs are created by velero-server and reconciled by node-agent. They
carry the specifics of what to upload/download and status back to
velero-server.
| Field | Type | Description |
|---|---|---|
spec.snapshotType |
string |
CSI (current) or Restic (legacy). Determines which uploader is used. |
spec.sourceNamespace |
string |
Namespace of the PVC being backed up. |
spec.sourcePVC |
string |
Name of the PVC. node-agent resolves this to a host path. |
spec.backupStorageLocation |
string |
Which BSL to write Kopia repository data to. |
spec.operationTimeout |
duration |
How long node-agent waits before failing the upload. Tune for large PVCs. |
spec.node |
string |
Target node name. node-agent only reconciles DataUploads targeted at its own node. |
status.snapshotID |
string |
Kopia snapshot ID written by node-agent on success. Used by DataDownload to restore. |
status.progress |
Progress |
BytesDone / TotalBytes: live progress from the Kopia upload stream. |
node-agent Configuration¶
# ConfigMap velero/node-agent
apiVersion: v1
kind: ConfigMap
metadata:
name: node-agent
namespace: velero
data:
# Max concurrent Kopia operations per node
concurrentRepoOperations: "3"
# Kopia upload workers per operation
parallelFilesUpload: "10"
# Timeout for acquiring repository lock
backupRepositoryLockCheckTimeout: "1m"
# How many maintenance records to keep
keepLatestMaintenance: "3"
Kopia Repository Password Management¶
# Default secret created by velero install
kubectl get secret -n velero velero-repo-credentials -o yaml
# Rotate the password (requires all node-agents to restart to pick up the new secret)
kubectl create secret generic velero-repo-credentials \
--from-literal=repository-password="$(openssl rand -base64 32)" \
--dry-run=client -o yaml | kubectl apply -f -
Repository Password Loss
If the velero-repo-credentials secret is lost and no backup of it exists,
the Kopia repository is permanently unreadable. Include this secret in your
cluster secrets backup strategy.
Monitoring Uploads¶
# Watch DataUpload objects for active backups
kubectl get dataupload -n velero -w
# Progress for a specific upload
kubectl get dataupload -n velero my-upload -o jsonpath='{.status.progress}'
# node-agent logs on the relevant node
kubectl logs -n velero -l name=node-agent --field-selector spec.nodeName=<node-name>
Data Mover Microservice Model (modern path)¶
For CSI-based volumes, Velero now uses a data mover microservice pattern instead of the node-agent DaemonSet. This is the recommended path for new deployments:
velero-server
│ PVCBackupItemAction creates:
│ 1. VolumeSnapshot
│ 2. DataUpload CR
│
▼
DataUploadController
│ Creates intermediate PVC from snapshot
│ Spawns data mover pod mounting that PVC
│
▼
Data Mover Pod (BackupMicroService)
│ Connects to Kopia repository
│ Reads data from mounted PVC
│ Uploads to BSL
│ Emits completion event with snapshotID
│
▼
DataUploadController marks DataUpload Completed
Cleans up intermediate PVC + mover pod
Key differences from node-agent DaemonSet path:
| Aspect | Node-Agent (legacy) | Data Mover (modern) |
|---|---|---|
| Pod model | DaemonSet (always running) | Per-operation pod (scale to zero) |
| Volume access | hostPath via kubelet dirs | PVC from CSI snapshot |
| CRDs | PodVolumeBackup/Restore | DataUpload/DataDownload (v2alpha1) |
| Concurrency control | Per-node concurrency config | VGDP counter per node |
| Result reporting | CR status update | Kubernetes Events |
| CSI required | No | Yes (snapshot capability) |
Both paths coexist
The node-agent DaemonSet path is still used for non-CSI volumes and for
the defaultVolumesToFsBackup option. The data mover microservice path
is used when CSI snapshots + data movement are enabled
(snapshotMoveData: true).
Restic Migration¶
If you're on a pre-v1.13 deployment using Restic:
# Migrate existing Restic backups to Kopia-readable format
velero backup-location set default --provider velero.io/aws # or your provider
# Restic repos and Kopia repos are incompatible — old Restic backups
# can still be RESTORED with the Restic uploader even after switching to Kopia
# for new backups. Set spec.uploaderType: restic on the restore if needed.