
This assumes you have already
- Installed the OpenShift API for Data Protection (OADP) Operator
- Created OpenShift API for Data Protection (OADP) Data Protection Application
- Created OpenShift API for Data Protection (OADP) backup Storage Location
Let's say we want to use OpenShift API for Data Protection (OADP) to backup resources and store the backups in an Amazon Web Services S3 Bucket. Let's use the aws s3api create-bucket command to create an S3 Bucket. Notice this is done using an account named "admin". Check out my article Amazon Web Services (AWS) - List Profile Config using the AWS CLI for more details on Amazon Web Services (AWS) profiles.
aws s3api create-bucket --bucket my-bucket-asdfadkjsfasfljdf --region us-east-1 --profile admin
Next let's create an Identity and Access Management (IAM) user named velero using the aws iam create-user command.
aws iam create-user --user-name velero --profile admin
Then create an access key and secret key for the velero user using the aws iam create-access-key command. Notice that the output will include both the value for both the access key and secret key. Make note of the value of the secret key! This is your one and only chance to view the access key. But don't worry, you can always create a new access key if you forgot to make note of the access key.
~]$ aws iam create-access-key --user-name velero --profile admin
{
"AccessKey": {
"UserName": "velero",
"AccessKeyId": "AKIA2MITL76GFDLORQU6",
"Status": "Active",
"SecretAccessKey": "Nzy7dzWcr4hU6sYUg0PCquMCiCv04ae2aXmFIsGE",
"CreateDate": "2025-04-09T01:26:08+00:00"
}
}
Let's say you add the access key and secret key to your $HOME/.aws/credentials file (on a Linux system).
~]$ cat ~/.aws/credentials
[velero]
aws_secret_access_key = Nzy7dzWcr4hU6sYUg0PCquMCiCv04ae2aXmFIsGE
aws_access_key_id = AKIA2MITL76GFDLORQU6
And to the $HOME/.aws/config file too.
~]$ cat ~/.aws/config
[profile velero]
region = us-east-1
output = json
You can now try to list the location of your S3 Buckets using the velero user account but you'll get Access Denied because you've not yet granted velero any permissions.
~]$ aws s3api get-bucket-location --bucket my-bucket-asdfadkjsfasfljdf --profile velero
An error occurred (AccessDenied) when calling the GetBucketLocation operation: User: arn:aws:iam::123456789012:user/velero is not authorized to perform: s3:GetBucketLocation because no identity-based policy allows the s3:GetBucketLocation action
Let's create a file named velero-s3-policy.json that contains the following JSON, replacing my-bucket-asdfadkjsfasfljdf with the name of your S3 Bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:DeleteObject",
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:aws:s3:::my-bucket-asdfadkjsfasfljdf/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads"
],
"Resource": [
"arn:aws:s3:::my-bucket-asdfadkjsfasfljdf"
]
}
]
}
Let's use the aws iam put-user-policy command to attach the policy to the velero user account.
aws iam put-user-policy --user-name velero --policy-name velero-s3 --policy-document file://velero-s3-policy.json --profile admin
Now velero should be able to list the location of the S3 bucket. Don't worry if LocationContraint is null. We just want to make sure that we get a response instead of Access Denied. So far, so good.
~]$ aws s3api get-bucket-location --bucket my-bucket-asdfadkjsfasfljdf --profile velero
{
"LocationConstraint": null
}
Let's create a file named credentials-velero that contains your AWS Access Key and Secret Key.
[default]
aws_access_key_id=<your access key>
aws_secret_access_key=<your secret key>
Let's create a secret named cloud-credentials using the credentials-velero file.
oc create secret generic cloud-credentials --namespace openshift-adp --from-file cloud=credentials-velero
Let's create a YAML file that contains the following, replacing my-bucket-asdfadkjsfasfljdf with the name of your S3 Bucket.
apiVersion: oadp.openshift.io/v1alpha1
kind: DataProtectionApplication
metadata:
name: my-data-protection-application
namespace: openshift-adp
spec:
backupImages: false
backupLocations:
- name: default
velero:
default: true
config:
region: us-east-1
profile: default
credential:
key: cloud
name: cloud-credentials
objectStorage:
bucket: my-bucket-asdfadkjsfasfljdf
provider: aws
configuration:
velero:
defaultPlugins:
- openshift
- aws
nodeSelector: worker
resourceTimeout: 10m
Be aware that by default, the velero deployment will contain three containers, each with 500m of CPU requests. If you do not have that much available CPU on your OpenShift nodes, you can bump down the CPU requests to perhaps something like 50m.
apiVersion: oadp.openshift.io/v1alpha1
kind: DataProtectionApplication
metadata:
name: my-data-protection-application
namespace: openshift-adp
spec:
backupImages: false
backupLocations:
- name: default
velero:
default: true
config:
region: us-east-1
profile: default
credential:
key: cloud
name: cloud-credentials
objectStorage:
bucket: my-bucket-asdfadkjsfasfljdf
provider: aws
configuration:
velero:
defaultPlugins:
- openshift
- aws
nodeSelector: worker
resourceTimeout: 10m
podConfig:
resourceAllocations:
requests:
cpu: 50m
Let's use the oc apply command to create my-data-protection-application.
oc apply --filename my-data-protection-application.yaml
Let's ensure the status is Reconciled.
~]$ oc describe DataProtectionApplication --namespace openshift-adp
. . .
Status:
Conditions:
Last Transition Time: 2025-04-10T01:25:11Z
Message: Reconcile complete
Reason: Complete
Status: True
Type: Reconciled
The Data Protection Application should provision additional "velero" resources in the openshift-adp namespace.
~]$ oc get all --namespace openshift-adp
NAME READY STATUS RESTARTS AGE
pod/openshift-adp-controller-manager-55f68b778f-tlr8v 1/1 Running 0 8m52s
pod/velero-6777878978-nvqm4 1/1 Running 0 3m10s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/openshift-adp-controller-manager-metrics-service ClusterIP 172.30.220.147 <none> 8443/TCP 9m3s
service/openshift-adp-velero-metrics-svc ClusterIP 172.30.87.161 <none> 8085/TCP 3m10s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/openshift-adp-controller-manager 1/1 1 1 8m52s
deployment.apps/velero 1/1 1 1 3m10s
NAME DESIRED CURRENT READY AGE
replicaset.apps/openshift-adp-controller-manager-55f68b778f 1 1 1 8m52s
replicaset.apps/velero-6777878978 1 1 1 3m10s
If you included podConfig with 50m of CPU, then the contains in the deployment should have 50m of CPU requests.
~]$ oc get deployments --namespace openshift-adp --output yaml | grep cpu
cpu: 50m
cpu: 50m
cpu: 50m
Next let's check to see if the backupStorageLocation is Available.
~]$ oc get backupStorageLocations --namespace openshift-adp
NAME PHASE LAST VALIDATED AGE DEFAULT
default Available 60s 64s true
Now let's say you want to backup the pods in namespace my-project.
~]# oc get pods --namespace my-project
NAME READY STATUS RESTARTS AGE
foo-9mzm2 1/1 Running 0 8d
bar-pflxc 1/1 Running 0 8d
And let's say the pods have label app: helloworld.
~]$ oc get pod foo-9mzm2 --namespace my-project --output jsonpath="{.metadata.labels}" | jq
{
"app": "helloworld"
}
Let's create a velero backup resource that will backup the resources in namespace my-project that are labeled app: helloworld once every 24 hours. Let's say this markup is in a file named hello-world-backup.yml.
The reason "default" is used as the name of the storageLocation is because the oc get backupStorageLocations command returns a backup storage location named "default".
apiVersion: velero.io/v1
kind: Backup
metadata:
name: hello-world
labels:
velero.io/storage-location: default
namespace: openshift-adp
spec:
hooks: {}
includedNamespaces:
- helloworld
includedResources: []
excludedResources: []
storageLocation: default
ttl: 24h0m0s
labelSelector:
matchLabels:
app: helloworld
Let's use the oc apply command to create the velero backup resource.
~]$ oc apply --filename hello-world-backup.yml
backup.velero.io/hello-world created
There should now be a backup resource named hello-world in the openshift-adp namespace.
~]$ oc get backups --namespace openshift-adp
NAME AGE
hello-world 102s
If you then describe the backup resource named hello-world the status should be InProgress.
~]$ oc describe backup hello-world --namespace openshift-adp
Name: hello-world
Namespace: openshift-adp
Labels: velero.io/storage-location=default
Annotations: velero.io/resource-timeout: 10m0s
velero.io/source-cluster-k8s-gitversion: v1.29.10+67d3387
velero.io/source-cluster-k8s-major-version: 1
velero.io/source-cluster-k8s-minor-version: 29
API Version: velero.io/v1
Kind: Backup
Metadata:
Creation Timestamp: 2025-04-16T01:25:13Z
Generation: 4
Resource Version: 497329502
UID: 8f72d858-77da-40bc-8fa2-b8f1ca0deb14
Spec:
Csi Snapshot Timeout: 10m0s
Default Volumes To Fs Backup: false
Excluded Resources:
Hooks:
Included Namespaces:
helloworld
Included Resources:
Item Operation Timeout: 4h0m0s
Label Selector:
Match Labels:
App: helloworld
Snapshot Move Data: false
Storage Location: default
Ttl: 24h0m0s
Status:
Expiration: 2025-04-17T01:25:13Z
Format Version: 1.1.0
Hook Status:
Phase: InProgress
Progress:
Items Backed Up: 18
Total Items: 18
Start Timestamp: 2025-04-16T01:25:13Z
Version: 1
Events: <none>
And then shortly thereafter, the status should be Completed. Awesome!
~]$ oc describe backup hello-world --namespace openshift-adp
Name: hello-world
Namespace: openshift-adp
Labels: velero.io/storage-location=default
Annotations: velero.io/resource-timeout: 10m0s
velero.io/source-cluster-k8s-gitversion: v1.29.10+67d3387
velero.io/source-cluster-k8s-major-version: 1
velero.io/source-cluster-k8s-minor-version: 29
API Version: velero.io/v1
Kind: Backup
Metadata:
Creation Timestamp: 2025-04-16T01:25:13Z
Generation: 6
Resource Version: 497329528
UID: 8f72d858-77da-40bc-8fa2-b8f1ca0deb14
Spec:
Csi Snapshot Timeout: 10m0s
Default Volumes To Fs Backup: false
Excluded Resources:
Hooks:
Included Namespaces:
helloworld
Included Resources:
Item Operation Timeout: 4h0m0s
Label Selector:
Match Labels:
App: helloworld
Snapshot Move Data: false
Storage Location: default
Ttl: 24h0m0s
Status:
Completion Timestamp: 2025-04-16T01:25:16Z
Expiration: 2025-04-17T01:25:13Z
Format Version: 1.1.0
Hook Status:
Phase: Completed
Progress:
Items Backed Up: 18
Total Items: 18
Start Timestamp: 2025-04-16T01:25:13Z
Version: 1
Events: <none>
Recall in this example that OADP was configured to store the backups in an Amazon Web Services (AWS) S3 Bucket named my-bucket-asdfadkjsfasfljdf. The aws s3api list-objects command can be used to list the objects in the S3 Bucket. Something like this should be returned, where there are hello-world objects in the S3 Bucket. Awesome, it works!
~]$ aws s3api list-objects --bucket my-bucket-asdfadkjsfasfljdf --profile admin
{
"Contents": [
{
"Key": "backups/hello-world/hello-world-csi-volumesnapshotclasses.json.gz",
"LastModified": "2025-04-16T01:25:17+00:00",
"ETag": "\"6848cb8d5f3669ef603f87e48ece8567\"",
"Size": 29,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-csi-volumesnapshotcontents.json.gz",
"LastModified": "2025-04-16T01:25:17+00:00",
"ETag": "\"6848cb8d5f3669ef603f87e48ece8567\"",
"Size": 29,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-csi-volumesnapshots.json.gz",
"LastModified": "2025-04-16T01:25:17+00:00",
"ETag": "\"6848cb8d5f3669ef603f87e48ece8567\"",
"Size": 29,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-itemoperations.json.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"ae811dd04e417ed7b896b4c4fa3d2ac0\"",
"Size": 27,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-logs.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"673aef92adf289811d5c04b270084eac\"",
"Size": 11312,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-resource-list.json.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"47145873ba24f87182ee601bc7dd92fc\"",
"Size": 307,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-results.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"4b8f571a28628df1f222ee56c3673550\"",
"Size": 49,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-volumeinfo.json.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"05cd97096815e99b306792f280b67b06\"",
"Size": 292,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world-volumesnapshots.json.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"6848cb8d5f3669ef603f87e48ece8567\"",
"Size": 29,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/hello-world.tar.gz",
"LastModified": "2025-04-16T01:25:16+00:00",
"ETag": "\"c28c1d05c60cfb80f21799b5b11faac9\"",
"Size": 13046,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
},
{
"Key": "backups/hello-world/velero-backup.json",
"LastModified": "2025-04-16T01:25:17+00:00",
"ETag": "\"33c1cecb4d65267049037e13b78759d1\"",
"Size": 3826,
"StorageClass": "STANDARD",
"Owner": {
"DisplayName": "john.doe",
"ID": "ab0e0a41e318d5103a77c82240d5cb3fc41ff11cc325c65b5c777a5f8e743743"
}
}
]
}
Did you find this article helpful?
If so, consider buying me a coffee over at