サイボウズの Kubernetes 基盤を開発している Neco プロジェクトの ymmt です。 サイボウズ製品のほとんどはデータベースとして MySQL を採用しています。 現在 400 を越える MySQL のインスタンスを運用しており、これら全てを新しい Kubernetes 基盤に移行していく予定です。
Kubernetes 上でアプリケーションやミドルウェアの運用を自動化するソフトウェアのことをオペレーターと言います。 大量の MySQL インスタンスを Kubernetes 基盤に移行するにはオペレーターが必須であると考え、技術顧問の @yoku0825 さんの監修の下で MOCO というソフトウェアを開発しオープンソースライセンスで公開しました。
本記事では Kubernetes 上の MySQL オペレーターの状況と、開発した MOCO の機能を詳細に解説いたします。
- MySQL オペレーターの状況
- MOCO について
- 機能紹介
- MySQL 8 に対応 (5.7 以前はサポートしない)
- 接続の自動切換えと負荷分散
- 自動フェイルオーバー
- 手動および自動のスイッチオーバー
- Errant transaction を持つインスタンスを自動検出しクラスタから隔離
- 既存の MySQL から非同期レプリケーションするクラスタを作成可能
- 単体構成の MySQL との高い互換性
- バックアップとリストア
- バージョンアップ
- メトリクス
- my.cnf のカスタマイズ
- innodb_buffer_pool_size の自動設定
- Pod 定義のカスタマイズ
- Service 定義のカスタマイズ
- Slow query log
- PodDisruptionBudget を自動設定
- まとめと今後の予定
MySQL オペレーターの状況
私たちは以下の MySQL オペレーターソフトウェアについて採用できるか調査しました。
- mysql/mysql-operator: Oracle 公式のオペレーター
- presslabs/mysql-operator: Presslabs 製のオペレーター
- percona/percona-xtradb-cluster-operator: Percona 製オペレーター
こちらに示す状況は 2021 年 6 月時点の情報です。
開発状況
Oracle のオペレーターは 2019 年から 2 年間開発が停滞している状況でしたが、つい先日新規に作り直すアナウンスがありました。まだプレビュー段階のようです。
Presslabs のオペレーターは活発に開発されているようですが、MySQL 8 対応版がまだリリースされておらず、またプロダクション利用はしないように明記されています。
Percona のオペレーターは活発に開発され、プロダクション利用可能なようです。
対応製品の違い
Oracle のオペレーターは MySQL (InnoDB Cluster) 専用となっています。
Presslabs と Percona のオペレーターは MySQL 互換製品である Percona Server for MySQL 専用となっています。
レプリケーション方式の違い
MySQL および互換製品の Percona Server for MySQL は複数のレプリケーション方式をサポートしています。
- Asynchronous Replication
- Semi-synchronous Replication
- Group Replication (InnoDB Cluster)
- Galera Cluster / XtraDB Cluster
詳しい解説はしませんが、レプリケーション方式により様々な制限事項や障害耐性の違いが生じます。
Asynchronous Replication ではデータベースサーバーの故障時に、過去に成功したトランザクションが消失する可能性があります。
Semi-synchronous Replication では適切に設定すると一台のサーバーが故障してもトランザクションが消失しないようにすることができます。 一方で、故障サーバーが復帰すると errant transaction という不整合データが発生する可能性があります。
Group Replication は Paxos をベースにした分散合意方式により errant transaction の発生を防止します。
Galera Cluster およびそれを元にした XtraDB Cluster は詳細に調査していません。 早々にサイボウズ製品が要求する仕様を満たせないことが判明したためです。
Oracle のオペレーターは Group Replication を採用しています。
Presslabs のオペレーターは現在 Asynchronous Replication のみのようです。Semi-synchronous にも対応を進めている様子は伺えます。
Percona のオペレーターは Galera Cluster を元にした XtraDB Cluster を採用しています。
なぜ MOCO を開発したのか
サイボウズの製品は長年レプリケーションをしない単独の MySQL サーバーを前提として開発してきました。 そのため制限事項が多いレプリケーション方式には移行することが困難な状況です。
具体的には以下の条件を満たす方式が製品開発サイドより求められました。
- MySQL 8 に対応していること
- フェイルオーバー時にトランザクションを失わないこと
- 2 GiB を越える大きさのトランザクションを実行できること
innodb_autoinc_lock_mode
に 1 を設定できること- SERIALIZABLE トランザクション分離レベルをサポートしていること
結論から言いますと、これらを満たすオペレーターは存在しませんでした。
Oracle のオペレーターが採用する InnoDB Cluster は制限事項が多く、なかでも 2 GiB 以上のトランザクションを扱えない点が問題になりました。
Presslabs のオペレーターは MySQL 8 と semi-synchronous レプリケーションに未対応です。
Percona のオペレーターは 2 GiB を越えるトランザクションが扱えず、innodb_autoinc_lock_mode
を 1 にできず、また SERIALIZABLE の対応は experimental でした。
上記要件を満たすものは、自社で開発を行う以外に方法がありませんでした。
MOCO について
MOCO は 1, 3, もしくは 5 台のインスタンスからなるクラスタを幾つでも作成・管理することができます。 クラスタの中の 1 台だけは書き込みが可能で、プライマリと呼びます。 他の読み込みしかできないインスタンスはレプリカと呼びます。
インスタンス間では GTID を用いた loss-less semi-synchronous replication という方式でデータをリアルタイムで複製しています。
この方式は適切に設定することで、書き込んでいるインスタンスが落ちても他のレプリカにこれまでに成功したトランザクションが全てあることを保証できます。一方で落ちたインスタンスが復帰してくると、他のレプリカに存在しないトランザクション(errant transaction)を持ってしまう可能性があります。
特徴
MOCO は多彩かつ高度な機能を提供しています。ここではまず全機能を箇条書きで説明します。
- MySQL 8 に対応
- 1 台のみに書き込みが可能なプライマリ + レプリカ方式を採用
- CLONEプラグインを利用した高速なレプリカの(再)作成
- プライマリ用、レプリカ用の Service (Kubernetes のロードバランサ)を提供
- データの反映が遅れているレプリカを Service から自動的に排除
- Loss-less semi-synchronization でプライマリ障害時にデータ損失が発生しない
- Errant transaction を持つインスタンスを自動検出しクラスタから隔離
- 既存の MySQL から非同期レプリケーションするクラスタを作成可能
- SERIALIZABLE トランザクション分離レベルをサポート
- 2 GiB 以上の大きさのトランザクションも扱える
innodb_autoinc_lock_mode
を 1 に設定できる- プライマリの故障を自動検出し、他のレプリカにプライマリを切り替えるフェイルオーバー機能
- プライマリを手動で切り替えるスイッチオーバー機能
- プライマリの Pod が削除される際に自動でスイッチオーバーする機能
- MySQL shell を利用した高速なバックアップ
- 定期的な自動バックアップおよび手動の臨時バックアップが可能
- 任意の時点のデータをリストアできる Point-in-Time Recovery (PiTR)
- クラスタ毎に異なる MySQL のバージョンを利用可能
- 自動的で安全な MySQL のバージョンアップが可能
- クラスタ内のインスタンスを作成後に増設可能
mysqld_exporter
が組み込まれており、多彩なメトリクスを取得可能my.cnf
の設定をクラスタ毎に変更可能- Pod 定義をカスタマイズ可能
- Service 定義をカスタマイズ可能
- Slow query log をサイドカーコンテナの標準出力に転送
- PodDisruptionBudget を自動設定
あと、1,000 以上のクラスタを管理できるようなスケーラブルな内部アーキテクチャになっています。
使い方
MOCO のインストールは簡単です。ユーザーマニュアルに書いてあるのが正式な手順ですが、端的には cert-manager を入れて MOCO のマニフェストを apply するだけです。
$ curl -fsL https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml | kubectl apply -f - $ curl -fsL https://github.com/cybozu-go/moco/releases/latest/download/moco.yaml | kubectl apply -f -
MySQL クラスタを作るには以下のように MySQLCluster リソースを作成します。 各フィールドの詳しい説明はリンク先のドキュメントを参照してください。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: # クラスタ内の mysqld インスタンスの数。1, 3, 5 から選択できます。後から増やすこともできます。 replicas: 3 podTemplate: spec: containers: # mysqld という名前のコンテナを定義してください。必要に応じて他にコンテナや Pod の設定を入れることもできます。 - name: mysqld # MOCO 用に補助ツールが入ったコンテナイメージを使います # 利用できるタグは https://quay.io/repository/cybozu/mysql?tag=latest&tab=tags を見てください。 # 自分でビルドする方法は https://cybozu-go.github.io/moco/custom-mysqld.html を見てください。 image: quay.io/cybozu/mysql:8.0.25 # 上記イメージは UID:GID 10000 で実行されます。 # もし下記 volumeClaimTemplates が作るファイルシステムが root 以外書けない場合、以下の設定が必要です。 securityContext: fsGroup: 10000 volumeClaimTemplates: # mysql-data という名前の volume claim template を定義してください。 - metadata: name: mysql-data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 100Gi
クラスタが構築される様子は kubectl get mysqlcluster
で確認できます。
以下のように HEALTHY が True になれば準備完了です。
$ kubectl get -w mysqlcluster test NAME AVAILABLE HEALTHY PRIMARY SYNCED REPLICAS ERRANT REPLICAS LAST BACKUP test 0 <no value> test False False 0 <no value> test False False 0 <no value> test False False 0 1 <no value> test True False 0 2 <no value> test True True 0 3 <no value>
できた MySQL にアクセスするには kubectl-moco
という kubectl
のプラグインを使います。
プラグインは https://github.com/cybozu-go/moco/releases/ から Windows 用、Linux 用、Mac 用がダウンロードできます。
以下のようにダウンロードして PATH の通っているディレクトリに kubectl-moco
(Windows は kubectl-moco.exe
) という名前で配置してください。
$ mkdir -p $HOME/go/bin $ PATH=$HOME/go/bin:$PATH $ curl -fsL -o $HOME/go/bin/kubectl-moco https://github.com/cybozu-go/moco/releases/latest/download/kubectl-moco-linux-amd64 $ chmod a+x $HOME/go/bin/kubectl-moco
kubectl-moco
には mysql
, credential
, switchover
というサブコマンドがあります。
詳しい仕様はマニュアルを参照してください。
以下は MySQL データベースに書き込み可能ユーザーでプライマリインスタンスにアクセスしてデータベース・テーブルを作成し、データを入れる例です。
mysql
コマンドを対話的に実行できます。autocommit=0
に初期設定されているので COMMIT
を明示的にしています。
$ kubectl moco -n default mysql -it -u moco-writable test mysql> CREATE DATABASE foo; Query OK, 1 row affected (0.00 sec) mysql> USE foo; Database changed mysql> CREATE TABLE t (i INT PRIMARY KEY AUTO_INCREMENT, data TEXT NOT NULL); Query OK, 0 rows affected (0.03 sec) mysql> INSERT INTO t (data) VALUES ('aaa'), ('bbb'); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> COMMIT; Query OK, 0 rows affected (0.00 sec) mysql> quit Bye
以下は読み込み専用ユーザーでレプリカ 1 番にアクセスして先ほどのテーブルの内容を確認する例です。
$ kubectl moco -n default mysql --index 1 test -- -D foo -t -e 'SELECT * FROM t' +---+------+ | i | data | +---+------+ | 1 | aaa | | 2 | bbb | +---+------+
しっかりレプリケーションされていますね。
MOCO は初期設定で moco-readonly
, moco-writable
, moco-admin
というユーザーを作っています。
アプリケーションから利用する場合、これらのユーザーではなく別途ユーザーを作成したほうが良いでしょう。
$ kubectl moco -n default mysql -it -u moco-writable test mysql> CREATE USER 'foo'@'%' IDENTIFIED BY 'xxx'; Query OK, 0 rows affected (0.00 sec) mysql> GRANT ALL ON foo.* TO 'foo'@'%'; Query OK, 0 rows affected (0.00 sec)
Kubernetes クラスタ内の他の Pod から MySQL を利用するには Service を使います。 以下のように、MOCO は MySQLCluster 一つにつき 3 種類の Service を作成します。
$ kubectl get svc -lapp.kubernetes.io/created-by=moco NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE moco-test ClusterIP None <none> 3306/TCP,33060/TCP 40m moco-test-primary ClusterIP 10.96.102.109 <none> 3306/TCP,33060/TCP 40m moco-test-replica ClusterIP 10.96.59.78 <none> 3306/TCP,33060/TCP 40m
プライマリへの書き込みには *-primary
の Service を使います。もちろん読み込みも可能です。
ポート番号 3306 はいわゆる旧来の MySQL プロトコル、33060 は X プロトコル用です。
レプリカからの読み込みには *-replica
の Service を使います。
こちらは更新が遅延していないレプリカインスタンスに負荷分散されるようになっています。
内部構成
以下の図は MySQLCluster にたいして MOCO のコントローラー(moco-controller
) が行う処理を示しています。
クラスタを作る中心となるのは StatefulSet です。
spec.replicas
で指定した数の Pod が StatefulSet から作成されます。
Pod には mysqld
コンテナ以外に以下のサイドカーコンテナが付随します。
moco-agent
: CLONE 操作やレプリケーション遅延のチェックをするfluent-bit
: Slow query log を吸い出して標準出力に転送するmysqld_exporter
:mysqld
の各種メトリクスを Prometheus 形式で出力する
moco-controller
と moco-agent
は gRPC で通信します。
この gRPC 通信の保護には mTLS (mutual TLS authentication) が用いられており、そのために moco-controller
は cert-manager を利用して証明書を発行し、MySQLCluster の namespace に転記しています。
また、moco-admin
等のユーザーのパスワードは moco-controller
がランダムに生成して Secret に保存されています。
機能紹介
特徴の項で示した機能について解説していきます。
MySQL 8 に対応 (5.7 以前はサポートしない)
サイボウズは全ての MySQL インスタンスを 8 に更新済みであるため、MOCO は MySQL 8 を前提に設計しました。 MySQL 5.7 以前への対応を切ることで、MySQL 8 から導入された以下のメリットを享受できます。
Atomic DDL
MySQL 8 からは
CREATE TABLE
等の DDL が InnoDB に保存されるようになり、不意の障害で中途半端な状態になることがなくなりました。mysqld
アップグレード処理の簡素化正確には MySQL 8.0.16 からですが、従前
mysql_upgrade
というコマンドを実行しないといけなかった処理が不要になりました。-
読んで字のごとくですが、バックアップ中に DDL をブロックしつつ DML は許可する新しいタイプのロックです。 プライマリからバックアップを取得しないといけないような場合にアプリケーションへの影響を最小に留めることができます。
接続の自動切換えと負荷分散
プライマリ・レプリカ方式を採用しているため、書き込めるインスタンスはクラスタ中 1 台だけとなります。 もしプライマリが変わるたびに利用しているアプリケーションが接続先を変更しなければいけないとなると、大変面倒です。
この問題を解決するため MySQL Router という専用のソフトウェアがあるのですが、Kubernetes には宛先 Pod を動的に切り替えることができる Service という仕組みがあります。 Service を使うほうが構成コンポーネントを減らして簡略にできるため、MOCO は Service 方式を採用しています。
moco-<クラスタ名>-primary
: 現在のプライマリインスタンスに繋がる Servicemoco-<クラスタ名>-replica
: データの反映が遅れてないレプリカインスタンスに負荷分散する Service
データの遅延をどの程度許容するかは MySQLCluster の spec.maxDelaySeconds
で調整可能です。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: maxDelaySeconds: 180 ...
自動フェイルオーバー
フェイルオーバーとは、プライマリインスタンスに障害が発生したときにレプリカの一台を新たなプライマリとして設定変更することです。
moco-controller はプライマリの以下の障害を検出し、自動的にフェイルオーバー操作を実行します。
- 接続不能
- データ消失
特徴としては、接続不能時に Node を fencing しなくてもフェイルオーバーを実行できることです。
一般的に、観測者のネットワークが分断されている状況であるサーバーに接続不能となっていてもアプリケーションからは当該サーバーが継続的に利用可能である可能性があります。
この状況で不用意に他のサーバーをプライマリに昇格してしまうと、アプリケーションからは二つ書き込み可能なサーバーが出現してしまい、それぞれに異なるデータを書き込んでしまう恐れ(スプリットブレイン)が発生します。 スプリットブレインを防止するために良く使われる技法が fencing で、当該サーバーの電源を別の手段で落とすといったことが行われます。
MOCO は MySQL の semi-synchronous replication の仕様をうまく利用することで fencing しなくても安全にプライマリを切り替えています。 詳細は省きますが、詳しく知りたい方は設計文書や実装を調べてみてください。
手動および自動のスイッチオーバー
スイッチオーバーとは、正常に稼働しているプライマリインスタンスを速やかにレプリカの一台と役割を入れ替えることです。
MOCO は kubectl moco switchover
という kubectl のプラグインコマンドを提供しているので、スイッチオーバーを手動で行うことが可能です。
以下の例では PRIMARY が 0 から 1 に変わっています。
$ kubectl get mysqlclusters test NAME AVAILABLE HEALTHY PRIMARY SYNCED REPLICAS ERRANT REPLICAS LAST BACKUP test True True 0 3 2021-05-31T19:37:58Z $ kubectl moco switchover test $ kubectl get mysqlclusters test NAME AVAILABLE HEALTHY PRIMARY SYNCED REPLICAS ERRANT REPLICAS LAST BACKUP test True True 1 3 2021-05-31T19:37:58Z
さらに、MOCO はプライマリインスタンスの Pod の metadata.deletionTimestamp
がセットされると自動でスイッチオーバーを開始します。
どのような状況で発生するかというと、例えば MySQLCluster の Pod テンプレートをちょっと修正するといった場面です。
この場合全ての mysqld
Pod が順番に削除されて作り直されるので、自動でスイッチオーバー処理を行うことでダウンタイムを極小化するわけです。
実際のダウンタイムがどの程度になるかは、ぜひ検証いただければ嬉しいです!
Errant transaction を持つインスタンスを自動検出しクラスタから隔離
Errant transaction は簡単に言えばレプリカに存在するがプライマリインスタンスには存在しないトランザクションのことです。 本来読み込み専用であるレプリカにこのようなトランザクションが入り込むはずはないのですが、不具合や人為的な操作で紛れ込む可能性はあります。
それ以外に、MySQL の semi-synchronous replication ではプライマリインスタンスが障害を起こし、フェイルオーバーしたあとにレプリカとして復帰すると、新しいプライマリが持っていないトランザクションをクラッシュリカバリー操作の結果持つ可能性があります。
このため MySQL のマニュアルでは以下のように障害を起こしたインスタンスは破棄するように注意しています。
MOCO は安全性を考慮してインスタンスは破棄しません。別のいいかたをするとデータの削除やその後の初期化はしません。 その代わりにプライマリインスタンスにないトランザクションを持つインスタンスを自動的に検出し、クラスタから排除します。 インスタンスの破棄はユーザーが手動で行う必要があります。
既存の MySQL から非同期レプリケーションするクラスタを作成可能
すでに稼働している MySQL からデータをレプリケーションするクラスタを作成できます。 レプリケーション元の MySQL は MOCO で構築したものでも、そうでないものでも大丈夫です。
この機能を利用すると、例えば地理的に離れたデータセンター間で災害対策のためにレプリケーションするといったことが可能です。
ただし Clone Plugin を使って開始時にデータを複製する関係で、レプリケーション元と MOCO のクラスタの MySQL は原則として同じバージョンである必要があります。 また、GTID を有効にしておく必要があります。
詳しい手順はユーザーマニュアルを参照してください。
単体構成の MySQL との高い互換性
Group Replication / InnoDB Cluster や Galera Cluster では満たせない以下の要件を充足できます。
- SERIALIZABLE トランザクション分離レベルをサポート
- 2 GiB 以上の大きさのトランザクションを扱える
innodb_autoinc_lock_mode
を 1 に設定できる
バックアップとリストア
MOCO のクラスタは定期的にバックアップするように設定が可能です。
以下のように BackupPolicy というリソースを作成します。 バックアップデータは Amazon S3 互換のオブジェクトストレージに保存するため、権限を持つ ServiceAccount を指定したり環境変数で必要なクレデンシャルを渡せるようになっています。
apiVersion: moco.cybozu.com/v1beta1 kind: BackupPolicy metadata: namespace: default name: daily spec: # CRON 形式でスケジュールが指定可能です schedule: "@daily" jobConfig: # S3 にアクセスするための権限を持つ ServiceAccount を指定できます # お使いのオブジェクトストレージがそういう形では認証できない場合、"default" で良いです serviceAccountName: default # 例えば MinIO なら環境変数でクレデンシャルを渡せます env: - name: AWS_ACCESS_KEY_ID value: minioadmin - name: AWS_SECRET_ACCESS_KEY value: minioadmin # バックアップを保存する bucket の情報です。bucketName 以外はオプションです。 bucketConfig: bucketName: moco region: us-east-1 endpointURL: http://minio.default.svc:9000 usePathStyle: true # 作業ディレクトリ用の volume を指定してください。データが大きいと見込まれる場合 # emptyDir より generic ephemeral volume 等が適切です workVolume: emptyDir: {}
作成した BackupPolicy を MySQLCluster から以下のように参照します。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: # 同じ Namespace に存在する BackupPolicy の名前 backupPolicyName: daily ...
そうすると、以下のような CronJob が自動的に作成されます。
$ kubectl get cronjobs NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE moco-backup-test @daily False 0 <none> 11s
臨時にバックアップを取るには kubectl create job
を使います。
空のインスタンスだとバックアップがエラーになるので、何かデータを入れてから実施してください。
$ kubectl create job --from=cronjob/moco-backup-test backup-now job.batch/backup-now created $ kubectl get jobs backup-now NAME COMPLETIONS DURATION AGE backup-now 1/1 2s 5s
成功したバックアップの情報は MySQLCluster の status.backup
に記録されます。
Prometheus のメトリクスとしても出力されます。
$ kubectl get mysqlclusters test NAME AVAILABLE HEALTHY PRIMARY SYNCED REPLICAS ERRANT REPLICAS LAST BACKUP test True True 0 3 2021-05-31T19:20:24Z $ kubectl get mysqlclusters test -o json | jq .status.backup { "binlogFilename": "binlog.000001", "binlogSize": 0, "dumpSize": 20480, "elapsed": "144.786401ms", "gtidSet": "d7f0a656-c243-11eb-8f30-a2a44bcdb3f8:1-3", "sourceIndex": 1, "sourceUUID": "d6b23081-c243-11eb-8609-aecb58bb22b1", "time": "2021-05-31T19:20:24Z", "warnings": null, "workDirUsage": 7886 }
バックアップは MySQL Shell の Instance Dump Utility を使用しています。
この方式は LOCK INSTANCE FOR BACKUP
を利用しているためバックアップ中でも DML が実行できます。
また mysqldump
, mysqlpump
と比較して非常に高速です。
cf. MySQL Shell Dump & Load part 2: Benchmarks
初回バックアップではフルダンプを取るだけで binary log はまだ保存しません。 二回目以降のバックアップで、Point-in-Time Recovery のために binary log を差分保存します。
$ kubectl create job --from=cronjob/moco-backup-test backup-now2 job.batch/backup-now2 created $ kubectl get jobs backup-now2 NAME COMPLETIONS DURATION AGE backup-now2 1/1 2s 2s $ kubectl get mysqlclusters test -o json | jq .status.backup { "binlogFilename": "binlog.000001", "binlogSize": 662, "dumpSize": 20480, "elapsed": "197.541044ms", "gtidSet": "d7f0a656-c243-11eb-8f30-a2a44bcdb3f8:1-5", "sourceIndex": 1, "sourceUUID": "d6b23081-c243-11eb-8609-aecb58bb22b1", "time": "2021-05-31T19:37:58Z", "warnings": null, "workDirUsage": 7879 }
リストアするには以下のように spec.restore
を指定した MySQLCluster を作成します。
restorePoint
にはリストアしたいデータの日時を RFC3339 形式で指定します。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: restore spec: restore: # リストアしたい MySQLCluster の namespace と name を指定してください # MySQLCluster リソース自体は失われていても問題ありません sourceNamespace: default sourceName: test # リストアしたいデータの日時を RFC3339 形式で指定してください restorePoint: "2021-05-31T19:37:20Z" # BackupPolicy の jobConfig と同様 jobConfig: serviceAccountName: default env: - name: AWS_ACCESS_KEY_ID value: minioadmin - name: AWS_SECRET_ACCESS_KEY value: minioadmin bucketConfig: bucketName: moco region: us-east-1 endpointURL: http://minio.default.svc:9000 usePathStyle: true workVolume: emptyDir: {} ...
作成すると、moco-restore-<クラスタ名>
という Job が自動的に作成されてリストアが開始されます。
この Job はリストア完了後はいつ削除しても大丈夫です。
$ kubectl get jobs moco-restore-restore NAME COMPLETIONS DURATION AGE moco-restore-restore 1/1 14s 25s $ kubectl get mysqlclusters restore NAME AVAILABLE HEALTHY PRIMARY SYNCED REPLICAS ERRANT REPLICAS LAST BACKUP restore True True 0 1 <no value> $ kubectl moco mysql restore -- -D foo -t -e 'SELECT * FROM t' +---+------+ | i | data | +---+------+ | 1 | aaa | | 2 | bbb | +---+------+
リストア処理の詳細は Job が作った Pod のログで確認できます。 Job を削除する前にログの内容を確認しておきましょう。
$ kubectl logs moco-restore-restore-vj2ql {"level":"info","ts":1622490569.9764667,"msg":"waiting for a pod to become ready","name":"moco-restore-0"} {"level":"info","ts":1622490571.98407,"msg":"waiting for the mysqld to become ready","name":"moco-restore-0"} {"level":"info","ts":1622490581.0054455,"msg":"restoring from a backup","dump":"moco/default/test/20210531-192024/dump.tar","binlog":"moco/default/test/20210531-192024/binlog.tar.zst"} Loading DDL, Data and Users from '/work/dump' using 4 threads. Opening dump... Target is MySQL 8.0.25. Dump was produced from MySQL 8.0.25 Checking for pre-existing objects... Executing common preamble SQL Executing DDL script for schema `foo` [Worker000] Executing DDL script for `foo`.`t` Analyzing table `foo`.`t` [Worker001] foo@t@@0.tsv.zst: Records: 1 Deleted: 0 Skipped: 0 Warnings: 0 Executing user accounts SQL... Executing common postamble SQL Resetting GTID_PURGED to dumped gtid set 1 chunks (1 rows, 6 bytes) for 1 tables in 1 schemas were loaded in 1 sec (avg throughput 6.00 B/s) 0 warnings were reported during the load. {"level":"info","ts":1622490581.1993299,"msg":"loaded dump successfully"} {"level":"info","ts":1622490581.231039,"msg":"applied binlog successfully"} {"level":"info","ts":1622490581.2483056,"msg":"restoration finished successfully"}
バージョンアップ
MySQL のバージョンを上げるには、MySQLCluster の mysqld
コンテナのイメージを差し替えるだけです。
必要な処理は全て MOCO が自動的に行います。念のため、バージョンアップ前に臨時バックアップを取るのがお勧めです。
なお、ダウングレーディングは MySQL がサポートしていないため MOCO もサポートしていません。
メトリクス
MOCO は各 MySQLCluster および mysqld
のインスタンスについて多種のメトリクスを Prometheus 形式で出力しています。
InnoDB の統計情報などは mysqld_exporter
で取得できる機能が備わっています。
以下のように spec.collectors
を MySQLCluter に指定すると mysqld_exporter
がサイドカーコンテナとして追加され、InnoDB や performance_schema の統計情報を出力可能です。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: # mysqld_exporter の collector flag (collect. は省略) を指定してください # 詳しくは https://github.com/prometheus/mysqld_exporter/blob/master/README.md#collector-flags collectors: - engine_innodb_status - info_schema.innodb_metrics ...
出力されるメトリクスの詳細や scraping ルールについてはユーザーマニュアルをご覧ください。
my.cnf
のカスタマイズ
my.cnf
の設定は ConfigMap を作成することで行えます。
以下のように data
の key-value 形式で指定してください。
apiVersion: v1 kind: ConfigMap metadata: namespace: default name: mycnf data: long_query_time: "0" innodb_log_file_size: "10M"
作成した ConfigMap 名を以下のように MySQLCluster で指定すれば設定が変更できます。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: # 同一 Namespace 内の ConfigMap 名 mysqlConfigMapName: mycnf ...
performance-schema-instrument
等一部の設定は複数回指定しないといけないため、ConfigMap の key-value 形式では指定できません。
このような場合 _include
というキーに my.cnf
にそのまま転記される内容を書くことができます。
apiVersion: v1 kind: ConfigMap metadata: namespace: default name: mycnf data: _include: | performance-schema-instrument='memory/%=ON' performance-schema-instrument='wait/synch/%/innodb/%=ON' performance-schema-instrument='wait/lock/table/sql/handler=OFF' performance-schema-instrument='wait/lock/metadata/sql/mdl=OFF'
innodb_buffer_pool_size
の自動設定
innodb_buffer_pool_size
は MySQL の性能に極めて重要な影響を持つパラメーターです。
ConfigMap で指定した my.cnf
に innodb_buffer_pool_size
が指定されておらず、mysqld
コンテナの resources.requests.memory
(ない場合は resources.limits.memory
) が指定されている場合、MOCO はこのパラメータを自動設定します。
例えば以下の MySQLCluster では 100 GiB のメモリをリクエストしているので、innodb_buffer_pool_size
は 70Gi
に設定されます。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: podTemplate: spec: containers: - name: mysqld image: quay.io/cybozu/mysql:8.0.25 resources: requests: memory: 100Gi
Pod 定義のカスタマイズ
これまで見てきたように、MySQL Pod の定義は自在にカスタマイズできます。
お勧めは podAntiAffinity
で Pod が複数のホストに分散するように設定することです。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: podTemplate: spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app.kubernetes.io/name operator: In values: - mysql - key: app.kubernetes.io/instance operator: In values: - test # metadata.name の値を指定 topologyKey: "kubernetes.io/hostname" ...
app.kubernetes.io/name
と app.kubernetes.io/instance
は MOCO が自動的につけるラベルです。
Service 定義のカスタマイズ
プライマリとレプリカ用に作成される Service もカスタマイズできます。
例えば type: LoadBalancer
にすれば、Kubernetes クラスタの外部からアクセス可能な MySQL になります。
apiVersion: moco.cybozu.com/v1beta1 kind: MySQLCluster metadata: namespace: default name: test spec: serviceTemplate: spec: type: LoadBalancer ...
Slow query log
MySQL は実行に長時間かかったクエリを記録する slow query log という機能があります。 運用上不可欠に近いものです。
Slow query log は mysqld
の標準出力ないし標準エラー出力には出せないため、MOCO slow log の内容を吸い出して標準出力に流すサイドカーコンテナを自動で追加しています。
また、slow query log のファイルは一定時間毎に自動でローテートされています。
$ kubectl logs moco-test-0 -c slow-log /usr/local/mysql/bin/mysqld, Version: 8.0.25 (Source distribution). started with: Tcp port: 3306 Unix socket: /run/mysqld.sock Time Id Command Argument
Loki 等でコンテナのログを収集している環境であれば、slow query log の内容も自動で収集することが可能です。
PodDisruptionBudget を自動設定
PodDisruptionBudget とは Kubernetes の Node 退役処理などで Pod を drain する際、アプリケーションが壊れないように制限を課す機能です。
MOCO はインスタンス数が 3 の MySQLCluster であれば 1 つまで、インスタンス数が 5 の MySQLCluster であれば 2 つまで Pod が drain されて良いような PodDisruptionBudget を自動で作成します。
まとめと今後の予定
サイボウズでは今後 cybozu.com の全ての MySQL インスタンスを自社 Kubernetes 基盤である Neco に移行していきます。 MOCO はその作業の要となるソフトウェアで、近い将来数百のクラスタを稼働することになる見通しです。
MOCO は現時点で最も機能が充実している MySQL オペレーターの一つです。ぜひお試しください。