VMware SQL with MySQL for Kubernetesのインストール方法やバックアップ方法を確認する

[VMware SQL with MySQL for Kubernetes](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/index.html)は以前はTanzu SQLとして展開されていたもので、Kubernetesクラスタ上にMySQLインスタンスを管理するOperatorを展開してくれるものだ。このOperatorがある状態でカスタムリソースであるMySQLの定義(Manifest)をクラスタ上でapplyすると、簡単にDBが構築できる。すなわち、DDB as a Serviceのような感じで簡単にDBを用意することが出来る。

コンポーネントとしては以下が含まれる。
- Percona Server 8.0
- Percona XtraBackup 8.0

Percona ServerはMySQLを拡張させたオープンソースのDBで、[こちら](https://github.com/percona/percona-server)で開発が行われている。
無料で完全な互換性があり、パフォーマンス、拡張性、可視性で優れているとのこと。
Percona XtraBackupもオープンソースのバックアップユーティリティで、[こちら](https://github.com/percona/percona-xtrabackup)で開発が行われている。
バックアップ中にロックをせず、DBのパフォーマンスを落とさずにシームレスにバックアップできるとのこと。

今回はVMware SQL with MySQL for Kubernetesの構築からこのバックアップを使うところまでやってみる。
なお、バージョンは1.7.1を利用し、TKG2.2.0上で構築するものとする。

## 前提条件
[こちら](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/install-operator.html#prerequisites)によると、以下の条件を揃えておく必要がある。
- kubectlとkubernetesクラスタが利用可能（バージョン指定なし）
- kubectlを`cluster-admin`権限で利用可能
- Kubernetesクラスタ内にcert-managerが構築済み
- Helm CLIが利用可能 (v3.8.0以降)
- registry.tanzu.vmware.comにアクセスするためのユーザ名とパスワードを用意
- Docker daemonが起動している

registry.tanzu.vmware.comにアクセスするアカウント情報は[Tanzu Network](https://network.pivotal.io/)にアクセスするものと同一になる。
また、バックアップを利用する場合はS3互換ストレージが必要となる。
ここではMinIOを利用する。MinIOの構築手順は[過去の記事](https://qiita.com/ipppppei/items/ad0b01498e62c2e55256)で書いているためここでは割愛する。


<!-- また、Tanzu Networkからファイルを取得する際に今回はCLIを利用してダウンロードする。
[`pivnet`](https://github.com/pivotal-cf/pivnet-cli)コマンドが必要になるので、こちらを入手してインストールする。
```
wget https://github.com/pivotal-cf/pivnet-cli/releases/download/v3.0.1/pivnet-linux-amd64-3.0.1
sudo install pivnet-linux-amd64-3.0.1 /usr/local/bin/pivnet
rm pivnet-linux-amd64-3.0.1
```
また、利用するためのAPIを払い出しておく。
[Tanzu Network](https://network.pivotal.io/)にログイン後、アカウント名をクリックして[Edit Profile](https://network.pivotal.io/users/dashboard/edit-profile)を選択する。
下の方に`UAA API TOKEN`の項目の横に`REQUEST NEW REFRESH TOKEN`があるのでクリックしてTokenを取得する。

ブラウザからダウンロードしたい人は上記手順は省略可。 -->

## インストール
[こちら](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/install-operator.html)の手順に従い進めていく。

最初にTanzu Networkのユーザ名とパスワードを設定しておく。
```
export TANZUNET_USERNAME=xxx@hoge.info
export TANZUNET_PASSWORD=xxxx
```
レジストリにログインして、Helm Chartを取得する。公式手順だとHelm Chartを/tmpに展開していたが、残しておきたいのでカレントディレクトリに変更している。
```sh
echo $TANZUNET_PASSWORD | helm registry login registry.tanzu.vmware.com -u $TANZUNET_USERNAME --password-stdin
helm pull oci://registry.tanzu.vmware.com/tanzu-mysql-for-kubernetes/vmware-sql-with-mysql-operator --version 1.7.1 --untar --untardir ./
```

Namespaceを作成し、MySQLインスタンスを作る際にイメージをpullするための認証情報を格納したSecretを作成する。
```sh
kubectl create namespace vmware-mysql-for-kubernetes-system
kubectl create secret docker-registry tanzu-image-registry \
    --docker-server=https://registry.tanzu.vmware.com/ \
    --docker-username="${TANZUNET_USERNAME}" \
    --docker-password="${TANZUNET_PASSWORD}" \
    --namespace=vmware-mysql-for-kubernetes-system
```
必要に応じて`vmware-sql-with-mysql-operator/values.yaml`を編集する。
編集可能な項目とその説明は[こちら](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/install-operator.html#install-the-operator)の中にある表を参照。
今回はデフォルト値を利用するため、特に編集していない。
最後にインストールする。
```sh
helm install --wait my-mysql-operator ./vmware-sql-with-mysql-operator/ \
  --namespace=vmware-mysql-for-kubernetes-system \
  --create-namespace
```
※追記：`values.yaml`を編集しない場合は`helm pull`を飛ばして以下でもOK。
```s
helm install --wait my-mysql-operator oci://registry.tanzu.vmware.com/tanzu-mysql-for-kubernetes/vmware-sql-with-mysql-operator \
  --namespace=vmware-mysql-for-kubernetes-system \
  --create-namespace
```

インストールに無事成功すれば、以下のような感じで各種リソースが取得できる。
```sh
$ kubectl get all --namespace=vmware-mysql-for-kubernetes-system
NAME                                     READY   STATUS    RESTARTS   AGE
pod/my-mysql-operator-5898d897f6-5854t   1/1     Running   0          92s

NAME                                   TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
service/vmware-mysql-webhook-service   ClusterIP   100.66.6.61   <none>        443/TCP   92s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-mysql-operator   1/1     1            1           92s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/my-mysql-operator-5898d897f6   1         1         1       92s
```

### インストールされるカスタムリソースと動作確認
VMware SQL with MySQLでは以下のカスタムリソースがインストールされる。
- `kind: MySQL`：MySQLインスタンス本体
- `kind: MySQLVersion`：利用可能なDBのバージョンの管理
- `kind: MySQLBackup`：バックアップのリクエスト
- `kind: MySQLBackupLocation`：バックアップに使うS3のバケットを定義
- `kind: MySQLBackupSchedule`：バックアップのスケジュールの定義
- `kind: MySQLRestore`：リストアのリクエスト

ここではメインとなる`kind: MySQL`の挙動だけ確認しておく。
`default`Namespaceに`kind: MySQL`オブジェクトを作成してみる。
MySQLインスタンスのイメージは`registry.tanzu.vmware.com`にあるため、Pullする際に認証情報が必要となる。
Namespaceごとに認証情報を用意するのが面倒であれば、`tanzu secret registry add`で全Namespaceから参照できるようにし、Exportするとよい。
```
tanzu secret registry add tanzu-image-registry-public --server registry.tanzu.vmware.com --username ${TANZUNET_USERNAME} --password ${TANZUNET_PASSWORD} --export-to-all-namespaces --yes --namespace vmware-mysql-for-kubernetes-system
```
Importには`SecretImport`を使う。TKG2.2.0であれば標準で利用できる。
```yaml
cat << EOF > ./secret-import.yaml
apiVersion: secretgen.carvel.dev/v1alpha1
kind: SecretImport
metadata:
  name: tanzu-image-registry-public
spec:
  fromNamespace: vmware-mysql-for-kubernetes-system
EOF
kubectl apply -f ./secret-import.yaml -n default
```
Secretが取り込まれていることがわかる。
```sh
$ kubectl get secret tanzu-image-registry-public
NAME                          TYPE                             DATA   AGE
tanzu-image-registry-public   kubernetes.io/dockerconfigjson   1      2m6s
```
次に作成したSecretを使ってMySQLインスタンスを作成する。
[こちら](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/architecture.html#mysql)のManifestを流用し、Secret名だけ変更して実行する。
```yaml
cat << EOF > ./mysql-sample.yaml
apiVersion: with.sql.tanzu.vmware.com/v1
kind: MySQL
metadata:
  name: mysql-sample
spec:
  resources:
    mysql:
      requests:
        memory: 1Gi
  storageSize: 1Gi
  imagePullSecretName: tanzu-image-registry-public
EOF
kubectl apply -f ./mysql-sample.yaml -n default
```
起動すると、以下のような形でOperatorやDBのバージョンが取得できる。
```
$ kubectl get mysql
NAME           READY   STATUS    AGE   OPERATOR VERSION   DB VERSION   UPDATE STATUS      USER ACTION
mysql-sample   true    Running   10m   1.7.1              8.0.31       NoUpdateRequired
```
なお、`kubectl tree`で確認すると以下のような構造となる。
```sh
NAMESPACE  NAME                                                                   READY  REASON  AGE
default    MySQL/mysql-sample                                                     -              112s
default    ├─Certificate/mysql-sample-metrics-tls                                 True   Ready   111s
default    │ └─CertificateRequest/mysql-sample-metrics-tls-9k49n                  True   Issued  111s
default    ├─Certificate/mysql-sample-mysql-tls                                   True   Ready   112s
default    │ └─CertificateRequest/mysql-sample-mysql-tls-w4ngn                    True   Issued  111s
default    ├─ConfigMap/mysql-sample-autotune-config                               -              111s
default    ├─RoleBinding/tanzu-mysql-backup-cron-mysqlbackup-creator-rolebinding  -              112s
default    ├─RoleBinding/tanzu-mysql-sidecar-rolebinding                          -              112s
default    ├─Secret/mysql-sample-app-user-db-secret                               -              111s
default    ├─Secret/mysql-sample-credentials                                      -              111s
default    ├─Secret/mysql-sample-metrics-tls                                      -              110s
default    ├─Secret/mysql-sample-mysql-tls                                        -              110s
default    ├─Service/mysql-sample                                                 -              111s
default    │ └─EndpointSlice/mysql-sample-l48fx                                   -              111s
default    ├─Service/mysql-sample-members                                         -              111s
default    │ └─EndpointSlice/mysql-sample-members-kkvs9                           -              111s
default    ├─ServiceAccount/tanzu-mysql                                           -              112s
default    ├─ServiceAccount/tanzu-mysql-backup-cron                               -              112s
default    └─StatefulSet/mysql-sample                                             -              111s
default      ├─ControllerRevision/mysql-sample-788c8dc948                         -              111s
default      └─Pod/mysql-sample-0                                                 True           111s
```
証明書、RBAC(RoleBinding, SA))や設定周り(ConfigMap,Secret)、Service, StatefulSetあたりで構成されることが分かる。

この後は使わないので消しておく。なお、削除してもPVC、PVが残るので、完全削除するにはPVCも削除する。
```
kubectl delete mysql mysql-sample
kubectl delete pvc mysql-data-mysql-sample-0
```


## サンプルアプリの起動
公式ドキュメントの[Connectiong Application](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/connecting-apps.html)に従ってサンプルアプリを立ち上げてデータを格納する。
### サンプルアプリ用DBの構築
公式手順は[こちら](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/create-delete-mysql.html)となる。

先程動作確認用に作成したManifestにmysqlのVersionを追加したものを用意した。
（利用可能なMySQLのバージョンは`kubectl get mysqlversions`で取得可能。）
```yaml
cat << EOF > ./mysql-sample.yaml
apiVersion: with.sql.tanzu.vmware.com/v1
kind: MySQL
metadata:
  name: mysql-for-wordpress
spec:
  mysqlVersion:
    name: mysql-8.0.29
  resources:
    mysql:
      requests:
        memory: 1Gi
  storageSize: 1Gi
  imagePullSecretName: tanzu-image-registry-public
EOF
kubectl apply -f ./mysql-sample.yaml -n default
```
起動したことを確認する。
```sh
$ kubectl get mysql
NAME                  READY   STATUS    AGE     OPERATOR VERSION   DB VERSION   UPDATE STATUS      USER ACTION
mysql-for-wordpress   true    Running   4m54s   1.7.1              8.0.29       NoUpdateRequired
```
ここからは、[こちら](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/connecting-apps.html)の記述に従いサンプルアプリ用にDBの中をいじっていく。
MySQLのコンテナ内に入る。
```sh
kubectl exec -it mysql-for-wordpress-0 -c mysql -- bash
```
MySQLに接続する。環境変数やパスワードのファイルはPod内に持っているので特に設定等する必要はない。
```sh
mysql -p$(cat $MYSQL_ROOT_PASSWORD_FILE) -u root
```
以下実行し、`bitnami_wordpress`DBとユーザ`bn_wordpress`を作成する。
```
CREATE DATABASE bitnami_wordpress;
CREATE USER 'bn_wordpress'@'%' IDENTIFIED BY 'hunter2';
GRANT ALL PRIVILEGES ON bitnami_wordpress.* TO 'bn_wordpress'@'%';
FLUSH PRIVILEGES;
exit;
```

Podを抜けた後、cert-managerを使って証明書を発行する。
```yaml
cat << EOF | kubectl apply -f -
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: ca-certificate
spec:
  isCA: true
  issuerRef:
    name: selfsigned-issuer
  secretName: ca-secret
  commonName: ca-cert
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: tls-issuer
spec:
  ca:
    secretName: ca-secret
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: tls-certificate
spec:
  isCA: false
  dnsNames:
    - mysql-sample.default # See note after the code excerpt
  secretName: mysql-tls-secret
  issuerRef:
    name: tls-issuer
EOF
```
既存のMySQLインスタンスにTLSの設定を追加する。
```sh
kubectl patch mysql mysql-for-wordpress --type merge -p '{"spec":{"tls":{"secret":{"name":"mysql-tls-secret"}}}}'
```
追加後、ログを確認するとTLSサポートが確認できる。
```sh
$ kubectl logs pod/mysql-for-wordpress-0 -c mysql | grep TLS
2023-06-01T01:40:12.245902Z 0 [System] [MY-013602] [Server] Channel mysql_main configured to support TLS. Encrypted connections are now supported for this channel.
2023-06-01T01:40:12.325310Z 0 [Warning] [MY-000067] [Server] unknown variable 'loose-group-replication-recovery-tls-version=TLSv1.2,TLSv1.3'.
```
### サンプルアプリの用意
bitnami版WordPressを利用するため、最初にbitnamiリポジトリを追加する。
```
helm repo add  bitnami https://charts.bitnami.com/bitnami
```
WordPressの`values.yaml`が[公式手順](https://docs.vmware.com/en/VMware-SQL-with-MySQL-for-Kubernetes/1.7/vmware-mysql-k8s/connecting-apps.html#configure-your-app-with-mysql-user-and-connectivity-information)に用意されているので、これをコピーして一部修正してデプロイする。
```yaml
cat << EOF > bitnami-wordpress-values.yaml
mariadb:
  enabled: false

externalDatabase:
  host: mysql-for-wordpress.default.svc.cluster.local
  user: bn_wordpress
  password: hunter2
  database: bitnami_wordpress

extraEnvVars:
- name: "WORDPRESS_DATABASE_SSL_CA_FILE"
  value: "/etc/mysql/tls/ca.crt"
- name: "WORDPRESS_ENABLE_DATABASE_SSL"
  value: "yes"
- name: "BITNAMI_DEBUG"
  value: "true"

extraVolumes:
- name: mysql-ca
  secret:
    secretName: "mysql-tls-secret"
    items:
    - key: ca.crt
      path: ca.crt

extraVolumeMounts:
  - name: mysql-ca
    mountPath: /etc/mysql/tls/
EOF
helm install wp bitnami/wordpress -f ./bitnami-wordpress-values.yaml
```
なお、`extraEnvVars`に`WORDPRESS_ENABLE_DATABASE_SSL`をセットしないと動かない。同僚に聞いたところ、[WordPress側で同パラメタが設定されていないとMySQLとの接続を有効化してくれない](https://github.com/bitnami/containers/blob/41d2009f0986277418ca3fb0df1a5bbd2c4ed53e/bitnami/wordpress/6/debian-11/rootfs/opt/bitnami/scripts/libwordpress.sh#L183)とのこと。
実際、このパラメタなしで実行するとPodが停止するため、必ず付与してデプロイする。

起動後、デフォルトではWordPressは`type:LoadBalancer`で公開しているので、公開先IPにアクセスする。
![1685584765362.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/27ef6609-6fa3-c670-753a-3fa69f72a501.png)

`Hello world!`をクリックするとコメントを残すことが出来るので、適当に入力して`Post Comment`をクリックする。
![1685585378791.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/41aa8d85-1b0a-3433-432c-6e3979e15cda.png)

また、`<IP address>/admin`にアクセスすると管理画面にアクセスできる。
- ユーザ名：`user`
- パスワード：`kubectl get secret --namespace default wp-wordpress -o jsonpath="{.data.wordpress-password}" | base64 -d`

でログインすることが出来る。
左サイドバーの`Posts`からHello world!をEditし、タイトルを修正して右上の`Update`で保存する。
![1685585635943.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5806dab2-8e4a-d086-9840-8815fb0fe05b.png)
変更後の画面は以下のようになっている。

![1685585759564.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/1faaa678-6a78-af97-ccff-1105f4f8cc59.png)

これをバックアップ・リストアして復元できることを確認する。

## サンプルアプリのバックアップ・リストア
先ほど作成したサンプルアプリをバックアップする。バックアップにはS3互換ストレージが必要になる。
ここではMinIOを利用する。

### バックアップ
最初にバックアップの格納先リソース(`kind: MySQLBackupLocation`)を作成する。
以下の`bucket`、`endpoint`、`accessKeyId`、`secretAccessKey`は自環境のものにあわせて修正する。また、`forcePathStyle: true`がないとendpointのドメインの先頭にバケット名を含めてアクセスしに行くため、MinIOを使う場合は必須となる。
```yaml
cat << 'EOF' > ./backup-location.yaml
apiVersion: with.sql.tanzu.vmware.com/v1
kind: MySQLBackupLocation
metadata:
  name: mysql-location
spec:
  storage:
    s3:
      bucket: "mysql-bucket"
      endpoint: "http://10.222.147.222:9000"
      forcePathStyle: true
      secret:
        name: minio-creds
---
apiVersion: v1
kind: Secret
metadata:
  name: minio-creds
stringData:
  accessKeyId: "minio"
  secretAccessKey: "minio123"
EOF
kubectl apply -f ./backup-location.yaml
```
次にバックアップの要求を行う`kind: MySQLBackup`リソースを作成する。
`location.name`は先程作成した`MySQLBackupLocation`オブジェクトの名前を、`instance.name`は起動している`MySQL`オブジェクトの名前を指定する。
```yaml
cat << EOF > ./backup.yaml
apiVersion: with.sql.tanzu.vmware.com/v1
kind: MySQLBackup
metadata:
  name: backup-wp
spec:
  location:
    name: mysql-location
  instance:
    name: mysql-for-wordpress
EOF
kubectl apply -f ./backup.yaml
```
`MySQLBackupLocation`に問題がなければ、バックアップが実行(Running)される。
```
$ kubectl get mysqlbackup
NAME        STATUS    SOURCE INSTANCE       TIME STARTED           TIME COMPLETED
backup-wp   Running   mysql-for-wordpress   2023-06-01T02:46:14Z
```
成功するとSucceededに変わる。
```
$ kubectl get mysqlbackup
NAME        STATUS      SOURCE INSTANCE       TIME STARTED           TIME COMPLETED
backup-wp   Succeeded   mysql-for-wordpress   2023-06-01T02:46:14Z   2023-06-01T02:46:23Z
```
MinIO内にもファイルが作成されている。
![1685587806956.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/da5e3e9b-ccb1-0048-918e-0d448fba1695.png)

### リストア
問題なくMySQLインスタンスが作成されるか確認する。
一旦WordPressとMySQLインスタンスを削除する。

```
helm delete wp
kubectl delete mysql mysql-for-wordpress
kubectl delete pvc mysql-data-mysql-for-wordpress-0
```
ここから`MySQLRestore`リソースを使って復元する。以下のManifestをapplyする。
なお、`backup.name`に作成した`MySQLBackup`オブジェクトの名前を、`instanceTemplate.metadata.name`に`MySQL`インスタンス名（バックアップと一致しなくてOK）を指定する。また`imagePullSecretName`はPullが可能なSecretを指定する。
```yaml
cat << EOF > ./restore.yaml
apiVersion: with.sql.tanzu.vmware.com/v1
kind: MySQLRestore
metadata:
  name: restore-wp
spec:
  backup:
    name: backup-wp
  instanceTemplate:
    metadata:
      name: mysql-for-wordpress-restored
    spec:
      storageSize: 1Gi
      imagePullSecretName: tanzu-image-registry-public
EOF
kubectl apply -f ./restore.yaml
```
こちらも`MySQLBackupLocation`に問題がなければ、リストアが実行(Running)され、完了するとSucceededになる。
```
$ kubectl get mysqlrestore
NAME         STATUS      SOURCE BACKUP   TARGET INSTANCE                TIME STARTED           TIME COMPLETED
restore-wp   Succeeded   backup-wp       mysql-for-wordpress-restored   2023-06-01T02:55:07Z   2023-06-01T02:57:28Z
```
この状態でWordPressを立ち上げる。
MySQLインスタンス名を変更したため、values.yamlのDB参照パスを以下のように変更する。
```
externalDatabase:
  host: mysql-for-wordpress-restored.default.svc.cluster.local
```
変更が終わったら、helm installで立ち上げる。
```
helm install wp bitnami/wordpress -f ./bitnami-wordpress-values.yaml
```
バックアップ前に変更した内容が確認できた。
![1685588592950.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/a6a809b9-1944-9e61-cc68-30d704d57aef.png)