MariaDB Galera Clusterとは?
MariaDB Galera Clusterは同期式のマルチマスタ構成のMariaDBクラスタです。MariaDB10.1以降でデフォルトで利用できる機能です。
技術的にはGalera Cluster for MySQLを使っているようです。
Kubernetes上にGalera Clusterをデプロイする
Kubernetes上にGalera Clusterをデプロイして、その動作を確認してみます。
OpenShift向けに作られているadfinis-sygroup/openshift-mariadb-galeraがよくできているので、これを参考にすることにします。
Manifest
adfinis-sygroup/openshift-mariadb-galeraに書かれているマニフェストは少し古いので、Kubernetes 1.13系でも動くように変更しました。
またpodのaffinityを設定し、なるべく同じNodeにはPodが配置されないようにしています。
加えてPodDisruptionBudgetsを設定し、drain時に一気にPodが削除されないようにしています。
apiVersion: v1
kind: Namespace
metadata:
name: galera
---
apiVersion: v1
kind: Service
metadata:
name: galera
namespace: galera
labels:
app: galera
spec:
ports:
- port: 3306
name: galera
clusterIP: None
publishNotReadyAddresses: true
selector:
app: galera
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: galera
namespace: galera
spec:
serviceName: "galera"
replicas: 3
selector:
matchLabels:
app: galera
template:
metadata:
labels:
app: galera
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- galera
topologyKey: kubernetes.io/hostname
securityContext:
runAsUser: 27
fsGroup: 27
containers:
- name: galera
image: adfinissygroup/k8s-mariadb-galera-centos:v004
imagePullPolicy: Always
securityContext:
runAsNonRoot: true
ports:
- containerPort: 3306
name: mysql
- containerPort: 4444
name: sst
- containerPort: 4567
name: replication
- containerPort: 4568
name: ist
resources:
requests:
cpu: 500m
memory: 4Gi
readinessProbe:
exec:
command:
- /bin/sh
- -ec
- >-
mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} -h localhost -e "show status where Variable_name = 'wsrep_ready' and Value = 'ON'"
initialDelaySeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: datadir
mountPath: /var/lib/mysql
subPath: data
env:
- name: MYSQL_USER
value: user
- name: MYSQL_PASSWORD
value: password
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
volumes:
- name: datadir
emptyDir: {}
---
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: galera
namespace: galera
spec:
maxUnavailable: 1
selector:
matchLabels:
app: mysql
adfinissygroup/k8s-mariadb-galera-centos をみてみる
今回利用しているDocker Imageである、adfinissygroup/k8s-mariadb-galera-centosの動作についてもう少しみていきましょう。
Dockerfileはここにあります
その中を見るとENTRYPOINTとして/usr/bin/container-entrypoint.sh
を呼び出しています。
その実体はこちら
特に
/usr/bin/peer-finder -on-start="${CONTAINER_SCRIPTS_DIR}/configure-galera.sh" -service=${K8S_SVC_NAME}
が気になる処理です。
ここで利用しているpeer-finderは
これです。
これは、StatefulSetと組み合わせて、クラスタリングを行う時に便利なアプリケーションです。
peer-finder実行時に引数のon-startに指定したスクリプトにPeerの情報が送られます。
他にもon-changeなど指定することができますが、ここではon-startのみを使っているようです。
peer-finderから呼び出されるconfigure-galera.shをみてみます。
このスクリプトは、標準入力から渡ってくるPeerの情報を読み込み、Galeraのクラスタ設定情報(/etc/my.cnf.d/galera.cnf
)を更新しています。
Replicaが3のときの挙動を例に説明すると
- はじめのPod(galera-0)が起動するとき、Peerは
galera-0
のみ - 2つ目Pod(galera-1)が起動するとき、Peerは
galera-0,galera-1
- 3つ目Pod(galera-2)が起動するとき、Peerは
galera-0,galera-1,galera-2
ということで、それぞれのPodに設定されるPeerの情報が徐々に増えていくようになります。
Galeraは、その設定されたPeerの設定を元にクラスタを組むため、最終的に、3つのPodで構成されるクラスタが完成します。
デプロイしてみる
先ほどのマニフェストをデプロイします。
完全にデプロイできると次のようになります
$ kubectl get pods -n galera
NAME READY STATUS RESTARTS AGE
galera-0 1/1 Running 0 24m
galera-1 1/1 Running 0 22m
galera-2 1/1 Running 0 21m
データベースにアクセスするには Serviceで指定した名前のホストgalera
にアクセスします。
rootのパスワードは無指定となっています。
$ kubectl run client -n galera --image=mariadb:10.1 -it --rm --restart=Never -- mysql -u root -h galera -e "show databases;"
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
+--------------------+
pod "client" deleted
データを書き込む
galera構成のMariaDBは、MySQL互換です。ということで、普通にmysqlコマンドでデータベースとテーブルを作成し、適当なレコードを挿入しておきます。
$ kubectl run client -n galera --image=mariadb:10.1 -it --rm --restart=Never bash
If you don't see a command prompt, try pressing enter.
root@client:/# mysql -u root -h galera
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 275
Server version: 10.1.31-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> create database inajob;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> use inajob
Database changed
MariaDB [inajob]> create table hoge(id int auto_increment not null primary key, value varchar(20));
Query OK, 0 rows affected (0.00 sec)
MariaDB [inajob]> insert hoge(value) values ("hello");
Query OK, 1 row affected (0.00 sec)
MariaDB [inajob]> insert hoge(value) values ("world");
Query OK, 1 row affected (0.00 sec)
MariaDB [inajob]> select * from hoge;
+----+-------+
| id | value |
+----+-------+
| 5 | hello |
| 8 | world |
+----+-------+
2 rows in set (0.00 sec)
MariaDB [inajob]> exit
Bye
root@client:/# exit
exit
pod "client" deleted
Podを削除する
3個あるうちのPodを一つDeleteします。
$ kubectl get pods -n galera
NAME READY STATUS RESTARTS AGE
galera-0 1/1 Running 0 51m
galera-1 1/1 Running 0 50m
galera-2 1/1 Running 0 49m
$ kubectl delete pods -n galera galera-0
pod "galera-0" deleted
Kubernetesのセルフヒーリングにより、Podは再生成されます。
下のように-w
をつけてwatchしていると、Podがdeleteされたのちに、再生成されている様子がわかります。
再生成後はデータを持っているPodから自動的にデータをコピーしてくるため、DBに入っているデータ量に比例して復旧時間がかかるでしょう。
$ kubectl get pods -n galera -w
NAME READY STATUS RESTARTS AGE
galera-0 1/1 Running 0 52m
galera-1 1/1 Running 0 50m
galera-2 1/1 Running 0 49m
galera-0 1/1 Terminating 0 52m
galera-0 0/1 Terminating 0 52m
galera-0 0/1 Terminating 0 52m
galera-0 0/1 Terminating 0 52m
galera-0 0/1 Pending 0 1s
galera-0 0/1 Pending 0 1s
galera-0 0/1 ContainerCreating 0 1s
galera-0 0/1 Running 0 4s
galera-0 1/1 Running 0 46s
書き込んだデータが残っているか確認する
Podが一つ再生成されましたが、中のデータは大丈夫でしょうか?
$ kubectl run client -n galera --image=mariadb:10.1 -it --rm --restart=Never -- mysql -u root -h galera -e "select * from hoge;" inajob
+----+-------+
| id | value |
+----+-------+
| 5 | hello |
| 8 | world |
+----+-------+
pod "client" deleted
同じデータがかえってきました。
3つあるうちの1つのPodが再生成されてもデータが消えていないようです。Galera構成では3つのPodでデータをそれぞれ持っており、新しくクラスタに参加するPodに対しては、まずデータを同期することを行うからです。
Pod削除、復旧までの間DBにアクセスできるのか?
単にPodを削除し、再生成後、アクセスできることはわかりましたが、2つになっている状態ではアクセスできるのでしょうか?
試してみましょう。
$ kubectl run client -n galera --image=mariadb:10.1 -it --rm --restart=Never bash
If you don't see a command prompt, try pressing enter.
root@client:/# while :; do date;mysql -e "select count(*) from hoge;" -h galera -u root inajob; sleep 1; done
Wed Feb 13 05:04:26 UTC 2019
+----------+
| count(*) |
+----------+
| 2 |
+----------+
Wed Feb 13 05:04:27 UTC 2019
+----------+
| count(*) |
+----------+
| 2 |
+----------+
...
(以下繰り返し)
このように継続的にアクセスをしている状態で、Podを1つ削除してみます。
(略)
Wed Feb 13 05:05:40 UTC 2019
+----------+
| count(*) |
+----------+
| 2 |
+----------+
Wed Feb 13 05:05:41 UTC 2019 # <---- PodをDeleteした瞬間たまに接続が失敗することがある
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 104 "Connection reset by peer"
Wed Feb 13 05:05:44 UTC 2019
+----------+
| count(*) |
+----------+
| 2 |
+----------+
Wed Feb 13 05:05:45 UTC 2019
+----------+
| count(*) |
+----------+
| 2 |
+----------+
Wed Feb 13 05:05:46 UTC 2019
+----------+
| count(*) |
+----------+
| 2 |
+----------+
Wed Feb 13 05:05:57 UTC 2019 # <--- ここだけすごく時間がかかっている
+----------+
| count(*) |
+----------+
| 2 |
+----------+
Wed Feb 13 05:05:58 UTC 2019 # <---- このタイミングでは、Podは2つしか存在しないが問題なくアクセスができている
+----------+
| count(*) |
+----------+
| 2 |
+----------+
(略)
ログを抜粋しましたが、このように、PodをDeleteした瞬間にコネクションが切れることが確認できました。これはアプリケーション側にリトライの処理を書いておくことで対応できそうです。
また、PodがDeleteしてから10秒間ほどアクセスできない時間が発生しています。これは、Podが1ついなくなることで、Glaeraクラスタがクラスタからメンバーを1つ削除すべき と判断するためにかかる時間のようで、各種パラメータの調整で改善させることもできると思われます。(http://galeracluster.com/documentation-webpages/recovery.html#detecting-single-node-failures)
1つPodがDeleteされ、合計2つのPodになった際も、DBにはアクセスできています。
また、Podが再生成され、データの同期をしている間は、データベースがサービスを開始しないため、readinessProbeで指定したコマンドが失敗します。そうするとKubernetesのServiceによるロードバランス対象には含まれないので、準備ができていないPodにアクセスが行くようなことはないです。
まとめ
このようにGalera構成を使うことで、1つのPodが落ちてもデータが消えないRDBMSを実現することができます。
この仕組みをうまく使うことで、(数秒間の停止は伴いますが、)Podのローリングアップデートを実現したり、マシンの障害時でもデータを失わないようなデータベースを実現することができます。
Kubernetesでデータを保持するアプリケーションの運用は難しいイメージがありますが、データの同期周りのややこしい部分をGalera構成に任せることで、ある程度は簡単になるのではないかと考えています。
実際的なデータを扱う際には、Podを消して新しく作るたびに、既存のPodからデータを同期することが時間的にもネットワーク帯域的にもコストになるため、PersistentVolumeなどのデータ永続化手法と組み合わせて運用することになります。
また、MySQL互換ではあるもののいくつかの制限もあるので、利用の際は十分に検討が必要です。