OpenShift API for Data Protection (OADP) を使ってアプリをバックアップ／リストアしてみよう

# はじめに
OpenShift環境を運用するにあたり、「これどうすればいいの？」となりがちなのが、アプリケーションのバックアップ／リストアです。残念ながら従来の仮想サーバの運用のときのように、VMスナップショット取ってはい終わり、とはいきません。コンテナやKubernetes/OpenShiftの特性を把握し、それに適したバックアップ／リストア手法を採用する必要があります。

そこで今回は、OpenShiftが提供しているアプリケーションバックアップツールであるOpenShift API for Data Protection (OADP)を触ってみて、どのようにアプリケーションのバックアップを取得できるのかをご紹介します。

# OpenShift API for Data Protectionとは？
Red HatがOpenShiftクラスター上でバックアップ・リストアを実現するために作成したOperatorです。アップストリームは[こちら](https://github.com/openshift/oadp-operator)になりますが、Veleroをベースとしたツールになっており、OADPをインストールすることでVeleroのAPIも使用することができます。
OpenShift Container Platformのサブスクリプションに包含されているため、ユーザーは追加料金なしに利用することができます。
バックアップの対象は、OpenShiftクラスターリソース(yamlファイル)、内部イメージ、および永続ボリュームのデータになります。注意するべきは、基本的にNamespaceごとの情報を対象としたバックアップになりますので、このバックアップさえあれば有事の際も全部元に戻せる、といったものではないことを誤認識ください。


# 手順

## 前提条件
本手順は、以下の環境で実施します。
* AWS上にIPIインストールしたOpenShift Container Platform 4.11.18
* cluster-adminを持つOpenShiftユーザー
* AdministratorAccessのポリシーを持つIAMユーザー


## OADP Operatorをインストールする
OADPはOperatorを使ってインストールします。
まずWebコンソールで「OADP Opetaror」を検索します。

![OADP1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/abf4fa5b-7028-6f7e-5963-d9535b9719fb.png)

インストール画面では特に設定を変えず、そのまま「インストール」をクリックします。
![OADP2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/91606f24-a0d8-7dba-b3b3-96255cbdf76d.png)

インストールが終わると様々なAPIが利用可能になっていることがわかります。
![OADP3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/1ba18891-5ab3-43f6-0df4-968b3ca12533.png)

## AWSの設定
OADP Operatorの準備は一旦これくらいにして、AWSの設定に入ります。

### IAMの準備
OADPでは永続ボリュームのバックアップ手法として[restic](https://github.com/restic/restic)と呼ばれるオープンソースのバックアップツールと、CSIスナップショットの2種類をサポートしています。ここではAWS上に構築していることから、AWSのEBSを使ったCSIスナップショットを使ってみます。
また、OADPでは取得したバックアップの保存先としてオブジェクトストレージを使います。こちらもAWSを使っていることからS3に保存していきます。

EBSのスナップショットやS3へのputを行うにあたり、OADP用のIAMポリシー/IAMユーザーを作成する必要がありますが、今回は割愛してAdministratorAccessの権限を持つIAMユーザーのアクセスキー/シークレットキーを使います。OADP用のIAMを作成する場合は[公式ドキュメント](https://docs.openshift.com/container-platform/4.11/backup_and_restore/application_backup_and_restore/installing/installing-oadp-aws.html#migration-configuring-aws-s3_installing-oadp-aws)を参照ください。

のちほどAWSのIAM情報をSecretとしてデプロイするため、必要なファイルを作成しておきます。
```
cat << EOF > ./credentials-velero
[default]
aws_access_key_id=<AWS_ACCESS_KEY_ID>
aws_secret_access_key=<AWS_SECRET_ACCESS_KEY>
EOF
```

### オブジェクトストレージの準備
バックアップ情報を格納するS3バケットを作成します。
AWSのWebコンソールからS3のバケット作成画面に行き、OpenShiftクラスターがあるリージョン（今回は東京リージョン）にバケットを作成します。
名前以外は全てデフォルトのままでOKです。

![OADP4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/47d02da5-0a34-4aee-4e01-66e7596182d2.png)

## OpenShiftの設定
AWS側の設定が完了したので、今度はOpenShiftの設定を行います。
まずはEBSのCSIスナップショットを使うための設定変更です。

IPIインストールした時点でのデフォルトのストレージクラスがgp2という、CSIではないものになっているため、これをgp3-csiに変更します。
```
$ oc get storageclass
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   true                   5d7h
gp2-csi         ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   5d7h
gp3-csi         ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   5d7h
```

以下のコマンドで変更できます。
```
$ oc patch storageclass gp2 --type json -p '[{ "op": "remove", "path": "/metadata/annotations" }]'
$ oc patch storageclass gp3-csi -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
```

```
$ oc get storageclass
NAME                PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2                 kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   true                   5d7h
gp2-csi             ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   5d7h
gp3-csi (default)   ebs.csi.aws.com         Delete          WaitForFirstConsumer   true                   5d7h
```

次に`VolumeSnapshotClass`を修正します。
`VolumeSnapshotClass`とは、通常のPVでいう`StorageClass`のように、動的のボリュームスナップショットの作成・削除を実現します。
OpenShiftをインストールした時点で、`csi-aws-vsc`という`VolumeSnapshotClass`が作成されています。
```
$ oc get vsclass
NAME                    DRIVER            DELETIONPOLICY   AGE
csi-aws-vsc             ebs.csi.aws.com   Delete           5d7h
```

中身を見ると、`gp3-csi`StorageClassのProvisionerである`ebs.csi.aws.com`がDriverとして指定されていることがわかります。
```
$ oc get vsclass csi-aws-vsc -o yaml
apiVersion: snapshot.storage.k8s.io/v1
deletionPolicy: Delete
driver: ebs.csi.aws.com
kind: VolumeSnapshotClass
metadata:
  annotations:
    snapshot.storage.kubernetes.io/is-default-class: "true"
  generation: 1
  name: csi-aws-vsc
```

これにveleroでCSIスナップショットを取得するためのannotationを追加していきます。
```
$ oc patch vsclass csi-aws-vsc -p '{"metadata":{"labels":{"velero.io/csi-volumesnapshot-class":"true"}}}' --type=merge
```

最後に、少し前に作成したAWSのcredentialをデプロイします。
```
$ oc create secret generic cloud-credentials -n openshift-adp --from-file cloud=credentials-velero
```

これでOpenShiftの設定は完了です。

## OADPの設定
最後に本丸のOADPの設定を行います。OADPのバックアップ／リストアの基本的な構成設定は、`DataProtectionApplication`というカスタムリソースで行います。
さまざまな設定項目があるので、詳細は[公式ドキュメント](https://docs.openshift.com/container-platform/4.11/backup_and_restore/application_backup_and_restore/installing/installing-oadp-aws.html#oadp-about-backup-snapshot-locations_installing-oadp-aws)を確認いただければと思いますが、ここでは最低限の設定例をお見せします。
```dataprotectionapplication.yaml
apiVersion: oadp.openshift.io/v1alpha1
kind: DataProtectionApplication
metadata:
  name: velero-sample
  namespace: openshift-adp
spec:
  configuration:
    velero:
      defaultPlugins:
        - openshift
        - aws
        - kubevirt
        - csi
    featureFlags:
    - EnableCSI
  backupLocations:
    - velero:
        config:
          profile: default
          region: ap-northeast-1
        credential:
          key: cloud
          name: cloud-credentials
        objectStorage:
          bucket: oadp-demo
          prefix: velero
        default: true
        provider: aws
```
ちなみに、webコンソールで作成しようとすると、あらかじめ以下のようにデフォルトの設定が確認できます。OpenShiftはこういったある程度のサンプルyamlファイルが確認できるのがいいですよね。
![OADP5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/50ae2217-b38c-0578-0dc1-654f13080434.png)

上記のファイルをデプロイします。
```
$ oc apply -f dataprotectionapplication.yaml
```

デプロイするとOADPのコンポーネントが立ち上がります。
![OADP6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/91ed99b6-8012-13ab-758c-ca59c3fccc0b.png)

色々準備しましたが、以上でOADPでバックアップを取得する準備が完了しました。

## バックアップを取得する
では実際にバックアップを取得してみましょう。
今回は[こちら](https://github.com/skitamura7446/sample-blog)のサンプルブログのアプリを対象とします。
このアプリはNginx-Django-Mysql/Redisの構成になっており、Mysqlは`Statefulset`でEBSを`PersistentVolume`として使っています。
READMEの手順に従いアプリをデプロイすると、`Sample-blog`Namespace配下に以下のようなコンポーネントがデプロイされます。
```
$ oc get pods,deploy,statefulsets,service,route,pv,pvc -n sample-blog
NAME                          READY   STATUS      RESTARTS   AGE
pod/django-1-build            0/1     Completed   0          34h
pod/django-5cd9f75c58-bcxvt   1/1     Running     0          33h
pod/mysql-0                   1/1     Running     0          33h
pod/nginx-1-build             0/1     Completed   0          34h
pod/nginx-7f98dd9f54-dv772    1/1     Running     0          33h
pod/redis-0                   1/1     Running     0          33h

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/django   1/1     1            1           34h
deployment.apps/nginx    1/1     1            1           34h

NAME                     READY   AGE
statefulset.apps/mysql   1/1     34h
statefulset.apps/redis   1/1     34h

NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/django   ClusterIP   172.30.87.142    <none>        8000/TCP   34h
service/mysql    ClusterIP   172.30.209.214   <none>        3306/TCP   34h
service/nginx    ClusterIP   172.30.163.114   <none>        80/TCP     34h
service/redis    ClusterIP   172.30.49.173    <none>        6379/TCP   34h

NAME                             HOST/PORT                                                 PATH   SERVICES   PORT   TERMINATION   WILDCARD
route.route.openshift.io/nginx   sample-blog.apps.aws-cluster-01.xxx.com          nginx      8080                 None

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                         STORAGECLASS   REASON   AGE
persistentvolume/pvc-a2c8e63a-5e95-46bb-9833-3e82d10d8a05   10Gi       RWO            Delete           Bound    sample-blog/redis-pv-claim    gp3-csi                 33h
persistentvolume/pvc-ee7a59d0-2ec2-48bf-ae91-60a18115b9f9   20Gi       RWO            Delete           Bound    sample-blog/mysql-pv-claim    gp3-csi                 33h

NAME                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/mysql-pv-claim   Bound    pvc-ee7a59d0-2ec2-48bf-ae91-60a18115b9f9   20Gi       RWO            gp3-csi        33h
persistentvolumeclaim/redis-pv-claim   Bound    pvc-a2c8e63a-5e95-46bb-9833-3e82d10d8a05   10Gi       RWO            gp3-csi        33h
```

バックアップの取得前にサンプルブログに適当に記事を投稿しておきます。

![OADP7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/d5be3084-f08b-32ac-abd7-0d2a2cee9f19.png)

ではbackup用のyamlファイルを作成します。
```backup.yaml
apiVersion: velero.io/v1
kind: Backup
metadata:
  name: backup-sample-blog-01
  namespace: openshift-adp
spec:
  storageLocation: velero-sample-1
  includedNamespaces:
  - sample-blog
```
* `spec.storageLocation`には`DataProtectionApplication`のNameに`-1`をつけたものを指定します。これは`DataProtectionApplication`を作成した際に生成される`backupStorageLocations`というカスタムリソース名になります。
* `includedNamespaces`には、バックアップの対象となるNamespaceのリストを記述します。今回は`sample-blog`を指定します。

そのほかの設定については[公式ドキュメント](https://docs.openshift.com/container-platform/4.11/backup_and_restore/application_backup_and_restore/backing_up_and_restoring/backing-up-applications.html#oadp-creating-backup-hooks_backing-up-applications)を参照ください。特定のクラスターリソースのみをバックアップするなど、柔軟な設定が可能です。

ではバックアップを取得します。
```
$ oc apply -f backup.yaml
```
デプロイすると、バックアップ処理が開始されます。
![OADP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/1e7758f6-3c68-ab92-3d6f-3a9b14dc0496.png)

ちなみにバックアップの処理に関するログは`DataProtectionApplication`のデプロイ時に生成された`velero`Podの標準出力に表示されます。
```
$ oc logs velero-76c54bcb77-hqtpt
```


完了するとステータスが`Completed`になります。
![スクリーンショット 2022-12-21 20.49.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/c27865e0-774a-cac1-a1e6-20c82478818f.png)

　実際に取得したバックアップを見てみましょう。
あらかじめ設定したS3バケット内に色々なデータが格納されていることが確認できます。
![OADP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/02d5bc93-0ebe-b2ed-540d-dead24b8b8f9.png)
永続ボリューム（MySQL/Redisのデータ）については、EBSのスナップショットで取得しています。こちらもAWSのWebコンソールで確認できます。
![OADP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/e6681132-867c-761b-6201-ebab90180be1.png)


## リストアを実行する
さて、無事にバックアップが取得できたので、次はリストアを実行します。
リストアの実行前に、今稼働しているサンプルブログにひとつ記事を追加しておきます。リストア後、これが消えていたらリストアが正常に完了しているといっていいでしょう。

![OADP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/4e079bb3-3b1f-e50d-d46b-a71385524d0b.png)


リストア用のyamlを作成します。
```restore.yaml
apiVersion: velero.io/v1
kind: Restore
metadata:
  name: restore-sample-blog-01
  namespace: openshift-adp
spec:
  backupName: backup-sample-blog-01
  includedNamespaces:
  - sample-blog
  excludedResources:
  - nodes
  - events
  - events.events.k8s.io
  - backups.velero.io
  - restores.velero.io
  - resticrepositories.velero.io
  restorePVs: true
```
こちらの詳細な設定は[公式ドキュメント](https://docs.openshift.com/container-platform/4.11/backup_and_restore/application_backup_and_restore/backing_up_and_restoring/restoring-applications.html#oadp-creating-restore-cr_restoring-applications)を参照ください。

ではリストアを実行しましょう。
```
$ oc apply -f restore.yaml
```
こちらもしばらくするとステータスが`Completed`になります。
![OADP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/8a17c317-4693-8129-c5bd-2183fc41dc5a.png)

サンプルブログにアクセスすると、2つ目の記事が消えているため、無事リストアされていることがわかります。
![OADP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419286/d5be3084-f08b-32ac-abd7-0d2a2cee9f19.png)

# おわりに
今回はOADPの基本的な機能を使ってOpenShift環境のアプリケーションバックアップ／リストアを実現できることを確認しました。
別の記事にて、もうすこしバックアップ／リストアというテーマを俯瞰して、「そもそもOpenShift環境のバックアップ／リストアってどう考えたらいいの？」という点についてまとめていこうと思いますので、乞うご期待ください！