三流エンジニアの落書き帳

さあ、今日も未知という扉を開けてみよう

InnoDB Cluster/ReplicasetのインスタンスのMySQL Router経由のアクセスを一時的に不可能にする

何気にブログを始めて半年ほど経っていました。

今のところ最低でも月に2本ほど書いているようですね。 ただ自分の中でノルマみたいなのは設定しないようにしています。(義務になっちゃうと一気にやる気を失せちゃうタイプなので)

たぶん無理に更新頻度を高く設定しないで書きたいときに書いているから続けられているだろうなあと思っています。

さて、本題へ…

今回はMySQL Router/Shellについてです。

MySQL Router/Shell 8.0.21から新たにInnoDB ClusterもしくはInnoDB Replicasetに含まれるインスタンスを一時的にアプリケーションからアクセス不可能にする機能が追加されました。

これにより例えばローリングアップグレードをする時や、バッグアップなど一時的にサーバー負荷が上がる操作をする時などに わざわざインスタンスを停止させたり、Cluster.removeInstance()をする必要がなくなりました。

というわけで早速触ってみます。

3行で

  • 8.0.21からMySQL Router経由のアクセスを制御できるようになった

  • これを使うことでインスタンスを停止することなくアプリケーションからの接続を不可能にすることができる

  • メンテナンスが組みやすくなる!

MySQL Router概要

その前にMySQL Routerについて簡単に整理しておきます。

MySQL RouterはMySQL Group Replicationとともに使うことでInnoDB Clusterを構成することが可能です。(正式には+MySQL Shell)

MySQL Routerは定期的にGroup Replicationからメタデータを取得しています。 このメタデータには例えばクラスターにはどのようなインスタンスが含まれているのか、インスタンスのステータス(書き込み可能か、インスタンスが停止していないか)などが含まれます。 そして、MySQL Routerはアプリケーションからクエリを受け取り、適切なインスタンスにそのリクエストを送ります。

MySQL Routerはアプリケーションに透過性を提供します。 すなわち、アプリケーションはクラスターに含まれるインスタンスが何台あるのか、故障しているインスタンスが無いのかといったことを考える必要がありません。

なお、今回はInnoDB Clusterに焦点を置いていますが、InnoDB ReplicaSetにも適用が可能です。

InnoDB ReplicaSetの場合はレプリケーション遅延が出ているインスタンスを一時的にアクセス不可にすることなども可能です。

バージョン

[root@centos8-mysql:~]# mysqld -V
/usr/sbin/mysqld  Ver 8.0.21 for Linux on x86_64 (MySQL Community Server - GPL)
[root@centos8-mysql:~]# mysql -V
mysql  Ver 8.0.20 for Linux on x86_64 (MySQL Community Server - GPL)
[root@centos8-mysql:~]# mysqlsh -V
mysqlsh   Ver 8.0.21 for Linux on x86_64 - for MySQL 8.0.21 (MySQL Community Server (GPL))
[root@centos8-mysql:~]# mysqlrouter -V
MySQL Router  Ver 8.0.21 for Linux on x86_64 (MySQL Community - GPL)

InnoDB Group Replicationの準備

今回はMySQL Shellのdba.deploySandboxInstance()を使って、ローカルのVMに3台のサンドボックス環境を立ち上げ、それらでGroup Replicationを構成します。 dba.deploySandboxInstance()を使ってGroup Replicationを構築する手順は過去に紹介していますので、よろしければそちらをご参考ください。

koreshiki-nanno.hatenablog.com

今回は4000、4001、4002をそれぞれポート番号に持つインスタンスを用意し、Single-PrimaryモードでGroup Replicationを構築しました。

 TEST  MySQL 8.0.21  root@localhost:4000 ssl 
 JS  2020-07-17 22:44:16   > cluster.status()
{
    "clusterName": "myCluster", 
    "defaultReplicaSet": {
        "name": "default", 
        "primary": "127.0.0.1:4000", 
        "ssl": "REQUIRED", 
        "status": "OK", 
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
        "topology": {
            "127.0.0.1:4000": {
                "address": "127.0.0.1:4000", 
                "mode": "R/W", 
                "readReplicas": {}, 
                "replicationLag": null, 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.21"
            }, 
            "127.0.0.1:4001": {
                "address": "127.0.0.1:4001", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": null, 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.21"
            }, 
            "127.0.0.1:4002": {
                "address": "127.0.0.1:4002", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": null, 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.21"
            }
        }, 
        "topologyMode": "Single-Primary"
    }, 
    "groupInformationSourceMember": "127.0.0.1:4000"
}

MySQL Routerの準備

--bootstrapオプションを使うことでmysqlrouter設定ファイルを自動で生成することが可能です。

[root@centos8-mysql:~]# mysqlrouter --bootstrap=localhost:4000 --user=mysqlrouter

[root@centos8-mysql:~]# systemctl start mysqlrouter

[root@centos8-mysql:~]# systemctl status mysqlrouter
● mysqlrouter.service - MySQL Router
   Loaded: loaded (/usr/lib/systemd/system/mysqlrouter.service; disabled; vendor preset: disabled)
   Active: active (running) since Fri 2020-07-17 22:50:14 JST; 3s ago
 Main PID: 4514 (mysqlrouter)
    Tasks: 9 (limit: 11492)
   Memory: 10.9M
   CGroup: /system.slice/mysqlrouter.service
           └─4514 /usr/bin/mysqlrouter -c /etc/mysqlrouter/mysqlrouter.conf

 7月 17 22:50:14 centos8-mysql systemd[1]: Started MySQL Router.
 7月 17 22:50:14 centos8-mysql mysqlrouter[4514]: logging facility initialized, switching logging to loggers specified in configuration

まずはデフォルトの状態でRead-Only用のポートでアクセスすると2つのセカンダリインスタンスに接続できるか確認します。

[root@centos8-mysql:~]# mysql -h127.0.0.1 -P6447 -e "select @@port"
+--------+
| @@port |
+--------+
|   4001 |
+--------+
[root@centos8-mysql:~]# mysql -h127.0.0.1 -P6447 -e "select @@port"
+--------+
| @@port |
+--------+
|   4002 |
+--------+

デフォルトのルーティングストラテジーラウンドロビンなので上記のようにそれぞれのセカンダリインスタンスにリクエストがいっていることがわかります。

インスタンスを一時的にアクセス不可能にする

それでは今回の主題であるインスタンスを一時的に不可能にする機能を使ってみます。 使い方はとてもシンプルです。

 TEST  MySQL 8.0.21  root@localhost:4000 ssl 
 JS  2020-07-17 23:00:22   > cluster.setInstanceOption("localhost:4002", "tag:_hidden", true)

Cluster.setInstanceOptionの第1引数には対象となるインスタンスを、第2引数と第3引数にはタグ及び設定する値を指定します。

今回の場合4002ポートのインスタンス_hiddenタグをtrue、つまり隠れている状態にします。

この状態で再びRead-Only用のポートにアクセスしてみます。

[root@centos8-mysql:~]# mysql -h127.0.0.1 -P6447 -e "select @@port"
+--------+
| @@port |
+--------+
|   4001 |
+--------+
[root@centos8-mysql:~]# mysql -h127.0.0.1 -P6447 -e "select @@port"
+--------+
| @@port |
+--------+
|   4001 |
+--------+
[root@centos8-mysql:~]# mysql -h127.0.0.1 -P6447 -e "select @@port"
+--------+
| @@port |
+--------+
|   4001 |
+--------+

ポートが4002のインスタンスにはアクセスが飛ばないことが確認できました。

この時でもインスタンスの状態はONLINEになっています。

 TEST  MySQL 8.0.21  root@localhost:4000 ssl 
 JS  2020-07-17 23:01:33   > cluster.status()
{
    "clusterName": "myCluster", 
    "defaultReplicaSet": {
        "name": "default", 
        "primary": "127.0.0.1:4000", 
        "ssl": "REQUIRED", 
        "status": "OK", 
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", 
        "topology": {
            "127.0.0.1:4000": {
                "address": "127.0.0.1:4000", 
                "mode": "R/W", 
                "readReplicas": {}, 
                "replicationLag": null, 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.21"
            }, 
            "127.0.0.1:4001": {
                "address": "127.0.0.1:4001", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": null, 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.21"
            }, 
            "127.0.0.1:4002": {
                "address": "127.0.0.1:4002", 
                "mode": "R/O", 
                "readReplicas": {}, 
                "replicationLag": null, 
                "role": "HA", 
                "status": "ONLINE", 
                "version": "8.0.21"
            }
        }, 
        "topologyMode": "Single-Primary"
    }, 
    "groupInformationSourceMember": "127.0.0.1:4000"
}

この方法が従来のインスタンスを停止したり、removeInstance()する方法と比べて優れている点はGroup Replicationの構成自体には何の変更もしていないという点です。 レプリケーション自体は継続されてるのでデータはしっかりと同期されています。

-- まずは5件のデータのみ
[root@centos8-mysql:~]# mysql -h127.0.0.1 -P4002 -e "select * from testdb.t1"
+---+---------------------+
| a | b                   |
+---+---------------------+
| 1 | 2020-07-17 23:15:08 |
| 2 | 2020-07-17 23:15:08 |
| 3 | 2020-07-17 23:15:09 |
| 4 | 2020-07-17 23:15:15 |
| 5 | 2020-07-17 23:15:16 |
+---+---------------------+

-- 1件INSERT
[root@centos8-mysql:~]# mysql -h127.0.0.1 -P6446 testdb -e "insert into testdb.t1 values (default, now())"

-- データは同期されている
[root@centos8-mysql:~]# mysql -h127.0.0.1 -P4002 -e "select * from testdb.t1"
+---+---------------------+
| a | b                   |
+---+---------------------+
| 1 | 2020-07-17 23:15:08 |
| 2 | 2020-07-17 23:15:08 |
| 3 | 2020-07-17 23:15:09 |
| 4 | 2020-07-17 23:15:15 |
| 5 | 2020-07-17 23:15:16 |
| 6 | 2020-07-17 23:16:15 |
+---+---------------------+

_hiddenの真偽を確認する

インスタンスがルーティングテーブルに含まれているかどうかはmysql_innodb_cluster_metadata.instances.attributesなどから確認が可能です。

mysql> SELECT instance_id, instance_name, address, attributes->>'$.tags' FROM mysql_innodb_cluster_metadata.instances;
+-------------+----------------+----------------+-----------------------+
| instance_id | instance_name  | address        | attributes->>'$.tags' |
+-------------+----------------+----------------+-----------------------+
|           1 | 127.0.0.1:4000 | 127.0.0.1:4000 | NULL                  |
|           2 | 127.0.0.1:4001 | 127.0.0.1:4001 | NULL                  |
|           3 | 127.0.0.1:4002 | 127.0.0.1:4002 | {"_hidden": true}     |
+-------------+----------------+----------------+-----------------------+

_hidden:trueであればMySQL Routerを通しての接続が不可能となります。

アクセス不可にするインスタンスの既存コネクションを処理する

tagには_hidden以外にも_disconnect_existing_sessions_when_hiddenというものがあります。

これは何かというと_hiddenタグをtrueにした時にそのインスタンスの既存コネクションをどのように処理するかを制御するもので

  • trueならば既存コネクションを切断する(デフォルトの動作)

  • falseならば既存コネクションは切断しない

といった動作となります。

trueの場合

mysql> SELECT instance_id, instance_name, address, attributes->>'$.tags' FROM mysql_innodb_cluster_metadata.instances where instance_id = 3;
+-------------+----------------+----------------+-----------------------------------------------------------------------+
| instance_id | instance_name  | address        | attributes->>'$.tags'                                                 |
+-------------+----------------+----------------+-----------------------------------------------------------------------+
|           3 | 127.0.0.1:4002 | 127.0.0.1:4002 | {"_hidden": true, "_disconnect_existing_sessions_when_hidden": true} |
+-------------+----------------+----------------+-----------------------------------------------------------------------+
mysql80-root@127.0.0.1:[(none)]>select @@port;
+--------+
| @@port |
+--------+
|   4002 |
+--------+
1 row in set (0.00 sec)

mysql80-root@127.0.0.1:[(none)]>\s
ERROR 2013 (HY000): Lost connection to MySQL server during query
mysql80-root@127.0.0.1:[(none)]>\s
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    58
Current database: *** NONE ***

--------------
mysql  Ver 8.0.20 for Linux on x86_64 (MySQL Community Server - GPL)

Connection id:      58
Current database:   
Current user:        root@localhost
SSL:            Cipher in use is TLS_AES_256_GCM_SHA384
Current pager:      less -i -X -R -E
Using outfile:      ''
Using delimiter:    ;
Server version:     8.0.21 MySQL Community Server - GPL
Protocol version:   10
Connection:     127.0.0.1 via TCP/IP
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb4
Conn.  characterset:    utf8mb4
TCP port:       6447
Binary data as:     Hexadecimal
Uptime:         1 hour 47 min 29 sec

Threads: 6  Questions: 2557  Slow queries: 0  Opens: 333  Flush tables: 3  Open tables: 251  Queries per second avg: 0.396
--------------

mysql80-root@127.0.0.1:[(none)]>select @@port;
+--------+
| @@port |
+--------+
|   4001 |
+--------+
1 row in set (0.00 sec)

4002ポートに接続していたセッションはLost connectionエラーを受け取っています。 そして再度接続した際はまだ_hiddenタグがtrueとなっていない4001ポートに接続しに行きます。 これはデフォルトの動作です。

falseの場合

mysql> SELECT instance_id, instance_name, address, attributes->>'$.tags' FROM mysql_innodb_cluster_metadata.instances where instance_id = 3;
+-------------+----------------+----------------+-----------------------------------------------------------------------+
| instance_id | instance_name  | address        | attributes->>'$.tags'                                                 |
+-------------+----------------+----------------+-----------------------------------------------------------------------+
|           3 | 127.0.0.1:4002 | 127.0.0.1:4002 | {"_hidden": true, "_disconnect_existing_sessions_when_hidden": false} |
+-------------+----------------+----------------+-----------------------------------------------------------------------+
mysql80-root@127.0.0.1:[(none)]>select @@port;
+--------+
| @@port |
+--------+
|   4002 |
+--------+
1 row in set (0.00 sec)

mysql80-root@127.0.0.1:[(none)]>\s
--------------
mysql  Ver 8.0.20 for Linux on x86_64 (MySQL Community Server - GPL)

Connection id:      69
Current database:   
Current user:        root@localhost
SSL:            Cipher in use is TLS_AES_256_GCM_SHA384
Current pager:      less -i -X -R -E
Using outfile:      ''
Using delimiter:    ;
Server version:     8.0.21 MySQL Community Server - GPL
Protocol version:   10
Connection:     127.0.0.1 via TCP/IP
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb4
Conn.  characterset:    utf8mb4
TCP port:       6447
Binary data as:     Hexadecimal
Uptime:         1 hour 49 min 40 sec

Threads: 7  Questions: 2740  Slow queries: 0  Opens: 329  Flush tables: 3  Open tables: 247  Queries per second avg: 0.416
--------------

mysql80-root@127.0.0.1:[(none)]>select @@port;
+--------+
| @@port |
+--------+
|   4002 |
+--------+
1 row in set (0.00 sec)

ちょっとこのログだけではわかりにくいですが、_disconnect_existing_sessions_when_hiddenタグがfalseの場合は_hiddenタグをtrueにしても 既存セッションは切断されることなくそのインスタンスに接続したままとなります。

この辺りは何を行うために_hiddenタグをtrueにするかによって_disconnect_existing_sessions_when_hiddenタグをtrueにするかfalseにするか決めるべきでしょう。 といっても大抵の場合は_disconnect_existing_sessions_when_hiddenタグはデフォルトのtrueでいいような気もします。

まとめ

ということで、今回はMySQL Routerに新たに追加されたインスタンスへのアクセス制御の方法を見てきました。

これを使うことで運用がさらに簡単になるのではないでしょうか。

今回は、このあたりで。

参考にさせていただいたサイト