Kubernatesを利用するシステムが増えて来ていることもあり、MySQL製品でもKubernates対応が加速してきています。
データベースのような複雑でステートフルなソフトウェアは、KubernatesのCoreコントローラのみでは正しく管理することが困難であることから、独自のCustom Resource Definition(CRD)と、Operatorを利用することが一般的です。
今回は、Oracleが開発中の MySQL Operator について検証しました。
MySQL(+MySQLフォーク)に関するKubernates Operatorについて
MySQL(or MySQL フォーク)のKubernates Operatorとしては、確認する限り以下のものが存在しています。
- mysql-operator
- MySQL/MySQL InnoDB Cluster用Operator
 
- percona-xtradb-cluster-operator
- Percona XtraDB Cluster 用Operator
 
- percona-server-operator
- Percona Server用Operator
 
- tidb-operator
- TiDB(MySQL互換分散データベース)用Operator
 
- vitess
- Vitess(MySQL互換分散データベース)用Operator
 
- moco
- MySQLレプリケーション構成用Operator
 
- bitpoke mysql operator
- MySQLレプリケーション構成用Operator
 ※ コメントは端的説明になりますのでご容赦ください。
 
- MySQLレプリケーション構成用Operator
なお、2022/03/04時点で mysql-operatorは Alpha バージョンになりますので、GAまでには今回触れた機能以外にも様々な機能が実装される可能性があります。
MySQL Operator
MySQL Operatorは、Kubernates上にMySQLを展開するものですが、ほぼ MySQL InnoDB Cluster 用となっています。
現時点では、MySQL ReplicaSetなどの非同期レプリケーション環境をサポートするものでは無いようです。
インストール方法は、直接マニフェストを適用する他にHelmによる方法が提供されています。
helmを利用する場合は、以下のようにリポジトリを登録し、 helm install を実行するだけです。
| 1 2 3 4 5 | helm repo add mysql-operator https://mysql.github.io/mysql-operator/ helm repo update helm install mysql-operator mysql-operator/mysql-operator \   --namespace mysql-operator \   --create-namespace --devel | 
helm install 時に設定可能なパラメータについては、 helm show values mysql-operator/mysql-innodbcluster --devel で確認できます。
※ 現在MySQL OperatorはAlpha版であるため --devel フラグを必要とします。
helmを使用すると、 helm upgrade 等によって管理が簡単になりますが、現在のところhelm経由でカスタマイズ可能なパラメータが少ないため、基本的にはマニフェストを使う方法をおすすめします。
ということで、まずリポジトリを git clone してしまいましょう。
| 1 | git clone https://github.com/mysql/mysql-operator.git | 
MySQL Operatorのデプロイ
オペレータのデプロイは、 deploy/deploy-crds.yaml 及び、 deploy/deploy-operator.yaml を使用します。
| 1 2 3 | $ kubectl config get-contexts CURRENT   NAME      CLUSTER   AUTHINFO   NAMESPACE *         default   default   default    blog | 
Operatorはデフォルトで mysql-operator というNamespaceにデプロイされます。
Namespaceがなければ作成されます。
| 1 2 | $ kubectl apply -f deploy/deploy-crds.yaml $ kubectl apply -f deploy/deploy-operator.yaml | 
しばらくするとOperatorが起動します。
OperatorのPodはDeploymentリソースによって管理されているため、Pod停止時はセルフヒーリングされます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ kubectl get deploy,pod -n mysql-operator NAME                             READY   UP-TO-DATE   AVAILABLE   AGE deployment.apps/mysql-operator   1/1     1            1           7m7s NAME                                  READY   STATUS    RESTARTS   AGE pod/mysql-operator-869d4b4b8d-7xdc6   1/1     Running   0          62s $ kubectl delete po -n mysql-operator mysql-operator-869d4b4b8d-7xdc6 pod "mysql-operator-869d4b4b8d-7xdc6" deleted $ kubectl get deploy,pod -n mysql-operator NAME                             READY   UP-TO-DATE   AVAILABLE   AGE deployment.apps/mysql-operator   1/1     1            1           7m22s NAME                                  READY   STATUS    RESTARTS   AGE pod/mysql-operator-869d4b4b8d-mk5k8   1/1     Running   0          3s | 
Podでは、mysqlshからmysqloperatorが起動している事がわかります。
| 1 2 3 4 5 | $ kubectl exec mysql-operator-869d4b4b8d-mk5k8 -n mysql-operator -it -- bash bash-4.4# microdnf install procps bash-4.4# ps -ef UID        PID  PPID  C STIME TTY          TIME CMD root         1     0  0 Mar08 ?        00:00:27 mysqlsh --log-level=@INFO --pym mysqloperator operator | 
これでMySQL InnoDB Clusterをデプロイする準備が整いました。
今回は検証用のNamespace(blog)を作成します。
| 1 2 3 4 5 | kubectl create ns blog kubectl config set-context $(kubectl config current-context) --namespace blog kubectl config get-contexts CURRENT   NAME      CLUSTER   AUTHINFO   NAMESPACE *         default   default   default    blog | 
早速実施したいところですが、mysql-operatorの自動バックアップ機能を利用したいため、事前にPVCを作成します。
※ k3s にバンドルされているlocal-path-provisionerを利用していますが、バックアップ用PVはReadWriteManyが可能なものを利用したほうが利便性はよいと考えられます。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ cat > backup-volume.yaml <<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata:   name: backup-pvc spec:   storageClassName: local-path   accessModes:     - ReadWriteOnce   resources:     requests:       storage: 15Gi EOF $ kubectl create -f backup-volume.yaml $ kubectl get pv,pvc # local-path provisionerは実際にマウントされる際にPVが作成されます NAME                               STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE persistentvolumeclaim/backup-pvc   Pending                                      local-path     16s | 
また、初期データロードのテスト用にsakila databaseを mysqlsh util.dumpSchemas() でダンプした結果を含むOCI Object Storage(blog-bucket)を作成しています。
OCIへのアクセス情報が必要になりますので、以下のSecretを作成しました。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | $ cat > oci-credentials.yaml <<EOF apiVersion: v1 kind: Secret metadata:   name: oci-credentials stringData:   user: "user ocid..."   fingerprint: "..."   tenancy: "tenancy ocid..."   region: "ap-tokyo-1"   passphrase: ""   privatekey: |     -----BEGIN RSA PRIVATE KEY-----     :     略     :     -----END RSA PRIVATE KEY----- EOF $ kubectl apply -f oci-credentials.yaml | 
MySQL InnoDB Clusterのデプロイ
MySQL InnoDB Clusterをデプロイすると以下のリソースが作成されます。
- StatefulSet: mysqld(Group replication)
- CronJob : mysqldバックアップ
- PersistentVolume/PersistentVolumeClaim : mysqld datadir
- Secret : mysql認証用
- ConfigMap : mysqld設定ファイル、livenessProbe, readinessProbe 用スクリプト
- Deployment : mysqlrouter
- Service : mysqld/mysqlrouter Podへの接続
マニフェストは、sample/sample-cluster.yaml、sample/sample-secret.yaml にあり、指定可能なパラメータはChapter 3 MySQL Operator Propertiesで説明されています。
今回は、サンプルを元に指定可能なパラメータを盛り込んだ以下のマニフェストを作成しました。
Secretに平文やbase64 encodeされた値を記載するのはセキュリティ上望ましくないため、本番で利用する場合はkubesecの利用をご検討ください。
設定の意味については、マニフェスト中に記載します。
| 1 2 3 4 5 6 7 8 9 | apiVersion: v1 kind: Secret metadata:   name: mycluster-sec stringData:   # MySQL接続ユーザ情報   rootUser: root   rootHost: '%'   rootPassword: 'sakila' | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | apiVersion: mysql.oracle.com/v2alpha1 kind: InnoDBCluster metadata:   name: mycluster spec:   secretName: mycluster-sec   # mysql pod数   instances: 3   # mysqlrouter定義   router:     # mysqlrouter pod数     instances: 3     # mysqlrouter pod のカスタム定義(Deployment.spec.templateと同じ構文)     # nameはrouterで固定     podSpec:       containers:       - name: router         # 6446 port(プライマリルーティング用)へのpingが通らなくなった場合MySQL Router Podを再作成         livenessProbe:           exec:             command:               - /usr/bin/mysqladmin               - ping               - -u$MYSQL_USER               - -p$MYSQL_PASSWORD               - -P6446               - -h127.0.0.1   podSpec:     # mysql podのカスタム定義(Statefulset.spec.templateと同じ構文)     # 以下ではテスト的にdatadirのマウントパスを変更しています     # 各containerのnameは固定     initContainers:       # mysqldの初期化処理用       - name: initmysql         volumeMounts:         - name: datadir           mountPath: /var/lib/mymysql       # mysqldの設定ファイル等デプロイ用       - name: initconf         volumeMounts:         - name: datadir           mountPath: /var/lib/mymysql     containers:       # mysqldが稼働するcontainer       - name: mysql         volumeMounts:         - name: datadir           mountPath: /var/lib/mymysql   # datadirにマウントするPVC定義(persistentVolumeClaimと同じ構文)   datadirVolumeClaimTemplate:     resources:       requests:         storage: 10Gi   # /etc/my.cnf.d/99-extra.cnfに書き込まれる設定内容   mycnf: |     [mysqld]     innodb_buffer_pool_size=1G     datadir=/var/lib/mymysql   # バックアップ定義   backupProfiles:     - name: dump       dumpInstance:         storage:           persistentVolumeClaim:             claimName: backup-pvc   # バックアップジョブ定義   backupSchedules:     - name: every-ten-minutes-dump-backup       schedule: "*/10 * * * *"       backupProfileName: dump       deleteBackupData: true       enabled: true   # mysqlsh util.loadDump()でロードする初期データ設定   initDB:     dump:       name: sakila       options:         includeSchemas:           - sakila       storage:         ociObjectStorage:           prefix: sakila-dump           bucketName: blog-bucket           credentials: oci-credentials | 
なお、initDB.dump.storage には persistentVolumeClaimを指定する事ができ、initDB.dump.storage.persistentVolumeClaim.path にロード対象データのパスを指定することも可能な実装になっています。
この際の util.loadDump() は sidecar コンテナが実行します。
しかし今回の検証では、persistentVolumeClaim.claimName に指定したディレクトリがマウントされず、volumes定義でマウントすると2つめのPodが起動しないという状態になりましたので、GA時にまた動作を確認したいと思います。
マニフェストの適用については特別な点はありません。
| 1 2 3 4 | $ kubectl apply -f secret.yaml secret/mycluster-secret created $ kubectl apply -f mic.yaml innodbcluster.mysql.oracle.com/mycluster created | 
リソース状態の確認
完全に起動すると以下の状態になります。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | $ kubectl get all -l tier=mysql NAME                                    READY   STATUS    RESTARTS        AGE pod/mycluster-0                         2/2     Running   0               5m22s pod/mycluster-1                         2/2     Running   0               4m37s pod/mycluster-2                         2/2     Running   0               3m47s pod/mycluster-router-5457bfc967-srnn6   1/1     Running   0               4m37s pod/mycluster-router-5457bfc967-4q7tk   1/1     Running   0               4m37s pod/mycluster-router-5457bfc967-6n7c6   1/1     Running   2 (3m52s ago)   4m37s NAME                          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)                               AGE service/mycluster-instances   ClusterIP   None          <none>        3306/TCP,33060/TCP,33061/TCP          5m22s service/mycluster             ClusterIP   10.43.5.250   <none>        6446/TCP,6448/TCP,6447/TCP,6449/TCP   5m22s NAME                                          DESIRED   CURRENT   READY   AGE replicaset.apps/mycluster-router-5457bfc967   3         3         3       5m23s NAME                         READY   AGE statefulset.apps/mycluster   3/3     5m23s NAME                                                       SCHEDULE       SUSPEND   ACTIVE   LAST SCHEDULE   AGE cronjob.batch/mycluster-every-ten-minutes-dump-backup-cb   */10 * * * *   True      0        <none>          4m31s | 
pod/mycluster-router-5457bfc967-6n7c6 が2回RESTARTしているのは、livenessProbeの開始が早すぎたためですので、もしMySQL Routerからの追加の死活監視等が必要なら開始タイミングの調整をしたほうが良いかもしれません。
Kubernates内部から 各 MySQLインスタンスへは、ServiceによるFQDNでアクセス可能です。
Service リソースが2つありますが、{cluster.name}-instance は各インスタンスへの直接接続用、{cluster.name} はMySQL Routerを介したロードバランス用になります。
例ではmyclusterですのでMySQL Routerへの接続では、mycluster.blog.svc.cluster.local のようなFQDNでアクセスします。
個別のインスタンスへの接続は、 mycluster-0.mycluster-instances.blog.svc.cluster.local のようになります。
接続確認してみましょう。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ kubectl run mysql --image=mysql/mysql-operator:8.0.28-2.0.3 --rm -ti  -- mysqlsh --sql MySQLSH> \connect root:sakila@mycluster.blog.svc.cluster.local:6447 MySQLSH> select @@hostname +-------------+ | @@hostname  | +-------------+ | mycluster-1 | +-------------+ MySQLSH> \connect root:sakila@mycluster-2.mycluster-instances.blog.svc.cluster.local:3306 MySQLSH>  select @@hostname; +-------------+ | @@hostname  | +-------------+ | mycluster-2 | +-------------+ | 
initDBでロードされたデータも入っています。
| 1 2 3 4 5 6 7 8 9 10 11 | MySQLSH> SHOW DATABASES; +-------------------------------+ | Database                      | +-------------------------------+ | information_schema            | | mysql                         | | mysql_innodb_cluster_metadata | | performance_schema            | | sakila                        | ★ | sys                           | +-------------------------------+ | 
バックアップも確認すると、問題なく取れているようです。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata:   name: busybox spec:   volumes:     - name: v       persistentVolumeClaim:         claimName: backup-pvc   containers:   - image: busybox     name: busybox     volumeMounts:     - mountPath: /backup       name: v     tty: true EOF $ kubectl exec -it busybox -- ls /backup mycluster-every-ten-minutes-dump-backup220311023205 mycluster-every-ten-minutes-dump-backup220311024004 $ kubectl exec -it busybox -- ls /backup/mycluster-every-ten-minutes-dump-backup220311024004 @.done.json @.json @.post.sql @.sql @.users.sql mysql_innodb_cluster_metadata.json mysql_innodb_cluster_metadata.sql  : | 
backupSchedules.[*].deleteBackupData という設定により、一見古いバックアップをローテーションしてくれそうに見えますが、コード上では実装されていないようでした。
また、バックアップ方法として dump 方式と snapshot 方式の設定が可能ですが、現時点ではsnapshot方式は未実装のようです。
まとめ
とりあえずインストールのみ試してみたという感じですが、GA前のプロダクトにしては問題なく動作している印象を受けました。
まだ実装されていない機能もありますので、GAした際にはさらに便利になっていることでしょう。
Kubernates上でのMySQL運用をお考えの方は今のうちに検証してみてはいかがでしょうか。


 
		 
			 
			 
			 
			 
			 
			 
			 
			