Kubernetesのリソース・PersistentVolumeをバックアップするVeleroの検証

# はじめに

　本検証では、[Velero(以前の名称はHeptio Ark)](https://velero.io)の動作検証を行います。2019/12時点ではVMware社の[Open Source Projects](http://vmware.github.io)のひとつとして開発が進められています。Veleroは、DR(災害復旧: Disaster Recovery)・マイグレーション・データ保護のユースケースをターゲットに、Kubernetesのリソースと永続ボリューム(Persistent Volume)のバックアップ・リストアを行うツールです。Veleroにより、取得されたバックアップデータは、オブジェクトストレージに保存されます。Persistent VolumeのデータもVolume Snapshot(※1)としてオブジェクトストレージに保存されます。
　また、VeleroはWebアプリケーションのようなステートレスアプリケーションだけでなく、Persistent Volumeのバックアップも可能なため、データベースのようなステートフルアプリケーションにも対応しています。ただし、以下のような[制限/注意事項](https://velero.io/docs/v1.2.0/locations/)がありますので注意してください。

- プロバイダ(クラウドプロバイダ)毎に単一のクレデンシャルのみサポートです
- プロバイダによりVolume Snapshotは、取得できる場所に制限があります。KubernetesのPersistent Volumeと異なるリージョンにVolume Snapshotのバックアップを保存しようとするとバックアップが失敗します
- 各Veleroのバックアップ先は、1つのBackupStorageLocation(アプリケーションなどのリソースの保存先)とVolumeSnapshotLocation(Persistent Volumeのデータの保存先)のみの指定となります
- クロスプロバイダーのVolume Snapshotは非サポートです
- バックアップ作成時にユーザが指定したBackupStorageLocationのBucket(オブジェクトストレージの格納先)のprefix/subdirectory配下にResticデータ(※2)が格納されます

:memo:
(※1) Veleroでは、Persistent VolumeのバックアップデータのことをVolume Snapshotという用語を使っています。データの差分を取得するストレージのスナップショット技術([Kubernetes Concepts/ Volume snapshots](https://kubernetes.io/docs/concepts/storage/volume-snapshots/),[Kubernetes: Volume Snapshotの検証](https://qiita.com/ysakashita/items/5a40124729dca81f04e4))とは異なりますので、注意してください。
(※2) Veleroではオープンソースのバックアップツール[restic](https://github.com/restic/restic)を利用しています。Resticデータとは、resticにより取得されたバックアップデータのことです。resticではいくつかの[制限事項](https://velero.io/docs/v1.2.0/restic/#limitations)があるため、注意してください(hostPathは非サポートだがLocal Volumeはサポートなど)。

# 検証環境

以下に、本検証の環境を示します。

- GKE (Kubernetes v1.15.4-gke.22)
- Velero v1.2.0
- Client: macOS 10.14.6

GKEを操作するCLI(gcloud,gsutilなど)はあらかじめセットアップされているものとします。

# 動作検証

Veleroはスケジュールで定期的にバックアップを取得する方法とCLIでオンデマンドに取得する2種類の実行方法があります。本検証ではCLIでオンデマンドに取得する実行方法を検証します。

## セットアップ

まず、veleroコマンドをClientにインストールします。

```
$ brew install velero
```

次に、バックアップデータを保存するオブジェクトストレージ(Google Cloud Storage bucket)を作成します。

```
$ BUCKET=velero-bucket

$ gsutil mb gs://$BUCKET/
```

作成したGoogle Cloud Storage bucketを確認します。

```
$ gsutil list
gs://velero-bucket/
```

Service Account を作成します。

```
$ PROJECT_ID=$(gcloud config get-value project)

$ gcloud iam service-accounts create velero \
    --display-name "Velero service account"
```

作成したService Accountにパーミッションを付与します。

```
$ SERVICE_ACCOUNT_EMAIL=$(gcloud iam service-accounts list \
  --filter="displayName:Velero service account" \
  --format 'value(email)')

$ ROLE_PERMISSIONS=(
    compute.disks.get
    compute.disks.create
    compute.disks.createSnapshot
    compute.snapshots.get
    compute.snapshots.create
    compute.snapshots.useReadOnly
    compute.snapshots.delete
    compute.zones.get
)

$ gcloud iam roles create velero.server \
     --project $PROJECT_ID \
     --title "Velero Server" \
     --permissions "$(IFS=","; echo "${ROLE_PERMISSIONS[*]}")"

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:$SERVICE_ACCOUNT_EMAIL \
    --role projects/$PROJECT_ID/roles/velero.server

$ gsutil iam ch serviceAccount:$SERVICE_ACCOUNT_EMAIL:objectAdmin gs://${BUCKET}
```

作成したService Accountのキーをファイルに保存します。

```
$ gcloud iam service-accounts keys create credentials-velero \
    --iam-account $SERVICE_ACCOUNT_EMAIL

$ ls
credentials-velero
```

`velero install`コマンドを使ってKubernetesへVeleroをデプロイします。

```
$ velero install \
    --provider gcp \
    --plugins velero/velero-plugin-for-gcp:v1.0.0 \
    --bucket $BUCKET \
    --secret-file ./credentials-velero

CustomResourceDefinition/backups.velero.io: attempting to create resource
CustomResourceDefinition/backups.velero.io: created
CustomResourceDefinition/backupstoragelocations.velero.io: attempting to create resource
CustomResourceDefinition/backupstoragelocations.velero.io: created
CustomResourceDefinition/deletebackuprequests.velero.io: attempting to create resource
CustomResourceDefinition/deletebackuprequests.velero.io: created
CustomResourceDefinition/downloadrequests.velero.io: attempting to create resource
CustomResourceDefinition/downloadrequests.velero.io: created
CustomResourceDefinition/podvolumebackups.velero.io: attempting to create resource
CustomResourceDefinition/podvolumebackups.velero.io: created
CustomResourceDefinition/podvolumerestores.velero.io: attempting to create resource
CustomResourceDefinition/podvolumerestores.velero.io: created
CustomResourceDefinition/resticrepositories.velero.io: attempting to create resource
CustomResourceDefinition/resticrepositories.velero.io: created
CustomResourceDefinition/restores.velero.io: attempting to create resource
CustomResourceDefinition/restores.velero.io: created
CustomResourceDefinition/schedules.velero.io: attempting to create resource
CustomResourceDefinition/schedules.velero.io: created
CustomResourceDefinition/serverstatusrequests.velero.io: attempting to create resource
CustomResourceDefinition/serverstatusrequests.velero.io: created
CustomResourceDefinition/volumesnapshotlocations.velero.io: attempting to create resource
CustomResourceDefinition/volumesnapshotlocations.velero.io: created
Waiting for resources to be ready in cluster...
Namespace/velero: attempting to create resource
Namespace/velero: created
ClusterRoleBinding/velero: attempting to create resource
ClusterRoleBinding/velero: created
ServiceAccount/velero: attempting to create resource
ServiceAccount/velero: created
Secret/cloud-credentials: attempting to create resource
Secret/cloud-credentials: created
BackupStorageLocation/default: attempting to create resource
BackupStorageLocation/default: created
VolumeSnapshotLocation/default: attempting to create resource
VolumeSnapshotLocation/default: created
Deployment/velero: attempting to create resource
Deployment/velero: created
Velero is installed! ⛵ Use 'kubectl logs deployment/velero -n velero' to view the status.
```

これでVeleroのセットアップは完了です。

## バックアップ&リストア1 (PersistentVolumeなし)

最初の検証では、データを保存するPersistentVolumeを使わないステートレスなアプリケーションのバックアップ＆リストアを試します。  
まず、検証用にnginxをデプロイします。
デプロイに使うManifest(`nginx.yaml`)を以下に示します。

```nginx.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: hoge
  labels:
    app: nginx

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: hoge
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.7.9
        name: nginx
        ports:
        - containerPort: 80
```

Manifest(`nginx.yaml`)をデプロイします。
Namespace(`hoge`)が作成され、その配下にnginxのPodがデプロイされています。

```
$ kubectl apply -f nginx.yaml 
namespace/hoge created
deployment.apps/nginx-deployment created

$ kubectl get deploy,pod -n hoge
NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/nginx-deployment   2/2     2            2           92s

NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-5754944d6c-c6jbr   1/1     Running   0          92s
pod/nginx-deployment-5754944d6c-dz5dx   1/1     Running   0          92s
```

次に、veleroコマンドを使ってバックアップを取得します。

```
$ velero backup create nginx-backup --include-namespaces hoge
```

取得したバックアップを確認します。

```
$ velero backup describe nginx-backup
Name:         nginx-backup
Namespace:    velero
Labels:       velero.io/storage-location=default
Annotations:  <none>

Phase:  Completed

Namespaces:
  Included:  hoge
  Excluded:  <none>

Resources:
  Included:        *
  Excluded:        <none>
  Cluster-scoped:  auto

Label selector:  <none>

Storage Location:  default

Snapshot PVs:  auto

TTL:  720h0m0s

Hooks:  <none>

Backup Format Version:  1

Started:    2019-12-14 17:43:23 +0900 JST
Completed:  2019-12-14 17:43:24 +0900 JST

Expiration:  2020-01-13 17:43:23 +0900 JST

Persistent Volumes: <none included>
```

Google Cloud Storage bucketにバックアップファイルが保存されていることを確認します。

```
$ gsutil dir -R gs://velero-bucket/
gs://velero-bucket/backups/:

gs://velero-bucket/backups/nginx-backup/:
gs://velero-bucket/backups/nginx-backup/nginx-backup-logs.gz
gs://velero-bucket/backups/nginx-backup/nginx-backup-podvolumebackups.json.gz
gs://velero-bucket/backups/nginx-backup/nginx-backup-resource-list.json.gz
gs://velero-bucket/backups/nginx-backup/nginx-backup-volumesnapshots.json.gz
gs://velero-bucket/backups/nginx-backup/nginx-backup.tar.gz
gs://velero-bucket/backups/nginx-backup/velero-backup.json
```

次に、リストアを検証します。
リストアの前に、障害を想定しNamaspace(`hoge`)を削除します。

```
$ kubectl delete ns hoge
namespace "hoge" deleted

$ kubectl get pod -n hoge
No resources found in hoge namespace.
```

Namespace(`hoge`)および配下のリソースが全て削除されました。
続いて、veleroコマンドを使いリストアします。

```
$ velero restore create --from-backup nginx-backup
```

リストアされたNamespace(`hoge`)と配下のリソースを確認します。

```
$ kubectl get deploy,pod -n hoge
NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/nginx-deployment   2/2     2            2           61s

NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-5754944d6c-c6jbr   1/1     Running   0          61s
pod/nginx-deployment-5754944d6c-dz5dx   1/1     Running   0          61s
```

無事、Namespace(`hoge`)と配下のリソースがリストアされました。

## バックアップ&リストア2 (PersistentVolumeあり)

次の検証では、データを保存するPersistentVolumeを使ったステートフルなアプリケーションのバックアップ＆リストアを試します。  

検証用にログをPersistentVolumeに保存するnginxをデプロイします。
デプロイに使うManifest(`nginx-pv.yaml`)を以下に示します。

```nginx-pv.yaml 
apiVersion: v1
kind: Namespace
metadata:
  name: boo
  labels:
    app: nginx

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-sts
  namespace: boo
spec:
  serviceName: nginx-sts
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
      annotations:
        pre.hook.backup.velero.io/container: fsfreeze
        pre.hook.backup.velero.io/command: '["/sbin/fsfreeze", "--freeze", "/var/log/nginx"]'
        post.hook.backup.velero.io/container: fsfreeze
        post.hook.backup.velero.io/command: '["/sbin/fsfreeze", "--unfreeze", "/var/log/nginx"]'
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        volumeMounts:
        - name: nginx-logs
          mountPath: /var/log/nginx
      - name: fsfreeze
        image: ubuntu:bionic
        securityContext:
          privileged: true
        volumeMounts:
          - name: nginx-logs
            mountPath: /var/log/nginx
        command:
          - "/bin/bash"
          - "-c"
          - "sleep infinity"
  volumeClaimTemplates:
  - metadata:
      name: nginx-logs
    spec:
      storageClassName: standard
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi  
```

なお、Veleroでは `pre.hook.backup.velero.io/container`, `pre.hook.backup.velero.io/command`, `post.hook.backup.velero.io/container`, `post.hook.backup.velero.io/command`のアノテーションにて指定されたコンテナのコマンドをバックアップの取得前と取得後に呼び出すことが出来ます。
上記では、Sidecar(`fsfreeze`)を用意し、Sidecarにてコマンドを実行します。  
これにより、アプリケーションの静止化を行うことができます。
静止化とは、アプリケーションのデータの書き込みを一時的に止める処理のことを言います。
バックアップの実行に書き込みがあった場合、バックアップのデータが壊れることがあります。
特にRDBなどでは静止化を行い、ステーブルになったデータをストレージに保存しないと、リストアできない場合がありますので注意してください。
今回は、ファイルシステムの静止化を行うコマンド(`fsfreeze`)を利用します。  
Manifest(`nginx-pv.yaml`)をデプロイします。
Namespace(`boo`)が作成され、その配下にnginxのPodとPersistentVolumeがデプロイされています。

```
$ kubectl apply -f nginx-pv.yaml

$ kubectl get sts,pod,pvc,pv -n boo
NAME                         READY   AGE
statefulset.apps/nginx-sts   1/1     13m

NAME              READY   STATUS    RESTARTS   AGE
pod/nginx-sts-0   2/2     Running   0          13m

NAME                                           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/nginx-logs-nginx-sts-0   Bound    pvc-5af42324-887f-4758-98da-c1dc6da8539c   1Gi        RWO            standard       13m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                        STORAGECLASS   REASON   AGE
persistentvolume/pvc-5af42324-887f-4758-98da-c1dc6da8539c   1Gi        RWO            Delete           Bound    boo/nginx-logs-nginx-sts-0   standard                13m
```

次に、nginxのアクセスログ(`/var/log/nginx/access.log`)に足跡を残すために、ワンタイムのPodをデプロイしcurlでniginxにアクセスします。

```
$ kubectl get pod -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP          NODE                                         NOMINATED NODE   READINESS GATES
nginx-sts-0   2/2     Running   0          15m   10.24.0.9   gke-velero-test-default-pool-9ec9e72c-460l   <none>           <none>

$ kubectl run -ti --image ubuntu:bionic access-test --restart=Never --rm /bin/sh
...
# apt-get update
...
# apt-get install curl
...
# curl 10.24.0.9
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
# exit
```

nginxのアクセスログ(`/var/log/nginx/access.log`)に足跡が記録されました。

```
$ kubectl exec nginx-sts-0 -n boo -- cat /var/log/nginx/access.log
...
10.24.2.9 - - [14/Dec/2019:10:37:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
```

続いて、veleroコマンドを使ってバックアップを取得します。

```
$ velero backup create nginx-backup-with-pv --include-namespaces boo
```

バックアップを確認します。

```
$ velero backup describe nginx-backup-with-pv
Name:         nginx-backup-with-pv
Namespace:    velero
Labels:       velero.io/backup=nginx-backup-with-pv
              velero.io/pv=pvc-5af42324-887f-4758-98da-c1dc6da8539c
              velero.io/storage-location=default
Annotations:  <none>

Phase:  Completed

Namespaces:
  Included:  boo
  Excluded:  <none>

Resources:
  Included:        *
  Excluded:        <none>
  Cluster-scoped:  auto

Label selector:  <none>

Storage Location:  default

Snapshot PVs:  auto

TTL:  720h0m0s

Hooks:  <none>

Backup Format Version:  1

Started:    2019-12-14 19:48:32 +0900 JST
Completed:  2019-12-14 19:48:35 +0900 JST

Expiration:  2020-01-13 19:48:32 +0900 JST

Persistent Volumes:  1 of 1 snapshots completed successfully (specify --details for more information)
```

Google Cloud Storage bucketにバックアップファイルが保存されていることを確認します。

```
$ gsutil dir -R gs://velero-bucket/
gs://velero-bucket/backups/:
...
gs://velero-bucket/backups/nginx-backup-with-pv/:
gs://velero-bucket/backups/nginx-backup-with-pv/nginx-backup-with-pv-logs.gz
gs://velero-bucket/backups/nginx-backup-with-pv/nginx-backup-with-pv-podvolumebackups.json.gz
gs://velero-bucket/backups/nginx-backup-with-pv/nginx-backup-with-pv-resource-list.json.gz
gs://velero-bucket/backups/nginx-backup-with-pv/nginx-backup-with-pv-volumesnapshots.json.gz
gs://velero-bucket/backups/nginx-backup-with-pv/nginx-backup-with-pv.tar.gz
gs://velero-bucket/backups/nginx-backup-with-pv/velero-backup.json
...
```

先ほどと同様に、障害を想定しNamespace(`boo`)を削除します。

```
$ kubectl delete ns boo
namespace "boo" deleted
```

Namespace(`boo`)と配下のリソースが全て削除されているのを確認します。
PersistentVolumeについても、Reclaim Policyが`Delete`と設定されているため、PersistentVolumeClaimが削除されると共に削除されています。

```
$ kubectl get sts,pod,pvc,pv -n boo
No resources found in boo namespace.
```

次に、リストアを実行します。

```
$ velero restore create --from-backup nginx-backup-with-pv
```

リストアされたリソースを確認します。

```
$ kubectl get sts,pod,pvc,pv -n boo
NAME                         READY   AGE
statefulset.apps/nginx-sts   1/1     38s

NAME              READY   STATUS    RESTARTS   AGE
pod/nginx-sts-0   2/2     Running   0          38s

NAME                                           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/nginx-logs-nginx-sts-0   Bound    pvc-5af42324-887f-4758-98da-c1dc6da8539c   1Gi        RWO            standard       38s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                        STORAGECLASS   REASON   AGE
persistentvolume/pvc-5af42324-887f-4758-98da-c1dc6da8539c   1Gi        RWO            Delete           Bound    boo/nginx-logs-nginx-sts-0   standard                38s
```

続いて、nginxのアクセスログ(`/var/log/nginx/access.log`)を確認します。

```
$ kubectl exec nginx-sts-0 -n boo -- cat /var/log/nginx/access.log
...
10.24.2.9 - - [14/Dec/2019:10:37:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
```

バックアップ前に、アクセスした足跡がアクセスログ(`/var/log/nginx/access.log`)に残っています。
無事、PersistentVolumeのデータもリストアすることが出来ました。


# 感想

　本検証では、Veleroを試しKubernetes上のリソースとPersistent Volumeのバックアップ・リストアを検証しました。Veleroは幾つか制限事項があるものKubernetes上のリソースとPersistent Volumeをコマンド一つで一括にバックアップ・リストアできました。
　Veleroの良い点としては、簡単に一括でバックアップ・リストアができる以外にも、バックアップを行う事前と事後に[Hooks](https://velero.io/docs/v1.2.0/hooks/)により、任意のコマンドを実行できる点です。2019年時点では、KubernetesのSIG-Storageでも同様にVolume Snapshotの事前と事後に任意のコマンドが挟めるようHookの開発が進められていますが、リリースはまだまだ先となっています。ストレージ・データベース業界にとっては、アプリケーションの静止化をおこなってバックアップを取得するのが安全にバックアップを取得する定石のひとつとなっています。これら定石を実行できる口をVeleroはいち早くサポートしているのが良い点のひとつではないでしょうか
　地震や火事などの災害では、データセンタ単位で壊れます。このような場合においても、迅速にサービスを復旧するために、日頃からバックアップを取得し備えることは、防災対策のひとつです。ITは水や電気などと同じく生活の基盤としてなくてはならないインフラです。防災対策としても、定期的にバックアップを取得し、いざと言う時に備えるのは重要ではないでしょうか。

# 参考情報

- [Velero v1.2.0 Documentation](https://velero.io/docs/v1.2.0/)
- [Velero Plugin for GCP](https://github.com/vmware-tanzu/velero-plugin-for-gcp#setup)