MySQL & MHA による RDB 自動フェイルオーバークラスタリングシステム

## 1. 要約
この記事では、MySQL & MHA による、RDB 自動フェイルオーバークラスタリングシステムの設計と構築の一例を投稿します。

## 2. はじめに
特に事業のコアとなるような RDB システムにおいては、SLA は非常に重要なファクターといえると思います。
しかし、RDB のマスターノードに障害が発生する可能性はゼロではありませんし、私の経験上からも、予期しない原因（OSS のバグなど）により発生することもあります。
もし障害が発生した場合には当然、復旧完了までの期間に機会損失が発生し、ビジネスにとって大きな影響を及ぼしかねません。
この記事では、そのような影響や機会損失を最小限に食い留めるソリューションの一つとして、MySQL & MHA による RDB 自動フェイルオーバークラスタリングシステムの設計と構築の一例を投稿してみます。
このクラスタリングシステムは、1 台のマスター、2 台のスレーブ、1 台の監視ノード（例えば、リバースプロキシーなど）の、最低でも計 4 台のサーバー構成を必要とします。
そしてその仕組みは、マスター障害を監視ノードから自動検知させ、スレーブ 1 号機をマスター昇格、 スレーブ 2 号機をスレーブ 1 号機へと切り替え、障害マスターを切り離した上でセミレプリケーションを再構成し、その際に発生しうるデータの破損などの自動修復も試みるという流れとなります。
そのフェイルオーバーに要する時間は、もちろんリトライ監視などの閾値にもよりますが、切り替え動作だけなら数秒単位で完了します。
これにより、マスター障害時の機会損失とビジネスの影響範囲を最小限に抑える事が可能となりえます。
某プロジェクトにおいて、マスター、スレーブ、ボリュームサーバー、リバースプロキシ、監視サーバー、バックアップサーバーと、RDB システムの運用に関わるサーバーだけで数百台というシステムにも導入して順調に稼働しています。

## 3. 環境
* RHEL 7 系
* MySQL 5.6
* MHA 0.57
* Keepalived 1.2.13
* IPVS 1.27
* firewalld 0.3.9
* GlusterFS 4.1.7

## 4. 設計
![MHA.png](https://qiita-image-store.s3.amazonaws.com/0/260161/3e95ae5c-62f8-807d-4660-5082701c2091.png)

* ロードバランサー x 2 台<br>
（監視サーバー x 2 台）
* RDB マスターサーバー x 1 台
* RDB スレーブサーバー x 2 台
* ボリュームサーバー x 4 台

## 5. ボリュームサーバー構築
ボリュームサーバーの構築例につきましては、分散用・複製用あわせて 4 台という構成となります。
構成は適宜ご調整いただき、<a href='https://qiita.com/KyojiOsada/items/e84f58c22aed1c7df484' target='_blank'>GlusterFS による Web サーバーの同期</a> のポストの、GlusterFS サーバーのセクションをご参照ください。

## 6. MySQL サーバー構築
MySQL サーバーの構築例につきましては、マスター x 1 台、スレーブ x 2 台の準同期構成となります。
構築方法は一般的な例をご参考ください。

## 7. ロードバランサー構築
ロードバランサー（リバースプロキシー）の構築例につきましては、ロードバランサー x 2 台という構成となります。
こちらは、<a href='https://qiita.com/KyojiOsada/items/376095d58b4e61b3e0ec' target='_blank'>LVS & firewalld による DSR 方式ロードバランサー</a> のポストの、Web サーバー部分を、RDB サーバーに置き換えてご参照ください。

## 8. MHA ノード構築
MHA では、監視サーバーをマネージャー、各 MySQL サーバーをノードと呼びます。
オフィシャルドキュメントはこちら。
https://github.com/yoshinorim/mha4mysql-manager/wiki

### 8-1. epel リポジトリインストール
@ MySQL サーバー 1, 2, 3
epel リポジトリが入ってない場合にはインストールしておきます。
`$ sudo cd /etc/yum.repos.d`
`$ sudo yum -y install epel-release`

enabled を 1 から 0 に変えておきます。
`$ sudo vim epel.repo`

```text:/etc/yum.repos.d/epel.repo
enabled = 0
```

### 8-2. MHA ノードパッケージダウンロード
@ MySQL サーバー 1, 2, 3
`$ sudo mkdir /usr/local/src/mha`
`$ sudo cd /usr/local/src/mha`

`$ sudo wget https://72003f4c60f5cc941cd1c7d448fc3c99e0aebaa8.googledrive.com/host/0B1lu97m8-haWeHdGWXp0YVVUSlk/mha4mysql-node-0.57-0.el7.noarch.rpm`


上記、デッドリンクになっていましので下記で再分布しました。（GPL-2.0）
↓
`$ sudo wget https://github.com/KyojiOsada/mha4mysql/raw/master/mha4mysql-node-0.57-0.el7.noarch.rpm`

### 8-3. MHA ノードパッケージインストール
@ MySQL サーバー 1, 2, 3
`$ sudo yum --enablerepo=epel -y localinstall mha4mysql-node-0.57-0.el7.noarch.rpm`

### 8-4. MHA ノードワーキングディレクトリ作成
@ MySQL サーバー 1, 2, 3
MHA 用ワーキングディレクトリを作成しておきます。
`$ sudo mkdir /var/mha`

### 8-5. SSH 設定
@ MySQL サーバー 1
各 MySQL サーバー間、および各監視サーバーから各 MySQL サーバーへ管理者権限 SSH 接続するための環境構築をします。
`$ sudo mkdir /root/.ssh`
`$ sudo cd /root/.ssh`
`$ sudo ssh-keygen -t rsa`
`$ sudo chmod 0600 id_rsa`
`$ sudo cp -a id.rsa.pub authorized_keys`

@ MySQL サーバー 2, 3
各 MySQL サーバーの /root/.ssh にも、MySQL サーバー 1 で生成した id_rsa と authorized_keys と同じものを設置します。

## 9. MHA マネージャー構築
MHA では、監視サーバーをマネージャー、各 MySQL サーバーをノードと呼びます。

### 9-1. epel リポジトリ
@ 監視サーバー1, 2
epel リポジトリが入ってない場合にはインストールしておきます。
`$ cd /etc/yum.repos.d`
`$ yum -y install epel-release`

enabled を 1 から 0 に変えておきます。
`$ vim epel.repo`

```text:/etc/yum.repos.d/epel.repo
enabled = 0
```

### 9-2. MHA ダウンロード
@ 監視サーバー1, 2
`$ sudo mkdir /usr/local/src/mha`
`$ sudo cd /usr/local/src/mha`

監視サーバーにマネージャーパッケージをダウンロードします。

`$ sudo wget https://72003f4c60f5cc941cd1c7d448fc3c99e0aebaa8.googledrive.com/host/0B1lu97m8-haWeHdGWXp0YVVUSlk/mha4mysql-manager-0.57-0.el7.noarch.rpm`


上記デッドリンクになっていましたので下記で再分布しました。（GPL-2.0）
↓
`$ sudo wget https://github.com/KyojiOsada/mha4mysql/raw/master/mha4mysql-manager-0.57-0.el7.noarch.rpm`

監視サーバーにノードパッケージもダウンロードします。

`$ sudo wget https://72003f4c60f5cc941cd1c7d448fc3c99e0aebaa8.googledrive.com/host/0B1lu97m8-haWeHdGWXp0YVVUSlk/mha4mysql-node-0.57-0.el7.noarch.rpm`


上記デッドリンクになっていましたので下記で再分布しました。（GPL-2.0）
↓
`$ sudo wget https://github.com/KyojiOsada/mha4mysql/raw/master/mha4mysql-node-0.57-0.el7.noarch.rpm`

### 9-3. MHA インストール
@ 監視サーバー1, 2
監視サーバーに、マネージャーパッケージとノードパッケージの両方をインストールします。
`$ sudo yum --enablerepo=epel -y localinstall mha4mysql-manager-0.57-0.el7.noarch.rpm mha4mysql-node-0.57-0.el7.noarch.rpm`

### 9-4. MHA ワーキングディレクトリ作成
@ 監視サーバー1, 2
MHA 用ワーキングディレクトリを作成しておきます。
`$ sudo mkdir /var/mha`

### 9-5. SSH 設定
@ 監視サーバー1, 2
8-5. で生成した id_rsa と authorized_keys を設置します。

### 9-6. MHA マネージャー設定
@ 監視サーバー1, 2
`$ sudo cp -a /etc/masterha_default.cnf /etc/masterha_default.cnf.org`
`$ sudo vim /etc/masterha_default.cnf`

```cnf:/etc/masterha_default.cnf
[server default]
# MySQL User Password
## 予め MySQL にMHA 用アカウントを作成しておき、ここで指定しておきます。
user = ユーザー名
password = パスワード

# SSH
## SSH 用ユーザーを指定します。ここでは root を指定しています。
ssh_user = root

# MySQL Replication User
# MySQL レプリケーション時に作成したユーザーを指定します。
repl_user = レプリケーション用 MySQL ユーザー
repl_password = レプリケーション用 MysQLパスワード

# Manager Working Directory
## 監視サーバーに、予めマネージャー用のワーキングディレクトリを作成し、ここで指定します。
manager_workdir = /var/mha
manager_log = /var/mha/manager.log

# Node Working Directory
## 各 RDB サーバーに、予めノード用のワーキングディレクトリを作成し、ここで指定します。
remote_workdir = /var/mha

# MySQL Working Directory
## RDB サーバーのワーキングディレクトリを指定します。
## もし RDB サーバーにボリュームサーバーを別立てする場合には、そのマウントポイントを指定します。
master_binlog_dir = /var/mysql,/var/mysql,/var/mysql
## もし RDB サーバーにボリュームサーバーを別立てする場合には、PID ファイルは RDB 起動後ロックし続けるため、ボリュームサーバーに置いてはなりません。
master_pid_file = /var/mysql/mysql.pid,/var/mysql/mysql.pid,/var/mysql/mysql.pid

# Faliover Script
## 適宜使用するスクリプトを指定します。
master_ip_failover_script = /srv/mha/master_ip_failover.sh
#master_ip_online_change_script = /data/mha/master_ip_failover.sh

# Report Script
## 適宜使用するスクリプトを指定します。
#report_script = /data/mha/report.sh

# MySQL Configurration
[server1]
hostname = 10.0.0.1

[server2]
hostname = 10.0.0.2
candidate_master = 1

[server3]
hostname = 10.0.0.3
no_master = 1
```

### 9-7. SSH 接続テスト
@ 監視サーバー1, 2
`$ sudo masterha_check_ssh --conf=/etc/masterha_default.cnf`

### 9-8. MySQL 接続 & レプリケーションチェック
@ 監視サーバー1, 2
`$ sudo masterha_check_repl --conf=/etc/masterha_default.cnf`

### 9-9. MHA マネージャー起動/停止テスト
@ 監視サーバー1, 2
フォアグラウンド起動
`$ sudo masterha_manager --conf=/etc/masterha_default.cnf`
`$ sudo ps aux | grep masterha_manager`
`$ sudo tail -f /var/mha/manager.log`

以下が出力されていれば成功

```
FriJan 24 23:35:09 20xx - [info] Ping(CONNECT) succeeded, waiting until MySQL doesn't respond..
```

フォアグラウンド停止
[Ctrl + c]

バックグラウンド起動
`$ sudo nohup masterha_manager --conf=/etc/masterha_default.cnf < /dev/null > /var/mha/manager.log 2>&1 &`
`$ sudo ps aux | grep masterha_manager`
`$ sudo tail -f /var/mha/manager.log`

```
FriJan 24 23:35:09 20xx - [info] Ping(CONNECT) succeeded, waiting until MySQL doesn't respond..
```

バックグラウンド停止<br>
`$ sudo masterha_stop --conf=/etc/masterha_default.cnf`

### 9-10. マネージャステータス確認
`$ sudo masterha_check_status --conf=/etc/masterha_default.cnf`

## 10. MySQL 稼働テスト
* マスターへ更新系クエリーを発行し、マスターおよびスレーブにレコードが登録されるかご確認ください。
* マスターへ参照系クエリーを発行し、データが参照できるかご確認ください。
* スレーブへ参照系クエリーを発行し、データが参照できるかご確認ください。
* スレーブへ更新系クエリーを発行し、クエリーがブロックされるかご確認ください。

## 11. 自動フェイルオーバーテスト
### 11-1. Stop MySQL service
マスターの MySQL サービスを落とします。

@ MySQL サーバー 1
`$ sudo systemctl stop mysql`

@ 監視サーバー 1 ログ
`$ sudo tail -f -n 100 /var/mha/master_manager.log`

### 11-2. Kill MySQL process
マスターの MySQL プロセスを全て落とします。

@ MySQL サーバー 1
`$ sudo killall -9 mysqld mysqld_safe`

@ 監視サーバー 1 ログ
`$ sudo tail -f -n 100 /var/mha/master_manager.log`

### 11-3. Drop MySQL port
マスターの MySQL の 3306 ポートを遮断します。

@ MySQL サーバー 1
`$ sudo firewall-cmd --remove-port=3306/tcp`

@ 監視サーバー 1 ログ
`$ sudo tail -f -n 100 /var/mha/master_manager.log`

### 11-4. Change MySQL permission to unwritable
マスターの MySQL ワーキングディレクトリの書き込み権限を剥奪します。

@ MySQL サーバー 1
`$ sudo cd /var/mysql`
`$ sudo chown -R root:mysal ./`
`$ sudo find ./ -type d | xargs chmod 2750`
`$ sudo find ./ -type f | xargs chmod 0640`

@ 監視サーバー 1 ログ
`$ sudo tail -f -n 100 /var/mha/master_manager.log`

## 12. まとめ
この記事では、ミニマム構成の MySQL & MHA による RDB 自動フェイルオーバークラスタリングシステムの設計と構築の一例を投稿してみましました。
上記のテストのように、マスター障害が発生すると自動フェイルオーバーされる事がわかります。
ただし、上記のテスト以外にもマスター障害の原因はまだまだありますが、あまり極端な閾値を設定しますと、テーブルロック時の待機が原因となる障害誤検知により、予期しないフェイルオーバーが発生するかもしれません。
RDB システムは、規模が大きくなる程、「動いた」ではなかなか成り立たない代物ですので、あらゆる障害ケースの検証を繰り返し、死活監視パターンを増やしていかれる事をオススメします。
また、この死活監視については、MHA だけでなく、LVS からのハンドリングも可能です。