LoginSignup
1
0

More than 3 years have passed since last update.

OpenShift4+MySQL8.0のActive-Standby構成

Last updated at Posted at 2021-01-24

昨日構成したCentOS 8上でのMySQL 8.0 Active-Standby構成をOpenShift 4の上でやってみる。
まあ、昨日の記事の焼き直しだが新規性はこちらの方があるだろう。

改めて、今回説明する構成の良いところは使用しているサーバー側コンポーネントがすべてOpenShift 4がサポートするカバー範囲内となる点にある。
→ OpenShiftのサポート範囲に「RHEL Software Collections and RHT SSO Common Service」が含まれている。
https://docs.openshift.com/container-platform/4.6/welcome/oke_about.html
→ RHEL Software CollectionsにMySQL 8.0が含まれている。
https://developers.redhat.com/products/softwarecollections/overview

なお、今回使用するMySQLの細かいバージョンは、昨日の記事と同じ8.0.21である。

また、前回記事と同じくフェールオーバーの切り替えの自動化には触れていない。それでも手順は十分機械的なので、少し頑張るくらいでフェールオーバー自動化は可能だろう(「フェールオーバーしてみる」の手順2~5が自動化できれば良いのだ)。

構成

OpenShiftクラスターの作成については以下の記事を参照。最近Master3台構成に作り替えたのだが、まあ似たようなものだ。
https://qiita.com/rk05231977/items/ec1626c1223dddffc98a

今回の構成ではMySQLサーバーの開閉塞はNodePortで行うのだが、bastionサーバー経由でないとクラスターにアクセスできないため、やはりHAProxyは使用する。

ストレージは、簡単だったのでbastionサーバー上のNFSストレージを使用している。使えるならmysql1、2のPodが稼働するノードのローカルストレージか、iSCSI等のブロックストレージを使った方が良いだろう。

シェアードナッシングなのでmysql1とmysql2が実行される物理ノードは遠隔に配置できる。マルチAZ単一クラスター構成なら本記事に書いてあるままで、DRでもちょっと頑張れば動作するだろう。

image.png

nfsストレージとインターナルレジストリを準備する

1.以下の記事のうち、「NFSサーバーを作る」、「Image Registryを設定する」を参照し、それらを構成する。
https://qiita.com/rk05231977/items/59027862b59207a5e61f

2.mysql用のnfsストレージを作る。

# mkdir -p /export/mysql1 /export/mysql2
# chmod 777 /export/mysql1 /export/mysql2
# echo "/export 192.168.1.0/24(rw,no_root_squash)" >> /etc/exports
# exportfs -r

MysqlのActive-Standbyクラスターを作る

1.bastionサーバーにログインし、mysqlクラスターのyamlファイルを作る。
Infrastructure as Codeの世界へようこそ。

mysql.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-mysql
provisioner: kubernetes.io/no-provisioner
reclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql1
  labels:
    instance-for: mysql1
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  persistentVolumeReclaimPolicy: Delete
  storageClassName: nfs-mysql
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /export/mysql1
    server: 192.168.1.1
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql2
  labels:
    instance-for: mysql2
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  persistentVolumeReclaimPolicy: Delete
  storageClassName: nfs-mysql
  mountOptions:
    - hard
    - nfsvers=4.1
  nfs:
    path: /export/mysql2
    server: 192.168.1.1
---
apiVersion: v1
kind: Namespace
metadata:
  name: mysql
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql1
  namespace: mysql
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 5Gi
  storageClassName: nfs-mysql
  selector:
    matchLabels:
      instance-for: mysql1
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql2
  namespace: mysql
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 5Gi
  storageClassName: nfs-mysql
  selector:
    matchLabels:
      instance-for: mysql2
---
apiVersion: v1
kind: Service
metadata:
  name: mysql1
  namespace: mysql
  labels:
    app: mysql1
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None
  selector:
    app: mysql1
---
apiVersion: v1
kind: Service
metadata:
  name: mysql2
  namespace: mysql
  labels:
    app: mysql2
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None
  selector:
    app: mysql2
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql1
  namespace: mysql
spec:
  selector:
    matchLabels:
      app: mysql1
  serviceName: mysql1
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql1
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql1
        image: image-registry.openshift-image-registry.svc:5000/mysql/mysql-80
        imagePullPolicy: Always
        ports:
        - containerPort: 3306
          name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: mysql1
      nodeSelector:
        kubernetes.io/hostname: node1
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql2
  namespace: mysql
spec:
  selector:
    matchLabels:
      app: mysql2
  serviceName: mysql2
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql2
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql2
        image: image-registry.openshift-image-registry.svc:5000/mysql/mysql-80
        imagePullPolicy: Always
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql/data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: mysql2
      nodeSelector:
        kubernetes.io/hostname: node2

2.yamlファイルをOpenShiftクラスターにデプロイする。

# oc create -f mysql.yaml

とりあえず、mysqlネームスペースにmysql1とmysql2という2つのStatefulSetが出来上がる。

インターナルレジストリにmysql-80のイメージをpushしていないので、上記をデプロイした直後にPodは起動しない。この後すぐの手順でイメージは配置する。

外部からのアクセスにNodePortが必要だが、それはまだ作っていない。

3.mysql-80のコンテナイメージをインターナルレジストリに配置する。
以下、説明は雑だが、registry.redhat.ioからイメージをpullしてインターナルレジストリに配置する。
https://catalog.redhat.com/software/containers/rhel8/mysql-80/5ba0ad4cdd19c70b45cbf48c

# podman login registry.redhat.io
# podman pull registry.redhat.io/rhel8/mysql-80

# oc login -u admin -p admin --server=https://api.ocp.example.com:6443
# oc port-forward svc/image-registry -n openshift-image-registry 5000 &

# podman login -u admin -p $(oc whoami -t) localhost:5000 --tls-verify=false
# podman tag registry.redhat.io/rhel8/mysql-80 localhost:5000/mysql/mysql-80
# podman push localhost:5000/mysql/mysql-80 --tls-verify=false

4.mysqlのPodを削除し、作り直す。

# oc delete pod -n mysql mysql1-0 mysql2-0

5.mysqlが起動したのを確認する。
Podのログの最後に「ready for connections. Version: '8.0.21'」と表示されることを確認する。

# oc logs -f -n mysql mysql1-0
# oc logs -f -n mysql mysql2-0

(実行結果)
[root@bastion mysql]# oc logs -f -n mysql mysql1-0
...
2021-01-24T12:01:42.398021Z 0 [System] [MY-010931] [Server] /usr/libexec/mysqld: ready for connections. Version: '8.0.21'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  Source distribution.

mysql1を構成する

1.何もデータが無いのもつまらないので、国データベースを初期データとしてロードしようか。

# curl -k -O -L https://downloads.mysql.com/docs/world.sql.gz
# gunzip world.sql.gz
# oc exec -i -n mysql mysql1-0 -- mysql -u root < world.sql

2.以降、oc exec経由でmysql1のインスタンスを構成するが、長たらしいコマンドを避けるため短縮コマンドを定義する。

# mysql1="oc exec -n mysql mysql1-0 -- mysql -u root"

3.MySQL Workbenchから繋ぐ用のadminユーザーを作る。

# $mysql1 -e "CREATE USER 'admin'@'%' IDENTIFIED BY 'password';"
# $mysql1 -e "GRANT ALL ON *.* TO 'admin'@'%';"

4.ここからがレプリケーション用の構成。
https://dev.mysql.com/doc/refman/8.0/en/binlog-replication-configuration-overview.html

MySQLにserver-idを設定し、レプリケーション用のユーザーを作る。
server_idは、前回記事を引き継いで201を設定しようか。
レプリケーション元のIPアドレスを特定することが難しいので、replのホスト名は%である。
あと、仮想マシンで試したときは要らなかったと思うがコンテナだと「WITH mysql_native_password」が無いとレプリケーションがエラーを吐く。何でだ。

# $mysql1 -e "SET PERSIST server_id = 201;"

# $mysql1 -e "CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'password';"
# $mysql1 -e "GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';"

5.レプリケーション用の初期データを用意し、レプリケーションの開始点を確認する。

# $mysql1 -e "FLUSH TABLES WITH READ LOCK;"
# $mysql1 -e "SHOW MASTER STATUS\G;" > master-status.txt
# oc exec -n mysql mysql1-0 -- mysqldump -u root --all-databases --master-data > dbdump.db
# $mysql1 -e "UNLOCK TABLES;"
# cat master-status.txt

(実行結果 - SHOW MASTER STATUS)
[root@bastion mysql]# cat master-status.txt
*************************** 1. row ***************************
             File: binlog.000002
         Position: 736399
     Binlog_Do_DB:
 Binlog_Ignore_DB:
Executed_Gtid_Set:

「binlog.~」と「Position」の値がStandby側でレプリケーションを開始するのに必要となる。

mysql2サーバー構成する

1.mysql2を構成するための短縮コマンドを定義する。

# mysql2="oc exec -n mysql mysql2-0 -- mysql -u root"

2.MySQL Workbenchから繋ぐ用のadminユーザーを作る。

# $mysql2 -e "CREATE USER 'admin'@'%' IDENTIFIED BY 'password';"
# $mysql2 -e "GRANT ALL ON *.* TO 'admin'@'%';"

3.mysql2にレプリケーション用の設定を行う。
server_idを202に設定する。
レプリケーション用のユーザーを作成する。

# $mysql2 -e "SET PERSIST server_id = 202;"

# $mysql2 -e "CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'password';"
# $mysql2 -e "GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';"

3.初期データとして、mysql1のバックアップをロードする。

# oc exec -i -n mysql mysql2-0 -- mysql -u root < dbdump.db

4.レプリケーションを開始する。

# MASTER_LOG_FILE=$(grep -oP '(?<=File: ).*' master-status.txt)
# MASTER_LOG_POS=$(grep -oP '(?<=Position: ).*' master-status.txt)

# $mysql2 -e "CHANGE MASTER TO MASTER_HOST='mysql1', \
MASTER_USER='repl', MASTER_PASSWORD='password', \
MASTER_LOG_FILE='${MASTER_LOG_FILE}', MASTER_LOG_POS=${MASTER_LOG_POS};"
# $mysql2 -e "START SLAVE;"
# $mysql2 -e "SHOW SLAVE STATUS\G;"

「Slave_IO_State: Waiting for master to send event」、「Last_IO_Error:」、「Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates」となっていることを確認する。

HAproxyを設定し、NodePortを作る

1.OpenShiftクラスター作成時に、bastionサーバー上にHAproxyを導入したと思うので、それを流用する。
haproxy.cfgのファイル末尾に以下を追加する。
node1、node2、node3は、OpenShiftクラスターを構成するノードと、そのIPアドレスである。

/etc/haproxy/haproxy.cfg

frontend mysql
    bind *:3306
    mode tcp
    default_backend mysql
backend mysql
    balance source
    mode tcp
    server node1 192.168.1.3:30306 check
    server node2 192.168.1.4:30306 check
    server node3 192.168.1.5:30306 check

2.haproxyをreloadし、firewalldを設定する。

# systemctl reload haproxy
# firewall-cmd --add-service=mysql --zone=external --permanent
# firewall-cmd --reload

3.mysql1に接続するNodePortを作る。

nodeport-mysql1.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql
spec:
  type: NodePort
  ports:
    - name: mysql
      protocol: TCP
      port: 3306
      targetPort: 3306
      nodePort: 30306
  selector:
    app: mysql1
# oc create -f nodeport-mysql1.yaml

MySQL WorkbenchからMySQLに繋ぐ

1.手元のPCにMySQL Workbenchをダウンロードしてインストールする。
https://www.mysql.com/jp/products/workbench/

2.HAproxyのIPアドレスに向けたConnectionを作って繋ぐ。
image.png

3.レコードを追加してみよう。
Schemas > world > Tables > countryのテーブルを開き、以下のSQL文を実行(⚡)する。

INSERT INTO `world`.`country` (`Code`, `Name`, `Continent`, `Region`, `SurfaceArea`, `Population`, `LocalName`, `GovernmentForm`, `Code2`) VALUES ('ASR', 'Asura Kingdom', 'Europe', 'Central Region', '100000.00', '1000000', 'Asura Kingdom', 'Monarchy', 'AA');

image.png

レコードが追加されただろうか。
image.png

フェールオーバーしてみる

1.mysql1が稼働しているnode1を電源オフしてもいいのだが、さすがに何なのでStatefulSetのレプリカ数を0にする。

# oc scale sts -n mysql mysql1 --replicas=0

2.MySQL Workbenchで表がロードできなくなった事を確認する。
image.png
image.png

3.念のため、mysqlへのアクセスを閉塞する。
NodePortを消せばいい。

# oc delete svc -n mysql mysql

4.mysql2でレプリケーションを停止し、Activeサーバーになっても良い様に準備する。

# $mysql2 -e "SHOW SLAVE STATUS\G;"
 → 「Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates」となっているのを確認する。
# $mysql2 -e "STOP SLAVE;"
# $mysql2 -e "RESET SLAVE ALL;"

# $mysql2 -e "FLUSH TABLES WITH READ LOCK;"
# $mysql2 -e "SHOW MASTER STATUS\G;" > master-status.txt
# $mysql2 -e "UNLOCK TABLES;"
# cat master-status.txt

この時点でmysql2は新しいActiveサーバーとして機能しているので、HAproxyの振り分けをmaster2に変更する。

5.mysql2に向けたNodePortを作成する。

nodeport-mysql2.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql
spec:
  type: NodePort
  ports:
    - name: mysql
      protocol: TCP
      port: 3306
      targetPort: 3306
      nodePort: 30306
  selector:
    app: mysql2
# oc create -f nodeport-mysql2.yaml

6.MySQL Workbenchで再度、表をロードする。
繋がるようになっているはずである。ついでに、先にmysql1で追加したレコードがmysql2にもデータ反映されている事を確認しよう。
image.png

7.mysql2がActiveサーバーになっている段階で、もう一つレコードを追加してみる。
MySQL Workbenchで以下の文を実行(⚡)する。

INSERT INTO `world`.`country` (`Code`, `Name`, `Continent`, `Region`, `SurfaceArea`, `Population`, `LocalName`, `GovernmentForm`, `Code2`) VALUES ('MLS', 'Holy Country of Millis', 'South America', 'Southern Millis', '100000', '1000000', 'Millis', 'Church State', 'MI');

image.png
image.png

元のActiveサーバーに切り戻す

mysql1サーバーに切り戻すところもやってみよう。

1.mysql1のレプリカ数を1に戻す。

# oc scale sts -n mysql mysql1 --replicas=1
# oc logs -f -n mysql mysql1-0
 → 「ready for connections.~」が表示されるのを待つ。

2.まずはmysql1を、mysql2のレプリカ(Standby)として構成する。シャットダウン中のデータ更新に追いつかせるためである。

# MASTER_LOG_FILE=$(grep -oP '(?<=File: ).*' master-status.txt)
# MASTER_LOG_POS=$(grep -oP '(?<=Position: ).*' master-status.txt)

# $mysql1 -e "CHANGE MASTER TO MASTER_HOST='mysql2', \
MASTER_USER='repl', MASTER_PASSWORD='password', \
MASTER_LOG_FILE='${MASTER_LOG_FILE}', MASTER_LOG_POS=${MASTER_LOG_POS};"
# $mysql1 -e "START SLAVE;"

3.mysql1サーバーで以下のコマンドを実行し、レプリケーションが追いつくのを待つ。

# $mysql1 -e "SHOW SLAVE STATUS\G;"

「Slave_IO_State: Waiting for master to send event」、「Last_IO_Error:」、「Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates」となっていることを確認する。

レプリケーションが追いついたらmysql1をActiveサーバーとして復帰するための準備完了である。

4.mysql2へのアクセスを閉塞する。

# oc delete svc -n mysql mysql

5.mysql1サーバーのレプリケーションを止め、Activeに復帰させる準備をする。

# $mysql1 -e "SHOW SLAVE STATUS\G;"
 → 「Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates」となっているのを確認する。
# $mysql1 -e "STOP SLAVE;"
# $mysql1 -e "RESET SLAVE ALL;"

# $mysql1 -e "FLUSH TABLES WITH READ LOCK;"
# $mysql1 -e "SHOW MASTER STATUS\G;" > master-status.txt
# $mysql1 -e "UNLOCK TABLES;"

6.master1に振るNodePortを作成し、MySQLへのアクセスを再開する。

# oc create -f nodeport-mysql1.yaml

7.MySQL Workbenchで再度、表をリロードする。表がリロードされ、master2側に追加したレコードがmaster1にも反映されていることを確認する。
image.png

8.最後に、mysql2サーバーが改めてStandbyとなるように再構成する。

# MASTER_LOG_FILE=$(grep -oP '(?<=File: ).*' master-status.txt)
# MASTER_LOG_POS=$(grep -oP '(?<=Position: ).*' master-status.txt)

# $mysql2 -e "CHANGE MASTER TO MASTER_HOST='mysql1', \
MASTER_USER='repl', MASTER_PASSWORD='password', \
MASTER_LOG_FILE='${MASTER_LOG_FILE}', MASTER_LOG_POS=${MASTER_LOG_POS};"
# $mysql2 -e "START SLAVE;"
# $mysql2 -e "SHOW SLAVE STATUS\G;"

「Slave_IO_State: Waiting for master to send event」、「Last_IO_Error:」、「Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates」となっていることを確認する。

(おまけ1)何回か往復したい時の追加データ

INSERT INTO `world`.`country` (`Code`, `Name`, `Continent`, `Region`, `SurfaceArea`, `Population`, `LocalName`, `GovernmentForm`, `Code2`) VALUES ('RAN', 'Ranoa Kingdom', 'Europe', 'Northern Central', '10000', '100000', 'Ranoa', 'Monarchy', 'RA');
INSERT INTO `world`.`country` (`Code`, `Name`, `Continent`, `Region`, `SurfaceArea`, `Population`, `LocalName`, `GovernmentForm`, `Code2`) VALUES ('KDK', 'King Dragon Kingdom', 'Europe', 'Southern Central', '50000', '500000', 'King Dragon Kingdom', 'Monarchy', 'KD');

(おまけ2)今回構成の消費メモリ

2回往復後の消費メモリ。
mysql1、mysql2の2台合わせて、大体1GBといったところか。
image.png

(おまけ3)きれいさっぱり消し去る

1.mysqlネームスペースを消す。

# oc delete namespace mysql

2.PV、StorageClassを消す。

# oc delete pv mysql1 mysql2
# oc delete sc nfs-mysql

3.イメージプルーニングを行い、使われなくなったmysql-80イメージを消す。

# oc adm prune images

4.nfsのストレージも消す。

# sed -i '/\/export.*/d' /etc/exports
# exportfs -r
# rm -rf /export

5.何なら、HAproxyの定義も消す。

/etc/haproxy/haproxy.cfg
(ファイル最後の以下を削除)

frontend mysql
    bind *:3306
    mode tcp
    default_backend mysql
backend mysql
    balance source
    mode tcp
    server node1 192.168.1.3:30306 check
    server node2 192.168.1.4:30306 check
    server node3 192.168.1.5:30306 check
# systemctl reload haproxy
# firewall-cmd --remove-service=mysql --zone=external --permanent
# firewall-cmd --reload

これで本記事の手順を1からやり直すことが出来る。
やってみよう。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0