はじめに
本検証では、Kubernetes NativeのバックアップツールKanisterの動作検証を行います。
Kanisterは、Veeam社のKasten(K10)のベースとなっているOSSです。Kanisterは、Kubernetes上で実行されているアプリケーションのデータをオブジェクトストレージへバックアップします。
Kanisterは、アプリケーションセントリックを特徴としていることもあり、アプリケーションごとのテンプレート(Blueprint)が用意されています。Blueprintには、アプリケーションごとのバックアップの前準備、後始末などの処理が実装されているため、アプリケーションに優しいバックアップを実施することができます。
2020/12時点でBlueprintが用意されているアプリケーションは以下になります。
- Cassandra
- Couchbase
- Elasticsearch
- FoundationDB
- MongoDB
- MySQL on OpenShift using DeploymentConfig
- MySQL
- PostgreSQL with Point In Time Recovery (PITR)
- ETCD
- PostgreSQL
検証環境
- minikube
- Kubernetes v1.19.4
- MinIO RELEASE.2020-12-10T01-54-29Z
- MySQL 5.7
- Kanister 0.44.0
事前準備
事前準備として、バックアップデータの格納先となるオブジェクトストレージ(MinIO)と、バックアップ対象のMySQLを準備します。
MinIOのセットアップ
はじめに、MinIOのNamespace(minio
)を作成し、移動します。
$ kubectl create ns minio
$ kubens minio
次にManifest(minio.yaml
)を使ってMinIOをセットアップします。
- minio.yaml
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
ports:
- port: 9000
targetPort: 9000
selector:
app: minio
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
spec:
serviceName: "minio"
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: minio-pvc
containers:
- name: minio
volumeMounts:
- name: data
mountPath: "/data"
image: minio/minio:RELEASE.2020-12-10T01-54-29Z
args:
- server
- /data
env:
- name: MINIO_ACCESS_KEY
value: "minio"
- name: MINIO_SECRET_KEY
value: "minio123"
ports:
- containerPort: 9000
hostPort: 9000
minio.yamlをデプロイし、MinIOの起動を確認します。
$ kubectl apply -f minio.yaml
$ kubectl get svc,pod,pvc,pv
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/minio ClusterIP 10.106.197.201 <none> 9000/TCP 2m57s
NAME READY STATUS RESTARTS AGE
pod/minio-0 1/1 Running 0 2m57s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/minio-pvc Bound pvc-f1016d2e-2fc5-48a0-befb-8a7304281b16 1Gi RWO standard 2m57s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-f1016d2e-2fc5-48a0-befb-8a7304281b16 1Gi RWO Delete Bound minio/minio-pvc standard 2m57s
Portwardを行った後、MinIOのWebUIにアクセスしてみます。
$ kubectl port-forward -n minio svc/minio 9000:9000 &
ブラウザでURL(http://localhost:9000
)にアクセスします。
AccessKey(minio
), Secret Key(mino123
)でアクセスできます。
本検証用のBuketを用意しておきます。
WebUIの右下の+
をクリックし、Create Bucket
を選択後、Bucket名にkanister
と入力しBuketを作成します。
以上でMinIOの準備が完了しました。
MySQLのセットアップ
今回はKanisterの動作検証用のMySQLのため、単一インスタンスのMySQLを作成します。
MySQLはNamespace(default
)にセットアップします。
$ kubens default
次にManifest(mysql.yaml
)を使ってMySQLをセットアップします。
- mysql.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql
clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/instance: mysql
spec:
serviceName: "mysql"
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: mysql:5.7
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql
key: mysql-root-password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
MySQLのrootのパスワードをSecret(mysql
)として作成後,
Manifest(mysql.yaml
)をデプロイしMySQLの起動を確認します。
今回の検証で利用するKanisterのBlueprint(mysql-blueprint
)はLabel app.kubernetes.io/instance:
の値をSecret名として(なぜか)利用しているため、設定する必要があります。
$ kubectl create secret generic mysql \
--from-literal=mysql-root-password='password'
$ kubectl apply -f mysql.yaml
$ kubectl get svc,pod,pvc,pv
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 67m
service/mysql ClusterIP None <none> 3306/TCP 2m26s
NAME READY STATUS RESTARTS AGE
pod/mysql-0 1/1 Running 0 2m26s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-mysql-0 Bound pvc-cbbb914b-6911-477c-b77a-1adc3c849d1a 1Gi RWO standard 2m26s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-cbbb914b-6911-477c-b77a-1adc3c849d1a 1Gi RWO Delete Bound default/data-mysql-0 standard 2m26s
persistentvolume/pvc-f1016d2e-2fc5-48a0-befb-8a7304281b16 1Gi RWO Delete Bound minio/minio-pvc standard 24m
MySQLにアクセスしてみます。
MySQLのrootのパスワードはpassword
にmysql.yamlで設定したため、これを使いSELECT 1
を実施しアクセスできることを確認します。
$ kubectl run mysql-client --image=mysql:5.7 -it --rm --restart=Never -- mysql -h mysql -uroot -ppassword -e 'SELECT 1'
mysql: [Warning] Using a password on the command line interface can be insecure.
+---+
| 1 |
+---+
| 1 |
+---+
pod "mysql-client" deleted
次に、本検証用に適当なテーブルを作成しレコードを追加します。
$ kubectl exec -ti mysql-0 -- bash
root@mysql-0:/# mysql -p
Enter password: # MySQLのrootのパスワードを入力
...
mysql> CREATE DATABASE test;
mysql> USE test;
mysql> CREATE TABLE pets (name VARCHAR(20), species VARCHAR(20));
mysql> INSERT INTO pets VALUES ('flare', 'dog');
mysql> INSERT INTO pets VALUES ('reno', 'dog');
mysql> SELECT * FROM pets;
+-------+---------+
| name | species |
+-------+---------+
| flare | dog |
| reno | dog |
+-------+---------+
2 rows in set (0.00 sec)
mysql> exit
root@mysql-0:/# exit
以上で事前準備が完了しました。
動作検証
Kanisterのセットアップ
長かった前準備ですが、いよいよ本題のKanisterをセットアップします。
はじめに、KanisterのNamespace(kanister
)を作成し、移動します。
$ kubectl create ns kanister
$ kubens kanister
次に、Helmを使ってKanister Operatorをセットアップします。
$ helm repo add kanister http://charts.kanister.io
$ helm install myrelease --namespace kanister kanister/kanister-operator --set image.tag=0.44.0
kanister operatorの起動を確認します。
$ $ kubectl get deploy,pod
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/myrelease-kanister-operator 1/1 1 1 53s
NAME READY STATUS RESTARTS AGE
pod/myrelease-kanister-operator-6f97fd58f6-dqt8g 1/1 Running 0 53s
以上で、Kanister Operatorのセットアップが完了です。
Kanister では以下の3つのCRDもデプロイされています。
$ kubectl get crd |grep kanister
actionsets.cr.kanister.io 2020-12-12T05:52:57Z
blueprints.cr.kanister.io 2020-12-12T05:52:57Z
profiles.cr.kanister.io 2020-12-12T05:52:57Z
各CRの簡単な説明を以下に示します。
- Actionset
- バックアップやリストアの実行するタスク(アクション)を示すリソース
- Blueprint
- バックアップやリストアなどの処理を定義するテンプレートのリソース
- Profile
- バックアップ先となるオブジェクトストレージのロケーション情報などを定義するリソース
Kanisterでは、上記の3つのCRを使いバックアップ/リストアを実施します。
Kanisterのワークフローを以下に示します。
Profileの作成
まずは、バックアップの格納先として先ほど用意したMinIOの情報を使いProfileを作成します。
ProfileのManifest(profile.yaml
)を以下に示します。
- profile.yaml
apiVersion: cr.kanister.io/v1alpha1
kind: Profile
metadata:
name: s3-profile
namespace: kanister
location:
type: s3Compliant
bucket: kanister
endpoint: http://minio.minio.svc:9000
credential:
type: keyPair
keyPair:
idField: minio_access_key_id
secretField: minio_secret_access_key
secret:
apiVersion: v1
kind: Secret
name: minio-secret
namespace: kanister
skipSSLVerify: true
なお、今回作成したMinIOではTLSを設定していないため、skipSSLVerify
はtrue
としています。
profile.yamlをデプロイします。
$ kubectl apply -f profile.yaml
次に、MinIOのAccessKey, Secret KeyをもったSecret(minio-secret
)を作成します。
$ kubectl create secret -n kanister generic minio-secret \
--from-literal=minio_access_key_id='minio' \
--from-literal=minio_secret_access_key='minio123'
MySQL用のBlueprintの準備
次に、MySQLのバックアップ・リストアの処理の記載されたBlueprintのManifest(mysql-blueprint.yaml
)をデプロイします。
$ git clone git@github.com:kanisterio/kanister.git
$ kubectl apply -f kanister/examples/stable/mysql/mysql-blueprint.yaml
$ kubectl get blueprint
NAME AGE
mysql-blueprint 11m
バックアップの実行
いよいよ、バックアップを実行します。
Kanisterではバックアップ、リストアはActionsetsリソースを作成することで実行します。
バックアップのActionsetのManifest(backup.yaml
)を示します。
apiVersion: cr.kanister.io/v1alpha1
kind: ActionSet
metadata:
name: backup
namespace: kanister
spec:
actions:
- blueprint: mysql-blueprint
name: backup
object:
kind: statefulset
name: mysql
namespace: default
profile:
name: s3-profile
namespace: kanister
バックアップが完了するまで、しばし待ちます。
バックアップのステータスは、Actionset(backup
)のEventを見ることで確認できます。
$ kubectl describe actionset -n kanister backup
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Started Action 3m18s Kanister Controller Executing action backup
Normal Started Phase 3m18s Kanister Controller Executing phase dumpToObjectStore
Normal Ended Phase 35s Kanister Controller Completed phase dumpToObjectStore
Normal Update Complete 35s Kanister Controller Updated ActionSet 'backup' Status->complete
Status->complete
となりバックアップが完了しました。
MinIOのWebUIからバックアップされたデータを確認してみます。
本検証用のBuket(kanister
)配下に、ネームスペース名やStatefulSet名や日付で階層が作成され、その下に、dump.sql.gz
というバックアップデータが格納されていました。
リストアの実行
次にリストアを検証します。
リストアを検証するために、一旦MySQLを削除します。
$ kubens default
$ kubectl delete -f mysql.yaml
$ kubectl delete pvc data-mysql-0
再度、MySQLのManifest(mysql.yaml
)をデプロイします。
$ kubectl apply -f mysql.yaml
$ kubectl get svc,pod,pvc,pv
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 4h3m
service/mysql ClusterIP None <none> 3306/TCP 68s
NAME READY STATUS RESTARTS AGE
pod/mysql-0 1/1 Running 0 68s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-mysql-0 Bound pvc-cc959139-bf18-465d-a17c-e1727b4c1ac2 1Gi RWO standard 68s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-cc959139-bf18-465d-a17c-e1727b4c1ac2 1Gi RWO Delete Bound default/data-mysql-0 standard 68s
persistentvolume/pvc-f1016d2e-2fc5-48a0-befb-8a7304281b16 1Gi RWO Delete Bound minio/minio-pvc standard 3h21m
これで、データが初期状態となったMySQLになりました。
ここに、Kanisterでバックアップしたデータをリストアしていきます。
リストアもActionsetを作り実行します。
リストア用のActionsetのManifestを作るために必要となる情報(artifacts.mysqlCloudDump.keyValue.s3path
)を取得します。
$ kubectl get actionsets backup -o yaml |grep -A 3 artifacts
- artifacts:
mysqlCloudDump:
keyValue:
s3path: /mysql-backups/default/mysql/2020-12-12T08-26-08/dump.sql.gz
上記値を使い作成したリストア用のActionsetのManifest(restore.yaml
)を以下に示します。
- restore.yaml
apiVersion: cr.kanister.io/v1alpha1
kind: ActionSet
metadata:
name: restore
namespace: kanister
spec:
actions:
- name: restore
artifacts:
mysqlCloudDump:
keyValue:
s3path: /mysql-backups/default/mysql/2020-12-12T08-26-08/dump.sql.gz
blueprint: mysql-blueprint
object:
kind: statefulset
name: mysql
namespace: default
profile:
name: s3-profile
namespace: kanister
Manifest(restore.yaml
)をデプロイしリストアを実行します。
リストアが完了するまで、しばし待ちます。
リストアのステータスは、Actionset(backup
)のEventを見ることで確認できます。
$ kubectl describe actionset -n kanister restore
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Started Action 85s Kanister Controller Executing action restore
Normal Started Phase 85s Kanister Controller Executing phase restoreFromBlobStore
Normal Ended Phase 83s Kanister Controller Completed phase restoreFromBlobStore
Normal Update Complete 83s Kanister Controller Updated ActionSet 'restore' Status->complete
Status->complete
となり、リストアが完了しました。
MySQLに接続し、正しくリストアされているかを確認します。
$ kubens default
$ kubectl exec -ti mysql-0 -- bash
root@mysql-0:/# mysql -p
Enter password:
...
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.00 sec)
mysql> USE test
...
Database changed
mysql> SHOW TABLES;
+----------------+
| Tables_in_test |
+----------------+
| pets |
+----------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM pets;
+-------+---------+
| name | species |
+-------+---------+
| flare | dog |
| reno | dog |
+-------+---------+
2 rows in set (0.00 sec)
mysql> exit
Bye
root@mysql-0:/# exit
正しくリストアできていることが確認できました。
補足: Actionsetを簡単にデプロイするコマンド(kanctl)
ActionsetのManifestは、利用するBlueprintをチェックしながら作成する必要があり、慣れない人には少々ハードルが高いかと思います。
Kanisterではkanctl
コマンドというものが用意されています。
このコマンドを使えば、バックアップやリストアをManifestを用意しなくてもkanctl
コマンドでActionsetのリソースを作成することができます。
kanctl
コマンドは、以下でインスールできます。
$ curl https://raw.githubusercontent.com/kanisterio/kanister/master/scripts/get.sh | bash
例えば、上記検証のリストアはActionsetのMainfestを作成しなくても以下のkanctl
コマンドでも同様に実行できます。
$ kanctl --namespace kanister create actionset --action restore --from backup
感想
今回は、Kubernetes Nativeなバックアップ/リストアのKanisterの動作検証を行いました。Kanisterは、アプリケーションごとにBlueprintというテンプレートを用意することで、アプリケーションごとのやり方でバックアップ/リストアを行うというアプローチをとっているツールでした。Blueprintが既に用意されているアプリケーションであれば、簡単に利用することができます。ただし、Blueprintが用意されているからといって、過信することは危険です。利用する前には必ずBlueprintの中身をチェックし、どのような処理が行われているのを確認することをお勧めします。
例えば、今回利用したBlueprint(mysql-blueprint
)の中身はmysqldump
コマンドで取得したバックアップファイルをオブジェクトストレージへコピーしているだけのシンプルなものです。バックアップ中のレコードが書き換えられないように、書き込みをロックするなどDBのバックアップでセオリーとなるような処理は残念ながら入っていません。必要な人は、Blueprintに自分で追加する必要があります。
Kanisterは、GitHubのページを見るとA framework for data management in Kubernetes.
と記載されています。つまり、アプリケーションセントリックなバックアップ/リストアをするための、フレームワークとの位置付けのようです。バックアップ/リストア用のソフトとして提供しているKasten(K10)との差別化なのかもしれません。
また、似たようなKubernetes Nativeなバックアップ/リストアのツールとしてVeleroがあります。(Veleroの検証レポートはこちら)
VeleroとKanisterの違いが気になる人もいるかと思います。一言で違いを述べると、VeleroはKubernetesにデプロイしたリソース(Pod, PVC,PVなど)を一式まるごとバックアップ/リストアするのに優れています。一方、Kanisterはアプリケーションのデータのみをアプリケーションに適したやり方でバックアップ/リストアを行います。
Kubernetesのリソース丸ごとバックアップするのか、それとも特定のアプリケーションのデータのみバックアップするのかなど利用シーンにあわせて、選択するのをお勧めします。