Skip to main content
NetApp container solutions

Back up and restore container applications in a vSphere Kubernetes Service cluster using Trident Protect

Contributors banum-netapp

Back up and restore container applications in a vSphere Kubernetes Service (VKS) cluster using Trident Protect snapshots and backups. This procedure includes creating an AppVault using ONTAP S3 object storage, configuring Trident Protect to capture application data including Kubernetes resource objects and persistent volumes, and restoring the data when necessary.

Container applications running in a VKS cluster are managed as Kubernetes workloads in worker node namespaces. It is important to protect both the application metadata and the persistent volumes so that, if they are lost or corrupted, you can recover them.

The persistent volumes of VKS applications can be backed by ONTAP storage integrated with the VKS cluster using Trident CSI. This procedure uses Trident Protect to create application snapshots, whose metadata is stored in ONTAP object storage while volume snapshot data remains on the storage backend, and backups, whose data is copied to ONTAP object storage. You can restore from either a snapshot or a backup when needed.

Trident Protect enables snapshots, backups, restoration, and disaster recovery of applications on a Kubernetes cluster. Data that can be protected with Trident Protect includes Kubernetes resource objects associated with the applications and their persistent volumes.

The following are the versions of the various components used for the examples in this section

Install Trident Protect on the vSphere Kubernetes cluster

Install Trident Protect

Use Helm to install Trident Protect on the vSphere Kubernetes Service cluster. The examples in this section use tridentctl-protect, the Trident Protect CLI. Install it by following the instructions in the Trident Protect CLI documentation.
Additional methods of installing Trident Protect are documented in the Trident Protect documentation.

First, create and label the trident-protect namespace. The enforce=privileged label is required because Trident Protect runs privileged workloads. Without it, the Kubernetes pod security admission controller will block Trident Protect pods from starting.

kubectl create namespace trident-protect
kubectl label namespace trident-protect pod-security.kubernetes.io/enforce=privileged

Then add the Helm repository and install Trident Protect into the namespace.

helm repo add netapp-trident-protect https://netapp.github.io/trident-protect-helm-chart
helm install trident-protect netapp-trident-protect/trident-protect --set clusterName=<name-of-cluster> --version 100.2606.0 --namespace trident-protect
Note If Trident Protect pods fail to start with a PodSecurity admission error, the pod security labels may not have been applied to the trident-protect namespace before installation. Use --overwrite to reapply them, then verify the pods start.
kubectl label namespace trident-protect pod-security.kubernetes.io/enforce=privileged --overwrite
kubectl get pods -n trident-protect
Trident Protect pods running
Trident Protect pods running

Create App Vault for Object Storage

Create AppVault

Before creating snapshots and backups for an application, you must configure object storage in Trident Protect. This is done by creating an AppVault CR. Only administrators can create and configure an AppVault CR.

AppVault objects are the Kubernetes custom resource representation of a storage bucket. An AppVault CR contains the configurations necessary for a bucket to be used in protection operations, such as backups, snapshots, restore operations, and SnapMirror replication.

The following steps create an AppVault CR configured for ONTAP S3:
. Create an S3 object store server in the SVM in the ONTAP cluster.
. Create a bucket in the object store server.
. Create an S3 user in the SVM. Keep the access key and the secret key in a safe location.

+
NOTE: Trident Protect requires the S3 user to have at least PutObject, GetObject, ListBucket, and DeleteObject permissions. Without these permissions, AppVault backup and restore operations fail with access-denied errors. For details, see Trident Protect AppVault documentation.
. In the VKS cluster, create a secret to store the ONTAP S3 credentials.
. Create an AppVault object for ONTAP S3.

Configure Trident Protect AppVault for ONTAP S3

Important The manifest below uses HTTPS with certificate validation enabled, which is required for production. If your ONTAP S3 endpoint uses a certificate signed by a public CA already trusted by the cluster, no additional certificate configuration is needed. If it uses a self-signed or internal CA certificate, provide the custom root CA certificate using the rootCA field as shown in the manifest. See the lab-only variant at the end of this section if you need an HTTP configuration for isolated non-production testing.
# alias tp='tridentctl-protect'

# cat appvault-secret.yaml
apiVersion: v1
data:
  accessKeyID: <base64 encoded access key>
  secretAccessKey: <base64 encoded secret access key>
# Alternatively, remove the data section above and use:
# stringData:
#   accessKeyID: "<access key of S3>"
#   secretAccessKey: "<secret access key of S3>"
kind: Secret
metadata:
  name: ontap-s3-appvault-secret1
  namespace: trident-protect
type: Opaque

# cat appvault.yaml
apiVersion: protect.trident.netapp.io/v1
kind: AppVault
metadata:
  name: ontap-s3-appvault1
  namespace: trident-protect
spec:
  providerConfig:
    azure:
      accountName: ""
      bucketName: ""
      endpoint: ""
    gcp:
      bucketName: ""
      projectID: ""
    s3:
      bucketName: trident-protect
      endpoint: <lif for S3 access>
      secure: "true"
      skipCertValidation: "false"
      # If your ONTAP S3 endpoint uses a certificate signed by a public CA
      # already trusted by the cluster, no additional certificate config is needed.
      # If it uses a self-signed or internal CA certificate, provide the
      # root CA certificate:
      # rootCA: <root CA certificate>
  providerCredentials:
    accessKeyID:
      valueFromSecret:
        key: accessKeyID
        name: ontap-s3-appvault-secret1
    secretAccessKey:
      valueFromSecret:
        key: secretAccessKey
        name: ontap-s3-appvault-secret1
  providerType: OntapS3

# kubectl create -f appvault-secret.yaml -n trident-protect
# kubectl create -f appvault.yaml -n trident-protect

Lab-only variant (HTTP, no certificate validation)

Only use the following s3 settings in an isolated lab environment where no sensitive data is present. Do not use these settings in production.

    s3:
      bucketName: trident-protect
      endpoint: <lif for S3 access>
      secure: "false"
      skipCertValidation: "true"
ONTAP S3 AppVault created

Create a Trident Protect Application

Create a Trident Protect Application

This example covers data protection for the sample PostgreSQL application, which is installed in the postgres namespace. For details about installation, see Deploy VKS workloads using NetApp Storage.

Note The sample PostgreSQL PVCs request RWO access mode. The access mode must be explicitly specified in the PVC manifest; Trident does not automatically select an access mode based on the storage protocol. RWX is supported for NAS-backed PVCs, and SAN-backed PVCs can support RWX with additional configuration. For more information, refer to the Trident Protect documentation.

In the example, the postgres namespace has one application and all resources of the namespace are included when creating the Trident Protect Application.

# alias tp='tridentctl-protect'
# tp create app postgres-app --namespaces postgres -n postgres --dry-run > postgres-app.yaml

# cat postgres-app.yaml
apiVersion: protect.trident.netapp.io/v1
kind: Application
metadata:
  name: postgres-app
spec:
  includedNamespaces:
  - namespace: postgres
  resourceFilter: {}
# kubectl create -f postgres-app.yaml -n postgres
Trident Protect application created

Protect the app by creating a backup

Create Backups

Create an On-demand Backup

Create a backup for the postgres-app created previously that includes all resources in the postgres namespace. Provide the appvault name where the backups will be stored.

# cat postgres-backup-on-demand.yaml
apiVersion: protect.trident.netapp.io/v1
kind: Backup
metadata:
  name: postgres-backup-on-demand
spec:
  appVaultRef: ontap-s3-appvault1
  applicationRef: postgres-app
  cleanupSnapshot: true
  replicateSnapshot: false
kubectl create -f postgres-backup-on-demand.yaml -n postgres
On-demand backup created
Application protection state is partial

Create Backups on a Schedule

Create a schedule for the backups specifying the granularity and the number of backups to retain.

# tp create schedule backup-schedule1 --app postgres-app --appvault ontap-s3-appvault1 --granularity Hourly --minute 45 --backup-retention 1 -n postgres --dry-run>postgres-backup-schedule1.yaml

#cat postgres-backup-schedule1.yaml
apiVersion: protect.trident.netapp.io/v1
kind: Schedule
metadata:
  name: backup-schedule1
  namespace: postgres
spec:
  appVaultRef: ontap-s3-appvault1
  applicationRef: postgres-app
  backupRetention: "1"
  dayOfMonth: ""
  dayOfWeek: ""
  enabled: true
  granularity: Hourly
  hour: ""
  minute: "45"
  recurrenceRule: ""
  replicationRetention: "0"
  runImmediately: false
  snapshotRetention: "0"
# kubectl create -f postgres-backup-schedule1.yaml -n postgres
Backup schedule created
Backup schedule

Restore from Backup

Restore from Backups

Restore the application to the same namespace

In this example, the backup postgres-backup-on-demand contains the backup for the postgres-app.

Before deleting the application, verify that the backup you plan to restore from is in the Completed state. An in-progress or failed backup cannot be used to restore the application.

kubectl get backup -n postgres

After confirming that the backup state is Completed, delete the postgres application and ensure that the PVCs and pod objects are deleted from the namespace "postgres".

Postgres-app objects
Postgres-app objects
Postgres-app objects deleted

Now, create a backup-in-place restore object.

# tp create bir postgres-app-restore --backup postgres/postgres-backup-on-demand -n postgres --dry-run>postgres-app-bir.yaml

# cat postgres-app-bir.yaml
apiVersion: protect.trident.netapp.io/v1
kind: BackupInplaceRestore
metadata:
  annotations:
    protect.trident.netapp.io/max-parallel-restore-jobs: "25"
  name: postgres-app-restore
  namespace: postgres
spec:
  appArchivePath: postgres-app_314bbaf6-2ce3-4065-b3c1-ab85c7bb7c7c/backups/postgres-backup-on-demand_63efc9d1-92d7-45ce-84fe-846ce7db7b29
  appVaultRef: ontap-s3-appvault1
  cleanUpAdditionalExecHooks: true
  cleanUpArchivedExecHooks: false
  resourceFilter: {}
  runArchivedExecHooks: true

# kubectl create -f postgres-app-bir.yaml -n postgres
Backup in-place restore created

Verify that the postgres application deployment, service, pod, and PVCs are restored.

Application restored

Restore the application to a different namespace

First, create a new namespace to which you want to restore the app, in this example postgres2.
An hourly backup created by the schedule is now available for the postgres-app. Use this backup to restore the application to the new namespace postgres2.

Hourly backup available
# tp create backuprestore --appvault ontap-s3-appvault1 --path postgres-app_314bbaf6-2ce3-4065-b3c1-ab85c7bb7c7c/backups/hourly-cbd11-20260831124500_2b57bda9-5cb0-4c8f-ae7a-20f94c23c3b9 --namespace-mapping postgres:postgres2 -n postgres2 --dry-run>postgres-app-postgres2-br.yaml
Note To get the path for the backup, use the command kubectl get backups <backup-name> -n postgres -o jsonpath='{.status.appArchivePath}'.
Hourly backup path
apiVersion: protect.trident.netapp.io/v1
kind: BackupRestore
metadata:
  annotations:
    protect.trident.netapp.io/max-parallel-restore-jobs: "25"
  name: postgres-app-z2woek
  namespace: postgres2
spec:
  appArchivePath: postgres-app_314bbaf6-2ce3-4065-b3c1-ab85c7bb7c7c/backups/hourly-cbd11-20260831124500_2b57bda9-5cb0-4c8f-ae7a-20f94c23c3b9
  appVaultRef: ontap-s3-appvault1
  cleanUpAdditionalExecHooks: true
  cleanUpArchivedExecHooks: false
  namespaceMapping:
  - destination: postgres2
    source: postgres
  resourceFilter: {}
  runArchivedExecHooks: true
  skipApplicationCreation: false

# kubectl create -f postgres-app-postgres2-br.yaml -n postgres2
Backup restore created

Verify that the postgres application objects and PVCs are created in the new namespace postgres2.

Postgres application restored in the new namespace

Protect the app using Snapshots

Create Snapshots

Create an on-demand snapshot
Create a snapshot for the app and specify the AppVault where the snapshot metadata will be stored. The volume snapshot data itself is not copied to object storage — it remains on the storage backend.

# tp create snapshot postgres-app-snapshot-ondemand --app postgres-app --appvault ontap-s3-appvault1 -n postgres --dry-run>postgres-app-snapshot-ondemand.yaml

# cat postgres-app-snapshot-ondemand.yaml
apiVersion: protect.trident.netapp.io/v1
kind: Snapshot
metadata:
  name: postgres-app-snapshot-ondemand
  namespace: postgres
spec:
  appVaultRef: ontap-s3-appvault1
  applicationRef: postgres-app
  cleanupSnapshot: false
  completionTimeout: 0s
  volumeSnapshotsCreatedTimeout: 0s
  volumeSnapshotsReadyToUseTimeout: 0s

# kubectl create -f postgres-app-snapshot-ondemand.yaml
snapshot.protect.trident.netapp.io/postgres-app-snapshot-ondemand created
On-demand snapshot

Create a schedule for snapshots
Create a schedule for the snapshots. Specify the granularity and the number of snapshots to be retained.

# tp create schedule snapshot-schedule1 --app postgres-app --appvault ontap-s3-appvault1 --granularity Hourly --minute 55 --snapshot-retention 1 -n postgres --dry-run>postgres-app-snapshot-schedule1.yaml

# cat postgres-app-snapshot-schedule1.yaml
apiVersion: protect.trident.netapp.io/v1
kind: Schedule
metadata:
  name: snapshot-schedule1
  namespace: postgres
spec:
  appVaultRef: ontap-s3-appvault1
  applicationRef: postgres-app
  backupRetention: "0"
  dayOfMonth: ""
  dayOfWeek: ""
  enabled: true
  granularity: Hourly
  hour: ""
  minute: "55"
  recurrenceRule: ""
  replicationRetention: "0"
  runImmediately: false
  snapshotRetention: "1"

# kubectl create -f postgres-app-snapshot-schedule1.yaml
schedule.protect.trident.netapp.io/snapshot-schedule1 created
schedule and snapshot created on a schedule

Restore from Snapshot

Restore from Snapshot

Restore the application from a snapshot to the same namespace

Before deleting the application, verify that the snapshot you plan to restore from is in the Completed state. An in-progress or failed snapshot cannot be used to restore the application.

kubectl get snapshot -n postgres

After confirming that the snapshot state is Completed, delete the application objects and the PVCs for postgres from the postgres namespace.

Application objects deleted

Create a snapshot-in-place-restore object from the snapshot.

# tp create sir postgres-restore-from-snapshot --snapshot postgres/hourly-f1dd9-20260831135500 -n postgres --dry-run > postgres-sir.yaml

# cat postgres-sir.yaml
apiVersion: protect.trident.netapp.io/v1
kind: SnapshotInplaceRestore
metadata:
  name: postgres-restore-from-snapshot
  namespace: postgres
spec:
  appArchivePath: postgres-app_314bbaf6-2ce3-4065-b3c1-ab85c7bb7c7c/snapshots/20260831135500_hourly-f1dd9-20260831135500_36c2bd2a-9405-4424-88a7-75e6bbc5b315
  appVaultRef: ontap-s3-appvault1
  cleanUpAdditionalExecHooks: true
  cleanUpArchivedExecHooks: false
  resourceFilter: {}
  runArchivedExecHooks: true

# kubectl create -f postgres-sir.yaml
snapshotinplacerestore.protect.trident.netapp.io/postgres-restore-from-snapshot created

Verify that the application's objects and PVCs are created in the postgres namespace.

Postgres app restored from snapshot

Restore the application from a snapshot to a different namespace

Delete the application in the postgres2 namespace previously restored from the backup.

Delete application objects and PVCs

Create the snapshot restore object from the snapshot and provide the namespace mapping.

# tp create sr postgres-sr --snapshot postgres/hourly-f1dd9-20260831135500 --namespace-mapping postgres:postgres2 -n postgres2 --dry-run>postgres-sr.yaml

# cat postgres-sr.yaml
apiVersion: protect.trident.netapp.io/v1
kind: SnapshotRestore
metadata:
  name: postgres-sr
  namespace: postgres2
spec:
  appArchivePath: postgres-app_314bbaf6-2ce3-4065-b3c1-ab85c7bb7c7c/snapshots/20260831135500_hourly-f1dd9-20260831135500_36c2bd2a-9405-4424-88a7-75e6bbc5b315
  appVaultRef: ontap-s3-appvault1
  cleanUpAdditionalExecHooks: true
  cleanUpArchivedExecHooks: false
  namespaceMapping:
  - destination: postgres2
    source: postgres
  resourceFilter: {}
  runArchivedExecHooks: true
  skipApplicationCreation: false

# kubectl create -f postgres-sr.yaml
snapshotrestore.protect.trident.netapp.io/postgres-sr created
Snapshot restore created

Verify that the application's objects and PVCs are restored in the namespace postgres2.

Application restored in a different namespace