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運用をお考えの方は今のうちに検証してみてはいかがでしょうか。