Kubernetes クラスタのバックアップツール Velero を試してみた

Kubenews #8 を視聴していたら Velero というツールの話題が出て、そういえば試そうと思って手付かずのままだったなぁと思い出し、試せるのはいつになるかなぁと Twitter で呟いたら「今でしょ!」とも言われたので、Velero を試してみました。

Velero とは

Velero は、Kubernetes クラスタのリソースと永続データをバックアップ、リカバリ、移行するためのツールです。 VMware のプロジェクトとして管理されており、VMware Tanzu (VMwareKubernetes 製品群) の一員です。

github.com

Velero は Kubernetes クラスタに対して以下のような使い方ができます。

Velero の特徴としては、Kubernetes API を使ってバックアップ・リストアをする API-driven な点があります。 他の Kubernetes 用バックアップツールはクラスタ内の etcd を直接参照してバックアップ・リストアするようで、その点が異なります。

API-driven なバックアップツールには以下のようなメリットがあります。

  • 名前空間、リソースタイプ、ラベルによってバックアップ・リストア対象を柔軟に選択可能
  • マネージド K8s クラスタの場合、etcd を直接参照できずバックアップ・リストアできないことがあるが、API 経由なら可能
  • 別の etcd にリソースが保存されていてもバックアップ・リストア可能

正直なところ、3点目はどのような場合を想定しているのか理解できていません…。

また、Velero は Kubernetes のリソースだけでなく、永続データもバックアップ・リストアの対象とすることができますが、今回はリソースを対象にしたバックアップ・リストアのみ試しています。

オブジェクトストレージの用意

Velero はバックアップデータをオブジェクトストレージに格納します。

Amazon S3 や Azure Blob Storage といったクラウドサービスとしてのオブジェクトストレージを使ってもよいのですが、今回はオンプレ環境の K8s クラスタ内に2種類のオブジェクトストレージを用意しました。

対応しているストレージプロバイダの一覧は以下に記載されていますが、今回使用する S3 互換のオブジェクトストレージは十分なテストはされていない点は要注意です。

velero.io

MinIO

まずは S3 互換のオブジェクトストレージとしてよく使われているであろう MinIO です。(今回、初めて存在を知りました…)

MinIO の場合は Velero 公式サイトに参考手順が載っています。

velero.io

上記手順でも使われていますが、公式リポジトリK8s クラスタに MinIO をデプロイするサンプルマニフェストが用意されています。

github.com

今回はこちらのサンプルマニフェストを一部変更して MinIO を用意しました。

変更箇所は kind: Service に対する以下の2点です。

  • ExternalDNS 用に external-dns.alpha.kubernetes.io/hostname アノテーションを付与
  • spec.type: ClusterIP から spec.type: LoadBalancer へ変更

以下は変更済みサンプルマニフェストのデプロイ結果です。

$ kubectl apply -f examples/minio/00-minio-deployment.yaml

$ kubectl get all -n velero
NAME                         READY   STATUS      RESTARTS   AGE
pod/minio-5b84955bdd-2qh6f   1/1     Running     0          26h
pod/minio-setup-nfcjq        0/1     Completed   0          26h

NAME            TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
service/minio   LoadBalancer   10.103.161.124   192.168.2.244   9000:31475/TCP   26h

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/minio   1/1     1            1           26h

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/minio-5b84955bdd   1         1         1       26h

NAME                    COMPLETIONS   DURATION   AGE
job.batch/minio-setup   1/1           13s        26h

service/minio は type: LoadBalancer に変更したため External-IP が振られており、 ExternalDNS による DNS 登録もされている状態です。

デプロイから数分待てば、アノテーションで指定したホスト名で MinIO のサービスにアクセスできるようになります。

ExternalDNS については以下の記事を参考にしてください。

nnstt1.hatenablog.com

Rook Ceph

もう一つは、Rook Ceph のオブジェクトストレージです。

既にクラスタ用のブロックストレージとして Rook Ceph は使っていたのですが、オブジェクトストレージとしての利用はしていなかったので今回を機に用意してみました。

Rook Ceph オブジェクトストレージのデプロイには、公式ドキュメントと以下の記事を参考にしました。

rook.io

qiita.com

$ kubectl apply -f rook/cluster/examples/kubernetes/ceph/object-test.yaml
$ kubectl apply -f rook/cluster/examples/kubernetes/ceph/storageclass-bucket-delete.yaml
$ kubectl apply -f rook/cluster/examples/kubernetes/ceph/object-bucket-claim-delete.yaml

既に Rook が入っていれば、これだけでオブジェクトストレージが使えるようになります。

が、自分の環境では OBC (Object Bucket Claim ) の status が Pending からずっと変わらない事象が発生しました。 (正しく動けば Bound になるはず)

Pending の状態だからオブジェクトストレージとしては使えないだろう、と思い数日原因を探っていたのですが、結局分からないままでした。

試しに Rook 公式ドキュメントに記載されている s3cmd を使ってテストしたところ、Pending の状態でもオブジェクトの格納や取得はできてしまいました。

今回の検証は上記のような状態でおこなっていますが、後日ちゃんと対応しようと思います。

また本筋から逸れますが、なぜ MinIO 以外のオブジェクトストレージを用意したのかというと「K8s クラスタ上に MinIO をデプロイしても内部では Rook Ceph のブロックストレージを使うためオーバーヘッドが掛かるのでは」と考えたからです。

Ceph のアーキテクチャ図を見ると分かりやすいのですが、Ceph 自体は RADOS (Reliable Autonomic Distributed Object Store) というオブジェクトストアを土台として構築し、その上で S3 互換オブジェクトストレージの RADOSGWブロックストレージの RDB、ファイルストレージの CephFS が動いています。

f:id:nnstt1:20210212222818p:plain
Ceph Architecture

そのため、MinIO を使って K8s クラスタ内にオブジェクトストレージを構築しても、Rook Ceph の StorageClass (ブロックストレージ) を使っていては「オブジェクトストレージ on ブロックストレージ on オブジェクトストア」のようになってしまうのでは、RADOSGW を使ってオブジェクトストレージを使ったほうが性能が良いのでは、と考えました。

考えました……。

が、執筆時に MinIO のサンプルマニフェストを見返して気付いたのですが、MinIO の volumes では emptyDir を使っていました。 つまり、Rook Ceph のブロックストレージを使っていませんでした。

最初に試した MinIO のデプロイは上記サンプルのものではなく別の方のマニフェストを参考にしていて、そちらでは PVC を使っていたので Rook Ceph を使っているものと勘違いしたまま考察してしまいました。

上記の考察は、Velero 公式リポジトリの MinIO サンプルマニフェストでは意味がないです。

こちらも後日 PVC を使った MinIO を用意して RADOSGW との性能差を測定をしてみます。

Velero を試す

オブジェクトストレージの用意ができたので、いよいよ Velero を試してみます。

インストール

Velero を K8s クラスタにインストールする方法は以下の2通りあります。

  • velero コマンドを使った velero install
  • Helm chart

今回は、velero コマンドを Linux クライアント環境にインストールする方法で試しました。 (Helm に苦手意識があるので…)

velero.io

まずは velero コマンドを準備します。 MacWindows だと Homebrew や Chocolatey といったパッケージマネージャが使えるのですが、Linux のため公式からバイナリをダウンロードしてきました。 velero ファイルを PATH の通ったとこに配置するだけで完了です。

次に、オブジェクトストレージの認証情報を記載したファイル (./credentials-velero) を用意します。

[default]
aws_access_key_id = minio
aws_secret_access_key = minio123

上記の認証情報は MinIO (サンプルマニフェスト)の場合ですが、Rook Ceph の場合は以下のコマンドで認証情報を取得できます。

$ kubectl get secret ceph-delete-bucket -o jsonpath='{.data.AWS_ACCESS_KEY_ID}' | base64 --decode
$ kubectl get secret ceph-delete-bucket -o jsonpath='{.data.AWS_SECRET_ACCESS_KEY}' | base64 --decode

velero コマンドの準備ができたらクライアントから velero install すればよいのですが、MinIO と Rook Ceph の場合でバケット名や s3Url など微妙に異なるので、適宜変更してください。

  • MinIO
$ velero install \
    --provider aws \
    --plugins velero/velero-plugin-for-aws:v1.0.0 \
    --bucket velero \
    --secret-file ./credentials-velero \
    --use-volume-snapshots=false \
    --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://minio.minio.svc:9000
  • Rook Ceph
$ velero install \
    --provider aws \
    --plugins velero/velero-plugin-for-aws:v1.0.0 \
    --bucket ceph-bkt-c71df44b-8657-4f02-b680-fe0894debc07 \
    --secret-file ./credentials-velero \
    --use-volume-snapshots=false \
    --backup-location-config region=us-east-1,s3ForcePathStyle="true",s3Url=http://rook-ceph-rgw-my-store.rook-ceph.svc

namespace: velero に velero Pod がデプロイされていればインストール完了です。

バックアップ

Velero 公式リポジトリに用意された Nginx のサンプルマニフェストを使って、バックアップ・リストアの確認をします。

# テスト用リソースの準備
$ kubectl apply -f https://github.com/vmware-tanzu/velero/blob/main/examples/nginx-app/base.yaml

$ kubectl get deployments -l component=velero --namespace=velero
NAME     READY   UP-TO-DATE   AVAILABLE   AGE
velero   1/1     1            1           24h

$ kubectl get all --namespace=nginx-example
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-57d5dcb68-tks6f   1/1     Running   0          24h
pod/nginx-deployment-57d5dcb68-wd456   1/1     Running   0          24h

NAME               TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
service/my-nginx   LoadBalancer   10.101.167.57   192.168.2.242   80:32308/TCP   24h

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   2/2     2            2           24h

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-57d5dcb68   2         2         2       24h


# バックアップ
$ velero backup create nginx-backup --selector app=nginx
Backup request "nginx-backup" submitted successfully.
Run `velero backup describe nginx-backup` or `velero backup logs nginx-backup` for more details.

# バックアップリソースの確認
$ velero backup get
NAME           STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
nginx-backup   Completed   0        0          2021-02-13 00:05:24 +0900 JST   29d       default            app=nginx

無事にバックアップは取れたようです。

リストア

Velero によるバックアップが成功したら、サンプルリソースを削除してバックアップデータからリストアできるか確認します。

# テスト用リソースの削除
$ kubectl delete namespace nginx-example
namespace "nginx-example" deleted

$ kubectl get all --namespace=nginx-example
No resources found in nginx-example namespace.


# リストア
$ velero restore create --from-backup nginx-backup
Restore request "nginx-backup-20210213000816" submitted successfully.
Run `velero restore describe nginx-backup-20210213000816` or `velero restore logs nginx-backup-20210213000816` for more details.

# リストアリソースの確認
$ velero restore get
NAME                          BACKUP         STATUS      STARTED                         COMPLETED                       ERRORS   WARNINGS   CREATED                         SELECTOR
nginx-backup-20210213000816   nginx-backup   Completed   2021-02-13 00:08:16 +0900 JST   2021-02-13 00:08:16 +0900 JST   0        0          2021-02-13 00:08:16 +0900 JST   <none>

$ kubectl get all --namespace=nginx-example
NAME                                   READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-57d5dcb68-tks6f   1/1     Running   0          29s
pod/nginx-deployment-57d5dcb68-wd456   1/1     Running   0          29s

NAME               TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
service/my-nginx   LoadBalancer   10.109.90.104   192.168.2.242   80:30316/TCP   29s

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-deployment-57d5dcb68   2         2         2       29s

一部リストアもできたようですが、Deployments だけがリストアできていません。

これは Nginx サンプルマニフェストで Deployments にラベルが設定されておらず、バックアップ対象のリソースとして選択されなかったからです。 Nginx サンプルマニフェストを弄ってラベルをつけてあげると、ちゃんとバックアップ・リストアされるようになりました。

おわりに

今回は Kubernetes クラスタのバックアップツール Velero を試してみました。

Velero 自身はとても簡単に使えるのですが、オブジェクトストレージへの理解が低いためか、オブジェクトストレージの準備にとても時間が掛かってしまいました。 記事もオブジェクトストレージ中心で Velero はオマケ程度になってしまいました。

また、K8s クラスタ内にオブジェクトストレージを用意したため、クラスタが壊れてしまったらバックアップデータも吹き飛ぶ構成となっています。 ちゃんとバックアップとして利用するためには外部ストレージが必要ですので、自宅でも別途ストレージを用意してみたい気持ちが出てきましたので、お財布と相談してみます。