InnoDB Cluster検証してみた

こちらはDMM.com #2 Advent Calendar 2017の25日目の記事です。

前日の記事は@tinojiさんの[SonarQubeとCircleCIで技術的負債を駆逐せよ！](https://qiita.com/tinoji/items/9a0f03695246a0228570)でした。


弊社のアドベントカレンダーのURLはこちら

[DMM.com #1 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/dmm)
[DMM.com #2 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/dmm2)


# tl;dr
oow 2016で発表されてたInnodb Cluster触ってみた
~~mysqlshellのバージョンによっては正常に動作しなかった~~


# まえがき
huatoです。
主に各サービスのデータベースの面倒を見ています。
最近は業務改善がメインで、運用負荷の軽減と障害発生時のダウンタイム短縮につながればと
MySQL5.7対応とあわせて各種の動作検証を続けてきました。
インストール中に発生した問題と解決方法についてまとめました。
少しでもお役に立てれば幸いです。


# InnoDB Cluster

オラクル社純正のパッケージだけでDBの高可用性構成がとれる製品です。
障害発生時のスレーブの自動切り離し、マスター昇格など
従来は管理者がデータベース運用時に独自実装してたことが機能として兼ね備えられています。
MHAではスクリプトを別で用意してやる必要がありました。
データのコピーはrsyncやxtraBackupで行わずMySQLの機能のみでまかなっている点がPercona XtraDB Clusterと異なります。
現在はシングルプライマリ構成がメインですが将来的にはマルチマスターもサービスレベルで使えるようになる、はず。
Group Replicationについては別記事にしました。
https://qiita.com/huato/items/29714e2ddf72df7009ec


# 環境
CentOS 7.3 x86_64
MySQL:5.7.20
CPU:2core
RAM:2GB

[mysql-router]
advent-01  192.168.0.100 ipalias 192.168.0.112
advent-02  192.168.0.101 ipalias 192.168.0.110
advent-03  192.168.0.102 ipalias 192.168.0.111

[mysql group replication]
advent-04  192.168.0.103
advent-05  192.168.0.104
advent-06  192.168.0.105


# 下準備
ポート開放
InnoDB Cluster構成独自のポートを解放します。
13306:Group Replicationのnode間の通信ポート、
33060:mysqlsh接続時のdefaultポート,X Protocolが使用です。
今回は検証ですのでポートまるごと解放してます。

innodb cluster関連ポート解放
firewall-cmd --add-port=3306/tcp --permanent
firewall-cmd --add-port=33060/tcp --permanent
firewall-cmd --add-port=13306/tcp --permanent
設定反映
firewall-cmd --reload
設定確認
firewall-cmd --list-all-zones


hosts追記
Group Replicationの各サーバのIP情報をすべてのサーバの/etc/hostsに追記します。

記載例
192.168.0.103 advent-04
192.168.0.104 advent-05
192.168.0.105 advent-06


MySQLインストール方法については割愛します。

rootユーザパスワード変更

mysqlsh経由の操作はパスワードが空の状態だと実行できず
単純なパスワードを設定すると後述するレプリケーション設定時に失敗しました。
https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-19.html


SET SQL_LOG_BIN=0;
ALTER USER 'root'@'localhost' IDENTIFIED BY '英数ランダム+記号';                                                                                                                 
SET SQL_LOG_BIN=1;


# MySQL-Shell

rpmでインストールします。
wget https://dev.mysql.com/get/Downloads/MySQL-Shell/mysql-shell-1.0.11-1.el7.x86_64.rpm
rpm -ivh MySQL-Shell/mysql-shell-1.0.11-1.el7.x86_64.rpm

mysqlsh --uri root@localhost:3306

Creating a Session to 'root@localhost:3306'
Enter password: 
ERROR: 1130 (HY000): Host '::1' is not allowed to connect to this MySQL server

/etc/hostsでlocalhost追記
現象変わらず

skip-name-resolve  コメントアウトしてmysqld再起動

sed -i 's/skip-name-resolve/#skip-name-resolve/g' /etc/my.cnf        
systemctl restart mysqld

再度挑戦し今度はログインできました。
初期設定が要件を満たしているかチェックと修正を実施します。

```bash
mysql-js>  dba.configureLocalInstance();                                                                                                                                                                                                   
Please provide the password for 'root@localhost:3306': 

Detecting the configuration file...
Found configuration file at standard location: /etc/my.cnf
Do you want to modify this file? [Y|n]:  [Y|n]: y
MySQL user 'root' cannot be verified to have access to other hosts in the network.

1) Create root@% with necessary grants
2) Create account with different name
3) Continue without creating account
4) Cancel
Please select an option [1]: 1
Password for new account: 
Confirm password: 
Validating instance...

The instance 'localhost:3306' is valid for Cluster usage
You can now use it in an InnoDB Cluster.

{
    "status": "ok"
}
```
   
クラスターの作成

var cluster = dba.createCluster('myCluster', {ipWhitelist:'192.168.0.0/22,127.0.0.1/8'});


エラー

```
Creating InnoDB cluster 'myCluster' on 'root@localhost:3306'...
Dba.createCluster: To add an instance to the cluster, please use a valid, non-local hostname or IP. localhost can only be used with sandbox MySQL instances. (RuntimeError)
```

localhostではなくIPアドレス直指定で再度接続してねということでやり直し。

[root@advent-06 ~]# mysqlsh --uri root@192.168.0.103:3306

```
mysql-js> var cluster = dba.createCluster('myCluster', {ipWhitelist:'192.168.0.0/22,127.0.0.1/8'});
A new InnoDB cluster will be created on instance 'root@192.168.0.103:3306'.

Creating InnoDB cluster 'myCluster' on 'root@192.168.0.103:3306'...
Adding Seed Instance...

Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.
At least 3 instances are needed for the cluster to be able to withstand up to
one server failure.
```

クラスタの作成に成功しました。
statusがONLINEであれば正常です。

```
mysql-js> var cluster = dba.getCluster();
mysql-js> cluster.status();                                                                                                                                                                                                                
{
    "clusterName": "myCluster", 
    "defaultReplicaSet": {
        "name": "default", 
        "primary": "192.168.0.103:3306", 
        "status": "OK_NO_TOLERANCE", 
        "statusText": "Cluster is NOT tolerant to any failures.", 
        "topology": {
            "192.168.0.103:3306": {
                "address": "192.168.0.103:3306", 
                "mode": "R/W", 
                "readReplicas": {}, 
                "role": "HA", 
                "status": "ONLINE"
            }
        }
    }
}
```



残り2台のnodeをクラスタに登録します。

cluster.addInstance('root@192.168.0.104:3306',  {ipWhitelist:'192.168.0.0/22,127.0.0.1/8'});
cluster.addInstance('root@192.168.0.105:3306',  {ipWhitelist:'192.168.0.0/22,127.0.0.1/8'});



mysql> select * from performance_schema.replication_group_members;

```
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | 26330a88-e6c3-11e7-9899-9ca3ba26309e | advent-04   |        3306 | ONLINE       |
| group_replication_applier | 95177de6-e6c4-11e7-8a41-9ca3ba2e60c5 | advent-05   |        3306 | RECOVERING   |
| group_replication_applier | d43cd4f2-e6c3-11e7-bc93-9ca3ba295d10 | advent-06   |        3306 | RECOVERING   |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
```

2017-12-22T10:17:44.788713+09:00 0 [ERROR] Plugin group_replication reported: '[GCS] The member was unable to join the group. Local port: 13306'
13306ポートの通信許可設定がされてないと失敗します。


GTID関係のエラーが出力された場合はreste masterしてGTIDをリセットしたところ解消されました。

```
2017-12-22T11:20:18.076317+09:00 0 [ERROR] Plugin group_replication reported: 'This member has more executed transactions than those present in the group. Local transactions: 0aa0ba56-e6be-11e7-beb4-9ca3ba2e60c5:1-124 > Group transactions: 87c6cc6c-e6b5-11e7-907c-9ca3ba26309e:1-41,
b4a208ce-e414-11e7-a6d2-9ca3ba26309e:1-16'
2017-12-22T11:20:18.076374+09:00 0 [ERROR] Plugin group_replication reported: 'The member contains transactions not present in the group. The member will now exit the group.'
2017-12-22T11:20:18.076383+09:00 0 [Note] Plugin group_replication reported: 'To force this member into the group you can use the group_replication_allow_local_disjoint_gtids_join option'
Cluster.addInstance: WARNING: Not running locally on the server and can not access its error log.
```

さらにrootのパスワード強度が弱い場合も自分の時は失敗しました。
無効化したはずだったんですが。。

mysql> SHOW VARIABLES LIKE 'validate_password%';
Empty set (0.00 sec)

https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-19.html

release noteにそれらしきものが記載されていましたのでパスワードを記号含む英数ランダム8文字以上に変更しました。

```
Replication: When first starting the MySQL server following an installation from RPM, 
passwword validation plugin is activated by default (true only for RPM installations).
 If binary logging was already enabled at this time, the activation was logged, even though plugin activations should not be recorded in the binary log. (Bug #25672750)
```
~~ここまでの対応をしてもRECOVERINGからONLINEにMEMBER_STATEが変更されません。~~
~~ログを見ると、マスター候補の環境のバイナリログを先頭から実施しようとしてmysqlデータベースがすでに存在することでエラーになっていました。~~

~~2017-12-22T13:05:14.914542+09:00 61 [Warning] Slave: Can't create database 'mysql'; database exists Error_code: 1007~~

~~内部処理でCHANGE MASTER実施時にmaster_log_posが不要に見受けられるのですが、解消できそうなオプションなどは見当たりませんでしたので~~
~~mysql-shell-1.0.8-0.1にダウングレードしたところ解消されました。~~
20180310追記
上記間違いでした。
追加対象のnode上から実行することで問題解消しました。
https://dev.mysql.com/doc/refman/5.7/en/mysql-innodb-cluster-production-deployment.html

```
Use the cluster.addInstance(instance) function to add more instances to the cluster, where instance is a URI type string to connect to the local instance. 

```
各node環境にログインしてcluster.addInstanceする必要があります。

# MySQL Router

Master環境でユーザを作成します。
WITH GRANT OPTIONはmysql-routerにレプリケーショングループを登録時に専用のユーザが自動作成されるために必要です。

```
create user 'srepl'@'192.168.0.101' identified by '英数ランダム+記号';
create user 'srepl'@'192.168.0.102' identified by '英数ランダム+記号';
create user 'srepl'@'192.168.0.100' identified by '英数ランダム+記号';
GRANT RELOAD, SHUTDOWN, PROCESS, FILE, SUPER, REPLICATION SLAVE, REPLICATION CLIENT, CREATE USER ON *.* TO 'srepl'@'192.168.0.101' WITH GRANT OPTION;
GRANT SELECT, INSERT, UPDATE, DELETE ON `mysql`.* TO 'srepl'@'192.168.0.101' WITH GRANT OPTION;
GRANT SELECT ON `performance_schema`.* TO 'srepl'@'192.168.0.101' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON `mysql_innodb_cluster_metadata`.* TO 'srepl'@'192.168.0.101' WITH GRANT OPTION;
GRANT RELOAD, SHUTDOWN, PROCESS, FILE, SUPER, REPLICATION SLAVE, REPLICATION CLIENT, CREATE USER ON *.* TO 'srepl'@'192.168.0.102' WITH GRANT OPTION;
GRANT SELECT, INSERT, UPDATE, DELETE ON `mysql`.* TO 'srepl'@'192.168.0.102' WITH GRANT OPTION;
GRANT SELECT ON `performance_schema`.* TO 'srepl'@'192.168.0.102' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON `mysql_innodb_cluster_metadata`.* TO 'srepl'@'192.168.0.102' WITH GRANT OPTION;
GRANT RELOAD, SHUTDOWN, PROCESS, FILE, SUPER, REPLICATION SLAVE, REPLICATION CLIENT, CREATE USER ON *.* TO 'srepl'@'192.168.0.100' WITH GRANT OPTION;
GRANT SELECT, INSERT, UPDATE, DELETE ON `mysql`.* TO 'srepl'@'192.168.0.100' WITH GRANT OPTION;
GRANT SELECT ON `performance_schema`.* TO 'srepl'@'192.168.0.100' WITH GRANT OPTION;
GRANT ALL PRIVILEGES ON `mysql_innodb_cluster_metadata`.* TO 'srepl'@'192.168.0.100' WITH GRANT OPTION;

```

yumでさくっとインストールします。
wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm
yum --enablerepo=mysql57-community --disablerepo=mysql56-community --disablerepo=epel install mysql-community-client mysql-router

mysql-router各環境それぞれで既存のレプリケーショングループを登録します。
登録時にDBにユーザが自動生成されます。


[root@advent-02 src]#mysqlrouter --bootstrap srepl@advent-04 --user=mysqlrouter

```
Please enter MySQL password for srepl: 
WARNING: The MySQL server does not have SSL configured and metadata used by the router may be transmitted unencrypted.

Bootstrapping system MySQL Router instance...
MySQL Router  has now been configured for the InnoDB cluster 'myCluster'.

The following connection information can be used to connect to the cluster.

Classic MySQL protocol connections to cluster 'myCluster':
- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447

X protocol connections to cluster 'myCluster':
- Read/Write Connections: localhost:64460
- Read/Only Connections: localhost:64470

Existing configurations backed up to /etc/mysqlrouter/mysqlrouter.conf.bak
```


nmtuiでIP付与


[root@advent-02 mysqlrouter]# systemctl status mysqlrouter

```
* mysqlrouter.service - MySQL Router
   Loaded: loaded (/usr/lib/systemd/system/mysqlrouter.service; disabled; vendor preset: disabled)
   Active: failed (Result: exit-code) since Sat 2017-12-23 07:35:29 JST; 7s ago
  Process: 4049 ExecStart=/usr/bin/mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf (code=exited, status=1/FAILURE)
 Main PID: 4049 (code=exited, status=1/FAILURE)

Dec 23 07:35:29 advent-02 systemd[1]: Started MySQL Router.
Dec 23 07:35:29 advent-02 systemd[1]: Starting MySQL Router...
Dec 23 07:35:29 advent-02 mysqlrouter[4049]: Error: stat() failed (/var/lib/mysqlrouter/keyring): Permission denied
Dec 23 07:35:29 advent-02 systemd[1]: mysqlrouter.service: main process exited, code=exited, status=1/FAILURE
Dec 23 07:35:29 advent-02 systemd[1]: Unit mysqlrouter.service entered failed state.
Dec 23 07:35:29 advent-02 systemd[1]: mysqlrouter.service failed.
```

ファイルの権限が足りていません。

https://bugs.mysql.com/bug.php?id=86050

実行ユーザに権限を変更して対処しました。
cd /var/lib
chown -R mysqlrouter:mysqlrouter mysqlrouter


configの設定を書き換えます。
マニュアルに記載はありませんでしたが
bind_addressでreadとwriteの向け先をコントールできそうです。



cat /etc/mysqlrouter/mysqlrouter.conf

```
# File automatically generated during MySQL Router bootstrap
[DEFAULT]
name=system
user=mysqlrouter
keyring_path=/var/lib/mysqlrouter/keyring
master_key_path=/etc/mysqlrouter/mysqlrouter.key
logging_folder=/var/log/mysqlrouter

[logger]
level = INFO

[metadata_cache:myCluster]
router_id=1
bootstrap_server_addresses=mysql://192.168.0.103:3306,mysql://192.168.0.104:3306,mysql://192.168.0.105:3306
user=mysql_router1_5npdqnkckv4u
metadata_cluster=myCluster
ttl=10

[routing:myCluster_default_rw]
bind_address=192.168.0.101
bind_port=3306
destinations=metadata-cache://myCluster/default?role=PRIMARY
mode=read-write
protocol=classic
max_connect_errors=2048
max_connections=1024

[routing:myCluster_default_ro]
bind_address=192.168.0.110
bind_port=3306
destinations=metadata-cache://myCluster/default?role=SECONDARY
mode=read-only
protocol=classic
max_connect_errors=2048
max_connections=1024
```

まずはマスターへの接続を試してみます。
何度実行してもマスターにしか接続が割り振られません。

```
[root@advent-01 mysqlrouter]# mysql -uroot -p -h 192.168.0.102
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8667
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show variables like 'hostname';
+---------------+-----------+
| Variable_name | Value     |
+---------------+-----------+
| hostname      | advent-04 |
+---------------+-----------+
1 row in set (0.01 sec)
```

次はスレーブに接続を試みます。
実行ごとにRound Robinで接続先の環境が切り替わりました。

[root@advent-01 mysqlrouter]# mysql -uroot -p -h 192.168.0.111
Enter password: 

```
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 20
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show variables like 'hostname';
+---------------+-----------+
| Variable_name | Value     |
+---------------+-----------+
| hostname      | advent-06 |
+---------------+-----------+
1 row in set (0.00 sec)
```

# まとめ
アプリケーションからの接続のコントロールをInnoDB Clusterにまかせることで
従来の運用と比べて運用負荷が下がりそうです。
本番運用を想定した負荷試験の結果などはまた機会があればどこかに記事を書きたいと思います。

参考URL
https://dev.mysql.com/doc/dev/mysqlsh-api-javascript/1.0/classmysqlsh_1_1dba_1_1_dba.html#a60dfedbd2784ba132879a21a7b13ae4b
https://mysqlserverteam.com/innodb-cluster-in-opc/
https://mysqlserverteam.com/innodb-cluster-in-opc-part2/
https://dev.mysql.com/doc/mysql-router/2.1/en/mysql-router-conf-options.html
https://dev.mysql.com/doc/refman/5.7/en/mysql-innodb-cluster-production-deployment.html
https://www.slideshare.net/ShinyaSugiyama/mysql-innodb-clusterdb-tech-showcase-2017