Tech ONTAP Blogs

Trident protect 25.06 introduces scheduled full backups

PatricU
NetApp
59 Views

NetApp® Trident™ protect revolutionizes application data management, enhancing the functionality and availability of stateful Kubernetes applications supported by NetApp ONTAP storage systems and the NetApp Trident Container Storage Interface (CSI) storage provisioner. Compatible with a wide range of fully managed and self-managed Kubernetes offerings, Trident protect is your ultimate solution for safeguarding Kubernetes services across diverse platforms and regions.

 

For backing up persistent data to object storage, Trident protect employs a data mover (Restic or, by default, Kopia) to seamlessly transfer data from CSI snapshots of the persistent volumes to object storage. Both data movers perform incremental backups from the initial backup, ensuring an "incremental forever" approach.

 

The "incremental forever" backup strategy - where the first backup is a full backup and all subsequent backups are incremental - provides storage efficiency and faster backup times. However, it comes with certain challenges:

  • Longer restore times
    • Dependency chain - to restore data, you must start with the last full backup and then apply every incremental backup in sequence. This means the restore process is only as fast as the slowest incremental backup in the chain.
    • Cumulative overhead - the more incremental backups there are, the longer the restore process takes, especially if the chain is long or if any incremental backup is corrupted or missing.
  • Increased complexity
    • Management overhead - tracking and managing a long chain of incremental backups can be error-prone, especially in large or dynamic environments.
    • Risk of failure
      • If any single incremental backup in the chain is corrupted or lost, the entire restore process may fail or require manual intervention.
      • As the data is deduplicated, single data corruption could invalidate all restore points at once.
    • Performance bottlenecks
      • I/O and CPU load - restoring from many incremental backups can place a heavy load on storage systems and CPUs, slowing down the entire process.
    • Point-in-time recovery challenges
      • Granularity issues - while incremental forever allows for point-in-time recovery, restoring to a specific moment may require reconstructing the entire chain up to that point, which can be time-consuming.

This can become problematic in these cases:

  • Large data volumes: The disadvantages are amplified when dealing with large datasets or frequent changes.
  • Critical systems: For systems where rapid recovery is essential (e.g., databases, mission-critical applications), the slower restore times can be a major drawback.

In summary, while incremental forever is efficient for storage and backup speed, the trade-off is slower and more complex restores, which can be a critical limitation in recovery scenarios.

 

To help you find the optimal backup strategy for your environment, Trident protect 25.06 introduces the ability to combine "incremental forever" with occasional full backups. This balanced approach enhances storage efficiency while improving restore speeds.

 

In this blog post, I'll guide you through the steps to leverage this new feature for both on-demand and scheduled backups. Let's dive in and elevate your backup strategy!

Prerequisites

To follow along with this guide, ensure you have the following:

  • A Kubernetes cluster with the latest versions of Trident and Trident protect installed, and their associated kubeconfig files
  • A NetApp ONTAP storage back end and Trident with configured storage back ends, storage classes, and volume snapshot classes
  • A configured object storage bucket for storing backups and metadata information
  • A workstation with kubectl configured to use kubeconfig
  • The tridentctl-protect CLI of Trident protect installed on your workstation
  • Admin user permission on the Kubernetes clusters
  • Optionally, the Kopia CLI installed on your workstation

Test environment

First, we quickly go through the setup of the test environment that we used throughout the blog.

We use two simple sample applications – NGINX and Alpine – in different namespaces on our Azure Kubernetes Service (AKS) test cluster.

The NGINX application is deployed in the namespace web and has one persistent volume (PV) backed by NetApp Azure File (ANF) storage, to which we have added some random files:

$ kubectl get all -n web
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-64cdb84b99-pnvnm   1/1     Running   0          5d23h

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web   1/1     1            1           5d23h

NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-64cdb84b99   1         1         1       5d23h

NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                  VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/nginxdata   Bound    pvc-1ff159f4-a3ea-45e5-bdb7-b0b5f3878466   50Gi       RWX            azure-netapp-files-standard   <unset>                 5d23h

$ kubectl -n web exec -it pod/web-64cdb84b99-pnvnm -- df -h /data
Filesystem                                           Size  Used Avail Use% Mounted on
10.21.2.7:/pvc-1ff159f4-a3ea-45e5-bdb7-b0b5f3878466   50G   41M   50G   1% /data
~$ k -n web exec -it pod/web-64cdb84b99-pnvnm -- ls -l /data
total 41164
-rw-r--r-- 1 nobody nogroup    10240 Oct 10 09:41 file1
-rw-r--r-- 1 nobody nogroup 10485760 Oct 16 08:15 file2
-rw-r--r-- 1 nobody nogroup 10485760 Oct 16 08:15 file3
-rw-r--r-- 1 nobody nogroup 10485760 Oct 16 08:15 file4
-rw-r--r-- 1 nobody nogroup 10485760 Oct 16 08:16 file5

We add the web namespace as application web to Trident protect:

$ tridentctl-protect create app web --namespaces web -n web
Application "web" created.

The second sample application is an Alpine container in the namespace alpine with two PVs backed by ANF, each populated with one random file, which we add as application alpine to Trident protect:

$ kubectl get all -n alpine
NAME                          READY   STATUS    RESTARTS   AGE
pod/alpine-6cd7c6fdf7-fm9dm   1/1     Running   0          7m9s

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/alpine   1/1     1            1           7m10s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/alpine-6cd7c6fdf7   1         1         1       7m10s

NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                  VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/alpinedata     Bound    pvc-9601c314-f343-4894-927a-c5ad2ac59644   50Gi       RWX            azure-netapp-files-standard   <unset>                 7m10s
persistentvolumeclaim/alpinedata-2   Bound    pvc-95bd408e-b0c7-4b68-83d0-9d85dadb3e46   50Gi       RWX            azure-netapp-files-standard   <unset>                 7m9s

$ kubectl -n alpine exec -it pod/alpine-6cd7c6fdf7-fm9dm -- ls -l /data /data2
/data:
total 102808
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 13:41 file1

/data2:
total 102808
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 13:46 file1
~$ k -n alpine exec -it pod/alpine-6cd7c6fdf7-fm9dm -- df -h | grep data
                         50.0G    101.0M     49.9G   0% /data
                         50.0G    101.0M     49.9G   0% /data2

$ tridentctl-protect create app alpine --namespaces alpine -n alpine
Application "alpine" created.

We have these two Trident protect applications defined now:

$ tridentctl-protect get application -A
+-----------+--------+------------+-------+-------+
| NAMESPACE |  NAME  | NAMESPACES | STATE |  AGE  |
+-----------+--------+------------+-------+-------+
| alpine    | alpine | alpine     | Ready | 1d3h  |
| web       | web    | web        | Ready | 1d4h  |
+-----------+--------+------------+-------+-------+

As object storage to store the backup data, we use the Azure Storage Blob container demo in the Azure Storage account puneptunetest, which is represented by the Trident protect appVault custom resource (CR) demo:

$ tridentctl-protect get appvault
+------+----------+-----------+-------+---------+--------+
| NAME | PROVIDER |   STATE   | ERROR | MESSAGE |  AGE   |
+------+----------+-----------+-------+---------+--------+
| demo | Azure    | Available |       |         | 19d23h |
+------+----------+-----------+-------+---------+--------+

See the Trident protect documentation for detailed steps and examples how to create an appVault CR and how to set a password for the data mover repository. Take note of the Kopia data mover password you configured here, we’ll need it later on.

Now we’re ready to investigate full backups with Trident protect.

On-demand backups

Let’s start with on-demand backups and see how we can control the creation of full and incremental on-demand backups.

The type of backup is configured by the annotation metadata.annotations.protect.trident.netapp.io/full-backup in Trident protect. If this annotation is set to true, the backup becomes non-incremental. If not specified, the backup follows the default incremental backup setting.

Here’s a sample yaml manifest for a full on-demand backup:

apiVersion: protect.trident.netapp.io/v1
kind: Backup
metadata:
  namespace: my-app-namespace
  name: my-cr-name
  annotations:
    protect.trident.netapp.io/full-backup: "true"
spec:
  applicationRef: my-application
  appVaultRef: appvault-name
  dataMover: Kopia

When creating a full on-demand backup with Trident protect’s powerful CLI, you add the --full-backup flag to the tridentctl-protect create backup command.

Next, we create a full on-demand backup of the alpine application now:

$ tridentctl-protect create backup alpine-full-1 --app alpine --appvault demo --full-backup -n alpine
Backup "alpine-full-1" created.

$ tridentctl-protect get backup -n alpine
+---------------+--------+----------------+-----------+-------+-------+
|     NAME      |  APP   | RECLAIM POLICY |   STATE   | ERROR |  AGE  |
+---------------+--------+----------------+-----------+-------+-------+
| alpine-full-1 | alpine | Retain         | Completed |       | 6m18s |
+---------------+--------+----------------+-----------+-------+-------+

Let’s have a closer look at the corresponding backup yaml manifest once the backup finishes.

$ kubectl -n alpine get backup alpine-full-1 -o yaml
apiVersion: protect.trident.netapp.io/v1
kind: Backup
metadata:
  annotations:
    protect.trident.netapp.io/correlationid: 1f15357e-1330-4478-a902-a826a26d4a67
    protect.trident.netapp.io/full-backup: "true"
  creationTimestamp: "2025-10-21T14:00:51Z"
  finalizers:
  .. ..
  generation: 1
  name: alpine-full-1
  namespace: alpine
  ownerReferences:
  - apiVersion: protect.trident.netapp.io/v1
    kind: Application
    name: alpine
    uid: 8ef17301-234c-4d48-b7c4-2809d592f125
  resourceVersion: "11054926"
  uid: 1736c690-98a2-4a04-977b-94ef1fec264d
spec:
  appVaultRef: demo
  applicationRef: alpine
  cleanupSnapshot: false
  dataMover: Kopia
  reclaimPolicy: Retain
status:
  appArchivePath: alpine_8ef17301-234c-4d48-b7c4-2809d592f125/backups/alpine-full-1_1736c690-98a2-4a04-977b-94ef1fec264d
  appVaultRef: demo
  completionTimestamp: "2025-10-21T14:05:05Z"
  conditions:
.. ..
  - lastTransitionTime: "2025-10-21T14:00:52Z"
    message: Not yet reconciled
    reason: Pending
    status: Unknown
    type: OnFailurePostBackupExecHooksRunCompleted
  latestRepositoryTimestamp: "20251021_140113"
  postBackupExecHooksRunResults: []
  postSnapshotExecHooksRunResults: []
  preBackupExecHooksRunResults: []
  preSnapshotExecHooksRunResults: []
  progress:
    volumeBackups:
    - completionTimestamp: "2025-10-21T14:05:04Z"
      progress:
        bytesCompleted: 104857600
        bytesRemaining: 0
        bytesTotal: 104857600
        updatedAt: "2025-10-21T14:04:01Z"
      pvcUid: 9601c314-f343-4894-927a-c5ad2ac59644
      repositoryPath: alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251021_140113/alpine/alpinedata_9601c314-f343-4894-927a-c5ad2ac59644/
      snapshotID: eb9bdc9f7f54ed1bd57fd63482575a21
      sourceVolumeSnapshot:
        name: snapshot-d9ec9c10-2963-4d4f-beb3-61434d26938f-pvc-9601c314-f343-4894-927a-c5ad2ac59644
        namespace: alpine
      volumeBackupCompleted: true
      volumeBackupCreated: true
      volumeSnapshotContentCopyName: backup-1736c690-98a2-4a04-977b-94ef1fec264d-vsc-e97828fc-99c7-4d3a-88c5-7559fdd3d3a4
      volumeSnapshotCopied: true
      volumeSnapshotCopyDeleted: true
      volumeSnapshotCopyName: backup-1736c690-98a2-4a04-977b-94ef1fec264d-vs-6ada2115-8252-4e91-88c8-fbd227193453
      volumeSnapshotCopyReadyToUse: true
    - completionTimestamp: "2025-10-21T14:04:51Z"
      progress:
        bytesCompleted: 104857600
        bytesRemaining: 0
        bytesTotal: 104857600
        updatedAt: "2025-10-21T14:03:50Z"
      pvcUid: 95bd408e-b0c7-4b68-83d0-9d85dadb3e46
      repositoryPath: alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251021_140113/alpine/alpinedata-2_95bd408e-b0c7-4b68-83d0-9d85dadb3e46/
      snapshotID: 0d7c8b2c34c17d0a48d1529d399e0c52
      sourceVolumeSnapshot:
        name: snapshot-d9ec9c10-2963-4d4f-beb3-61434d26938f-pvc-95bd408e-b0c7-4b68-83d0-9d85dadb3e46
        namespace: alpine
      volumeBackupCompleted: true
      volumeBackupCreated: true
      volumeSnapshotContentCopyName: backup-1736c690-98a2-4a04-977b-94ef1fec264d-vsc-3589ff58-854a-4b64-8f1b-78af102193db
      volumeSnapshotCopied: true
      volumeSnapshotCopyDeleted: true
      volumeSnapshotCopyName: backup-1736c690-98a2-4a04-977b-94ef1fec264d-vs-0d01ecc5-0b44-47d9-a4eb-40821d49eddc
      volumeSnapshotCopyReadyToUse: true
  sourceSnapshotName: backup-1736c690-98a2-4a04-977b-94ef1fec264d
  state: Completed

In the metadata, we can see that the full backup annotation is set correctly to protect.trident.netapp.io/full-backup: "true":

$ kubectl -n alpine get backup alpine-full-1 -o yaml | yq '.metadata.annotations."protect.trident.netapp.io/full-backup"'
true

As we have two PVs to backup, we see two corresponding volumeBackups in the progress section of the yaml, each with its own repositoryPath value:

$ kubectl -n alpine get backup alpine-full-1 -o yaml | yq '.status.progress.volumeBackups[].repositoryPath'
alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251021_140113/alpine/alpinedata_9601c314-f343-4894-927a-c5ad2ac59644/
alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251021_140113/alpine/alpinedata-2_95bd408e-b0c7-4b68-83d0-9d85dadb3e46/ 

The two repositoryPath values allow us to connect to the Kopia repositories that were created during the backup in our Azure Storage container using the Kopia CLI. Let’s connect to the first Kopia repository:

$ kopia repository connect azure --storage-account=puneptunetest --storage-key="<REDACTED>" --container=demo --prefix=$(k -n alpine get backup alpine-full-1 -o yaml | yq '.status.progress.volumeBackups[0].repositoryPath')
Enter password to open repository:

Connected to repository.

NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/Users/patricu/Library/Application Support/kopia/repository.config.update-info.json".

Now we can list the Kopia snapshots in the first Kopia repository, where we have on snapshot corresponding to the first (full) backup, containing the backup of one file:

$ kopia snapshot list --all
root@protect.trident.netapp.io:/sourceVolume
  2025-10-21 16:01:41 CEST k8900e92b09583eec8c3932762fb57e17 104.9 MB drwxrwxrwx files:1 dirs:1 (latest-1)

We see that same situation in the second Kopia repository:

$ kopia repository connect azure --storage-account=puneptunetest --storage-key="<REDACTED>" --container=demo --prefix=$(k -n alpine get backup alpine-full-1 -o yaml | yq '.status.progress.volumeBackups[1].repositoryPath')
Enter password to open repository:

Connected to repository.

NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/Users/patricu/Library/Application Support/kopia/repository.config.update-info.json".

~$ kopia snapshot list --all
root@protect.trident.netapp.io:/sourceVolume
  2025-10-21 16:01:29 CEST k32e40ed699bc3e3d938c0ea65497f95f 104.9 MB drwxrwxrwx files:1 dirs:1 (latest-1)

Now, let’s see what happens when we add another file to the /data directory in the Alpine container and run an incremental backup afterwards.

$ kubectl -n alpine exec -it pod/alpine-6cd7c6fdf7-fm9dm -- ls -l /data /data2
/data:
total 205616
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 13:41 file1
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 14:13 file2

/data2:
total 102808
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 13:46 file1

We start another on-demand backup alpine-inc-1 now, not specifying the --full-backup flag will make it an incremental backup.

$ tridentctl-protect create backup alpine-inc-1 --app alpine --appvault demo -n alpine
Backup "alpine-inc-1" created.

$ tridentctl-protect get backup -n alpine
+---------------+--------+----------------+-----------+-------+-------+
|     NAME      |  APP   | RECLAIM POLICY |   STATE   | ERROR |  AGE  |
+---------------+--------+----------------+-----------+-------+-------+
| alpine-full-1 | alpine | Retain         | Completed |       | 2h45m |
| alpine-inc-1  | alpine | Retain         | Completed |       | 4m15s |
+---------------+--------+----------------+-----------+-------+-------+

Let’s list the full-backup annotation next to each backup in the alpine namespace:

$ for i in $(k -n alpine get backups | awk '/alpine/ {print $1}'); do type=$(k -n alpine get backup ${i} -o yaml | yq '.metadata.annotations."protect.trident.netapp.io/full-backup"'); printf "${i}\t ${type} \n";done

alpine-full-1	 true
alpine-inc-1	 null

Checking for the repositoryPath values in the newly created backup alpine-inc-1, we see that the values didn’t change (look at the timestamp ../kopia_<timestamp>/ in the middle of the repositoryPath name):

$ kubectl -n alpine get backup alpine-inc-1 -o yaml | yq '.status.progress.volumeBackups[].repositoryPath'
alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251021_140113/alpine/alpinedata_9601c314-f343-4894-927a-c5ad2ac59644/
alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251021_140113/alpine/alpinedata-2_95bd408e-b0c7-4b68-83d0-9d85dadb3e46/

The incremental backup data was written to the same Kopia repositories, which we can see when looking at the Kopia repositories again. The first Kopia repository has a second, new snapshot as we added another file to /data:

$ kopia repository connect azure --storage-account=puneptunetest --storage-key="AUdQ0hKBW1xw6e6GwqxHGXuiEdrrrmWUY1Ft9E26yDx7dylKW/CUsbGG2/ff36RJ2K2PnAoHAUtC+AStgmsdiA==" --container=demo --prefix=$(k -n alpine get backup alpine-inc-1 -o yaml | yq '.status.progress.volumeBackups[0].repositoryPath')
Enter password to open repository:

Connected to repository.

NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/Users/patricu/Library/Application Support/kopia/repository.config.update-info.json".

~$ kopia snapshot list --all
root@protect.trident.netapp.io:/sourceVolume
  2025-10-21 16:01:41 CEST k8900e92b09583eec8c3932762fb57e17 104.9 MB drwxrwxrwx files:1 dirs:1 (latest-2)
  2025-10-21 18:42:47 CEST k00275f811aca280b34c4c9599c1a7278 209.7 MB drwxrwxrwx files:2 dirs:1 (latest-1)

While the second Kopia repository has an additional, identical snapshot as we didn’t change anything in the /data2 directory.

$ kopia repository connect azure --storage-account=puneptunetest --storage-key="AUdQ0hKBW1xw6e6GwqxHGXuiEdrrrmWUY1Ft9E26yDx7dylKW/CUsbGG2/ff36RJ2K2PnAoHAUtC+AStgmsdiA==" --container=demo --prefix=$(k -n alpine get backup alpine-inc-1 -o yaml | yq '.status.progress.volumeBackups[1].repositoryPath')
Enter password to open repository:

Connected to repository.

NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/Users/patricu/Library/Application Support/kopia/repository.config.update-info.json".

~$ kopia snapshot list --all
root@protect.trident.netapp.io:/sourceVolume
  2025-10-21 16:01:29 CEST k32e40ed699bc3e3d938c0ea65497f95f 104.9 MB drwxrwxrwx files:1 dirs:1 (latest-1..2)
  + 1 identical snapshots until 2025-10-21 18:42:48 CEST

In a last step, let’s add a new file to the /data2 directory and run another full backup afterwards.

$ kubectl -n alpine exec -it pod/alpine-6cd7c6fdf7-fm9dm -- ls -l /data /data2
/data:
total 205616
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 13:41 file1
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 14:13 file2

/data2:
total 205616
-rw-r--r--    1 nobody   nobody   104857600 Oct 21 13:46 file1
-rw-r--r--    1 nobody   nobody   104857600 Oct 22 07:56 file2

$ tridentctl-protect create backup alpine-full-2 --app alpine --appvault demo --full-backup -n alpine
Backup "alpine-full-2" created.

$ tridentctl-protect get backups -n alpine
+---------------+--------+----------------+-----------+-------+--------+
|     NAME      |  APP   | RECLAIM POLICY |   STATE   | ERROR |  AGE   |
+---------------+--------+----------------+-----------+-------+--------+
| alpine-full-1 | alpine | Retain         | Completed |       | 19h57m |
| alpine-full-2 | alpine | Retain         | Completed |       | 1h50m  |
| alpine-inc-1  | alpine | Retain         | Completed |       | 17h16m |
+---------------+--------+----------------+-----------+-------+--------+

$ printf "BACKUP \t\t FULL?\n"; for i in $(k -n alpine get backups | awk '/alpine/ {print $1}'); do type=$(k -n alpine get backup ${i} -o yaml | yq '.metadata.annotations."protect.trident.netapp.io/full-backup"'); printf "${i}\t ${type} \n";done
BACKUP 	 	 FULL?
alpine-full-1	 true
alpine-full-2	 true
alpine-inc-1	 null

Checking the repositoryPath values of the second full backup alpine-full-2, we see that new Kopia repositories were created (watch the timestamp ../kopia_<timestamp>/ in the middle of the repositoryPath name).

$ kubectl -n alpine get backup alpine-full-2 -o yaml | yq '.status.progress.volumeBackups[].repositoryPath'
alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251022_080816/alpine/alpinedata_9601c314-f343-4894-927a-c5ad2ac59644/
alpine_8ef17301-234c-4d48-b7c4-2809d592f125/kopia_20251022_080816/alpine/alpinedata-2_95bd408e-b0c7-4b68-83d0-9d85dadb3e46/

Each of the new Kopia repositories now contains one Kopia snapshot, protecting the two data files in each directory /data and /data2:

$ kopia repository connect azure --storage-account=puneptunetest --storage-key="AUdQ0hKBW1xw6e6GwqxHGXuiEdrrrmWUY1Ft9E26yDx7dylKW/CUsbGG2/ff36RJ2K2PnAoHAUtC+AStgmsdiA==" --container=demo --prefix=$(k -n alpine get backup alpine-full-2 -o yaml | yq '.status.progress.volumeBackups[0].repositoryPath')
Enter password to open repository:

Connected to repository.

NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/Users/patricu/Library/Application Support/kopia/repository.config.update-info.json".

~$ kopia snapshot list --all
root@protect.trident.netapp.io:/sourceVolume
  2025-10-22 10:08:28 CEST k74861c2a36fa0237c7e601638b2d3b9f 209.7 MB drwxrwxrwx files:2 dirs:1 (latest-1)
$ kopia repository connect azure --storage-account=puneptunetest --storage-key="AUdQ0hKBW1xw6e6GwqxHGXuiEdrrrmWUY1Ft9E26yDx7dylKW/CUsbGG2/ff36RJ2K2PnAoHAUtC+AStgmsdiA==" --container=demo --prefix=$(k -n alpine get backup alpine-full-2 -o yaml | yq '.status.progress.volumeBackups[1].repositoryPath')
Enter password to open repository:

Connected to repository.

NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use.
To disable this behavior, set environment variable KOPIA_CHECK_FOR_UPDATES=false
Alternatively you can remove the file "/Users/patricu/Library/Application Support/kopia/repository.config.update-info.json".

~$ kopia snapshot list --all
root@protect.trident.netapp.io:/sourceVolume
  2025-10-22 10:08:28 CEST kc054b7ac1b577b6d627bc28fd87d5177 209.7 MB drwxrwxrwx files:2 dirs:1 (latest-1)

Scheduled backups

Now that we examined how full on-demand backups work in Trident protect, let’s see how can leverage this new capability for scheduled backups with Trident protect’s scheduler.

With the scheduler, you can schedule non-incremental full backups by using the metadata.annotations.protect.trident.netapp.io/full-backup-rule annotation. By default, all backups are incremental. You can set it to Always for constant full backups for all backup granularities, or for daily granularity, you can specify the weekdays (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday) on which a full backup should occur. When using the Trident protect CLI to create a schedule, you can use the --full-backup-rule flag to specify the weekdays for full backups with a daily granularity of the backup schedule or set it to Always for constant full backups.

 

Let’s give it a try by creating a schedule with tridentctl-protect for daily backups of our second sample application web. We want the daily backups to run at 12:12 UTC and have the scheduler create full backups on Monday, Wednesday, Friday, and Sunday, while retaining the last 10 backups.

$ tridentctl-protect create schedule --app web --appvault demo --backup-retention 10 --granularity Daily --hour 12 --minute 12 --full-backup-rule "Monday,Wednesday,Friday,Sunday" -n web
Schedule "web-qfr01u" created.

A kubectl describe of the newly created schedule shows us the correct setting of the full-backup-rule annotation:

$ kubectl -n web describe schedule web-qfr01u
Name:         web-qfr01u
Namespace:    web
Labels:       <none>
Annotations:  protect.trident.netapp.io/full-backup-rule: Monday,Wednesday,Friday,Sunday
API Version:  protect.trident.netapp.io/v1
Kind:         Schedule
Metadata:
  Creation Timestamp:  2025-10-16T08:51:51Z
  Generation:          1
  Owner References:
    API Version:     protect.trident.netapp.io/v1
    Kind:            Application
    Name:            web
    UID:             0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62
  Resource Version:  12337070
  UID:               97f37f06-9c3e-4af6-8db0-9ba99b13eaf4
Spec:
  App Vault Ref:          demo
  Application Ref:        web
  Backup Retention:       10
  Data Mover:             Kopia
  Day Of Month:
  Day Of Week:
  Enabled:                true
  Granularity:            Daily
  Hour:                   12
  Minute:                 12
  Recurrence Rule:
  Replication Retention:  0
  Snapshot Retention:     0
Events:                <none>

We check back after the first backup run on Thursday, Oct 16, 2025:

$ date
Thu Oct 16 14:46:07 CEST 2025
$ tridentctl-protect get backup -n web
+----------------------------+-----+----------------+-----------+-------+--------+
|            NAME            | APP | RECLAIM POLICY |   STATE   | ERROR |  AGE   |
+----------------------------+-----+----------------+-----------+-------+--------+
| daily-90652-20251016121200 | web | Retain         | Completed |       | 34m10s |
+----------------------------+-----+----------------+-----------+-------+--------+

As this is the initial scheduled backup, it is implicitly a full backup, despite its full-backup annotation not being set to true:

$ kubectl -n web get backup daily-90652-20251016121200 -o yaml | yq '.metadata.annotations."protect.trident.netapp.io/full-backup"'
null

The application web has only one PV, so only one Kopia repository was created for the initial scheduled backup:

$ kubectl -n web get backup daily-90652-20251016121200 -o yaml | yq '.status.progress.volumeBackups[].repositoryPath'
web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/

Checking back on Friday next week, we confirm that the scheduled backups on Friday, Sunday, Monday, Wednesday, and again Friday are full backups, and each full backup was written to a new Kopia repository (watch the timestamp ../kopia_<timestamp>/ in the middle of the repositoryPath name):

$ tridentctl-protect get backup -n web
+----------------------------+-----+----------------+-----------+-------+--------+
|            NAME            | APP | RECLAIM POLICY |   STATE   | ERROR |  AGE   |
+----------------------------+-----+----------------+-----------+-------+--------+
| daily-90652-20251016121200 | web | Retain         | Completed |       | 8d     |
| daily-90652-20251017121200 | web | Retain         | Completed |       | 7d     |
| daily-90652-20251018121200 | web | Retain         | Completed |       | 6d     |
| daily-90652-20251019121200 | web | Retain         | Completed |       | 5d     |
| daily-90652-20251020121200 | web | Retain         | Completed |       | 4d     |
| daily-90652-20251021121200 | web | Retain         | Completed |       | 3d     |
| daily-90652-20251022121200 | web | Retain         | Completed |       | 2d     |
| daily-90652-20251023121200 | web | Retain         | Completed |       | 1d     |
| daily-90652-20251024121200 | web | Retain         | Completed |       | 37m29s |
+----------------------------+-----+----------------+-----------+-------+--------+

$ printf "BACKUP \t\t\t\t FULL? \t REPO \n"; for i in $(k -n web get backups | awk '/daily/ {print $1}'); do type=$(k -n web get backup ${i} -o yaml | yq '.metadata.annotations."protect.trident.netapp.io/full-backup"'); repo=$(k -n web get backup ${i} -o yaml | yq '.status.progress.volumeBackups[0].repositoryPath'); printf "${i}\t ${type}\t ${repo}\n"; done
BACKUP 				 FULL? REPO
daily-90652-20251016121200	 null	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251017121200	 true	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251017_121216/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251018121200	 null	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251017_121216/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251019121200	 true	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251019_121213/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251020121200	 true	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251020_121215/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251021121200	 null	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251020_121215/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251022121200	 true	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251022_121215/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251023121200	 null	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251022_121215/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/
daily-90652-20251024121200	 true	 web_0a87fec1-1c9a-4ef4-bee2-9b94a3df5c62/kopia_20251024_121214/web/nginxdata_1ff159f4-a3ea-45e5-bdb7-b0b5f3878466/

Conclusion and call to action

In conclusion, NetApp® Trident™ protect 25.06 significantly enhances your Kubernetes application data management by introducing the ability to schedule full backups alongside incremental ones. This new feature offers a compromise between storage efficiency and faster restore times, addressing the challenges posed by the "incremental forever" backup strategy. By leveraging both on-demand and scheduled full backups, you can ensure robust data protection and swift recovery for your critical systems.

 

Now that you're equipped with the knowledge to implement these capabilities, it's time to put them into action! Follow the steps outlined in this guide to set up and optimize your backup strategy with Trident protect. Ensure you have the prerequisites in place and start creating on-demand and scheduled backups to safeguard your Kubernetes applications effectively.

 

Don't wait until it's too late—enhance your data protection strategy today with Trident protect 25.06!

 

For more detailed instructions and examples, refer to the Trident protect documentation. If you have any questions or need further assistance, feel free to reach out to our support team or join the NetApp community forums.

 

Happy backing up!

 

 

Public