Bootstrap FreeKB - OpenShift - Use OpenShift API for Data Protection (OADP) to store resources in an Amazon Web Services (AWS) S3 Bucket
OpenShift - Use OpenShift API for Data Protection (OADP) to store resources in an Amazon Web Services (AWS) S3 Bucket

Updated:   |  OpenShift articles

This assumes you have already

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 Buy Me A Coffee



Comments


Add a Comment


Please enter d2e1bd in the box below so that we can be sure you are a human.