ＭｙＳＱＬ８．０のインストールと初期セットアップ

ＣｅｎｔＯＳ７上でＭｙＳＱＬ８．０のセットアップを行ってみた。色々と問題が発生したが、ナウでホットな話題のせいか、トラブルシュートが分散してたのでここでまとめてみる。

# 前提条件

## 検証環境
* CentOS:  `7.7.1908`/`x86_64`（`7.6.1810`）
* SELinux: `enforcing`モードで運用[^備考１]
* MySQL:　 `8.0.18`～`8.0.19`、`8.0.22`（`8.0.14`～`8.0.17`）

## メモ
* ※ここで紹介するＭｙＳＱＬ８．０のインストール手順（環境）は８．０．Ｘ（Ｘはパッチバージョン）を追いかけるものであるため、本記述とは違う（新しい）バージョンになっている場合がある。
* ここに書いてある基本的な部分について、改訂は無いと思われるが、バージョンが違う場合は[リリースノート](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/)を注意深く確認すること。
* バージョンアップにともなう細かいオペレーション（手動更新作業）についてはここでは記載しない。あくまで新規セットアップのみ記述する。
* またＣｅｎｔＯＳ７のインストールについては、既に基本的な設定[^備考２]がなされているものとする。
* 久々にセットアップ（８．０．１９）したら、グループレプリケーションの[設定ルールが変更](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-18.html#mysqld-8-0-18-feature)になったので、手順を変更する。
* この手順が旧バージョンでも有効かは未確認。
* また、ＳＥＬｉｎｕｘの振る舞いが７．６と７．７で若干違うこともあり、有効化は未確認。

## 構築目標
* 「一台」以上のグループレプリケーションを構成する。
* デフォルトの認証プラグインは`mysql_native_password`とするが、それ以外では可能な限り新しい認証プラグイン（`caching_sha2_password`）を使用する。
* 直後にサービス提供可能なよう、文字コードとタイムゾーンを設定しておく。
* ＳＥＬｉｎｕｘが有効（エンフォースモード）な環境でも稼働させることを目指す。
* ただし上記検証リストのうち、古い環境では動かなかったケースがある。最新の環境では上手くいったので有効を前提に記述する。

## 想定ネットワーク構成
サーバーは下記のようなネットワーク内に設置されているものとする

```plaintext
192.168.100.0/24
--------+---------------------------+--------------------
        |                           |
    eth0| 192.168.100.11        eth0| 192.168.100.12
+-------+--------+          +-------+--------+
| gr1.example.jp |          | gr2.example.jp |  ・・・
+-------+--------+          +-------+--------+
    eth1| 192.168.200.11        eth1| 192.168.200.12
        |                           |
--------+---------------------------+--------------------
192.168.200.0/24
```

* 自環境で使用する場合は、上記構成を参考に読み替えたし。
* ファイアウォールのゾーン分け（説明がめんどくさいので）をしないので、各インターフェース毎に振る舞いは変わらないモノとする。

# インストール

## ＭｙＳＱＬ ＹＵＭリポジトリのセットアップ
```shell
sudo yum install https://repo.mysql.com/yum/mysql-8.0-community/el/7/x86_64/mysql80-community-release-el7-3.noarch.rpm
```

デフォルトで`mysql80-community`リポジトリが有効になっているので、今回はそのまま利用する。

## ＭｙＳＱＬ互換ライブラリのアップデート
```shell
sudo yum -y update
```
この時、ＯＳ標準で提供される `mariadb-libs-5.5.60-1`（ないしは`mariadb-libs-5.5.64-1`）が`mysql-community-libs-compat-8.0.Ｘ-1`（`Ｘ`はパッチバージョン）に置き換わる。

## ＭｙＳＱＬ８．０サーバーのインストール
```shell
sudo yum -y install mysql-community-server
```
この時クライアントツール（`mysql`、`mysqladmin`等）もインストールされる。

# ＳＥＬｉｎｕｘとファイアウォールの一時無効化
ＳＥＬｉｎｕｘとファイアウォールの稼働に考慮した設定を行っているため `enforcing` モードでのセットアップは可能である。
セットアップに自信が無い場合は、一時的に無効化しておく。セットアップに慣れて、これらが有効な状態でも、問題無くできることを確認してから、有効にすること。

```shell
sudo setenforce 0
sudo systemctl stop firewalld
```

* ＳＥＬｉｎｕｘの無効化は`disabled`（再起動が必要）か`permissive`（オンザフライで変更可能）となるが、いつでも有効・無効化ができるよう`permissive`にしておく。
* 自分はＳＥＬｉｎｕｘの有効利用を推奨いたします（ポリコレ棍棒）。
* ＳＥＬｉｎｕｘの永続的な変更については`/etc/selinux/config`ファイルの`SELINUX=`行の設定を変更すること。

# 初期設定

## /etc/hosts
自ホストのエイリアスのみ`ethｎ`を加えるものとする。

下記のようなホスト構成がなされているものとする。
またこれを設定せず、全てＩＰアドレスで指定することは問題ない。
その場合、都度置き換えて解釈すること。

### 一台目
```plaintext
192.168.200.11    gr1.example.jp  eth1
192.168.100.11    gr1.example.jp  eth0
192.168.200.12    gr2.example.jp
192.168.100.12    gr2.example.jp
192.168.200.13    gr3.example.jp
192.168.100.13    gr3.example.jp
```

### 二台目
```plaintext
192.168.200.11    gr1.example.jp
192.168.100.11    gr1.example.jp
192.168.200.12    gr2.example.jp  eth1
192.168.100.12    gr2.example.jp  eth0
192.168.200.13    gr3.example.jp
192.168.100.13    gr3.example.jp
```

## /etc/my.cnf
設定は基本`/etc/my.cnf`で設定するのでなく、`/etc/my.cnf.d/`ディレクトリ以下に置いた`.cnf`ファイルを作成することで対応するものとする。

```diff
--- /etc/my.cnf.orig    2018-10-07 09:24:13.000000000 +0000
+++ /etc/my.cnf 2018-12-27 07:44:08.831076978 +0000
@@ -29,3 +29,5 @@

 log-error=/var/log/mysqld.log
 pid-file=/var/run/mysqld/mysqld.pid
+
+!includedir /etc/my.cnf.d
```

## /etc/my.cnf.d/authentication.cnf
```ini
[mysqld]
default-authentication-plugin   = mysql_native_password
default-password-lifetime       = 0
```

### authentication.cnf についての設定メモ
* 悪名高い`default-password-lifetime=360`問題を意識するため、明示的に０に設定する。
* ここで、バリデートパスワード機能の設定を行いたいところであるが、直近の変更（８．０．４以降）により、[プラグインからコンポーネントに再実装された](https://dev.mysql.com/doc/refman/8.0/en/validate-password.html)関係で、設定ファイルでは設定できなくなったため、ここでは設定しない（重要なので二回言った）。

## /etc/my.cnf.d/charset.cnf
```ini
[mysqld]
character-set-server            = utf8mb4
collation-server                = utf8mb4_bin

[client]
loose-default-character-set     = utf8mb4
```

### charset.cnf についての設定メモ
* ８．０．１にて、[デフォルト文字コードが`utf8mb4`になった](https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-1.html#mysqld-8-0-1-charset)とのことだが、各々の環境でどうしているかはこういう形で明示すべきと考えている。
* またコレーション（照合順序）も重要な要素であるため、データベース運用前に何を選択すべきか、よく話し合った上で明示すること。
* コレーション設定が `utf8mb4_bin` とあるのは、照合順序を無秩序（文字コードそのもので判定）とするためで、特にソート順がめちゃくちゃになる点に注意。
* クライアント（`client`）の中には `default-character-set` が設定されているとエラーになるケースがあるので、`loose-` プレフィックスを付与して、そのようなクライアントでも問題無いよう保護（オプションを無視させる）する。

## /etc/my.cnf.d/max-connections.cnf
```ini
[mysqld]
open-files-limit                = 65536
max-connections                 = 10000
table-open-cache                = 2000
```

### max-connections.cnf についての設定メモ
* データベースのリソース不足によくある一つに「ファイルディスクリプタ数」がある。
* Ｌｉｎｕｘ（大抵のＯＳはそうだが）自体は極めて大量のソケットやファイルを開くことができるが、デフォルトでかなり控えめ（４０９６）に制限されている。
* この制限の解放方法については後述するが、少メモリ環境でも「開けるだけなら」１万ファイルをオープンすることは余裕なので、ここではそこそこの値を設定してみる。
* より大規模な環境ではメモリ搭載量と合わせて大きく設定することになる（１００万オーダーとか）。
* なお後述する`/etc/systemd/system/mysqld.service.d/override.conf`の設定を合わせて実施しておくこと。

## /etc/my.cnf.d/mysqlx.cnf
```ini
[mysqld]
mysqlx                          = OFF
```
Ｘプラグイン機能を使用する（デフォルト）と、専用にポート（３３０６０／ＴＣＰ）を開くので、[使用しない場合は無効にしておく](https://dev.mysql.com/doc/refman/8.0/en/x-plugin-disabling.html)ことをお勧めする。

※この設定を行わないと、ＳＥＬｉｎｕｘ（の設定）により、ＭｙＳＱＬサーバーが立ち上がらない現象が発生する。ＳＥＬｉｎｕｘ有効化の時に、上記ポート番号の設定を実施する（こちらも後述）。

### /etc/sysconfig/mysql
久々にＭｙＳＱＬサーバーをセットアップしていて、８．０．２２でこの設定が機能していないとことを確認した。
起動時オプションの指定で無効化できたのを確認したので、上記設定の代わりに指定するのもありかもしれない。[マニュアル](https://dev.mysql.com/doc/refman/8.0/en/x-plugin-disabling.html)読み直しても、設定ファイル中で指定可能に見えるので、バグかも知れない。
取り急ぎのワークアラウンドというやつで。

```
MYSQLD_OPTS="--skip-mysqlx"
```

#### 愚痴
一応 `enforcing` モードでセットアップしてたのだけど、ＳＥＬｉｎｕｘ的には監査に引っかからずに無事起動。
ログを精査している時に気がついた。`semanage port -l | grep mysqld_port_t` 的（後述）にはひっかかるはずなんだが…。
まったく、ＳＥＬｉｎｕｘは魔窟だぜ！！

## /etc/my.cnf.d/network.cnf
```ini
[mysqld]
bind-address                    = eth1
port                            = 3306
```
ここで言う`eth1`は /etc/hosts で設定したものとなる。

## /etc/my.cnf.d/replication.cnf
```ini
[mysqld]
server-id                               = サーバーＩＤ
gtid-mode                               = ON
enforce-gtid-consistency                = ON
binlog-checksum                         = NONE
relay-log                               = mysql-relay-bin
loose-group-replication-group-name              = "グループレプリケーションＵＵＩＤ"
loose-group-replication-single-primary-mode     = ON
loose-group-replication-recovery-use-ssl        = ON
loose-group-replication-recovery-public-key-path = /etc/my.cnf.d/server_public_keys.pem
loose-group-replication-start-on-boot           = OFF                           #NOTE: Operating change by SET PERSIST :NOTE#
loose-group-replication-bootstrap-group         = OFF                           #NOTE: Operating change by SET PERSIST :NOTE#
loose-group-replication-local-address           = "自身のホスト名またはＩＰ:33061"
loose-group-replication-group-seeds             = "１台目のホスト名またはＩＰ:33061,２台目以降のホスト名またはＩＰ:33061,..."
```

※ＭｙＳＱＬサーバー立ち上げ前にグループレプリケーションプラグインのインストールが不可能なため、`loose-` プレフィックスを付けて後々有効化させる。

### replication.cnf についての設定メモ
* 「サーバーＩＤ」はレプリケーションするサーバー間でユニークとなるよう数字を割り振る。
* 本例ではサーバー名を、`gr1`、`gr2`、…と振ってることもあり`1`、`2`、…と設定しまうのが楽と思われ。
* 「グループレプリケーションＵＵＩＤ」はグループレプリケーションを構成する１つの集合に対する名前（ＵＵＩＤ）である。
* よって、同一グループレプリケーションに所属する全てのサーバーで同じＵＵＩＤを指定（共有）する。
* またＵＵＩＤは`uuidgen`コマンド[^備考４]を使用してテキトーに決める。
* 「自身のホスト名またはＩＰ」という表現であるが、どうも個々で指定したホスト名（ＩＰ）が重複してないかチェックしているようで`eth1`を指定して共通化することができなかった。 orz
* よってここでは丁寧に`gr1.example.jp:33061`のような形で指定する。
* 「１台目の～」「２台目の～」はグループレプリケーションを運用している主要なサーバーを指定しするもので、全てのサーバーを指定しなくてよい。
* 例えば初期に２台セットアップして、後日もう１台追加、という時でも、２台のまま設定変更しなくてもよい。
* その代わりここで指定されたサーバーが「全台」落ちることがないように注意すること。全台落ちた場合の復旧は手動で回復することになる。

### 一台目のＭｙＳＱＬサーバーのreplication.cnfの設定例
```ini
[mysqld]
server-id                               = 1
gtid-mode                               = ON
enforce-gtid-consistency                = ON
binlog-checksum                         = NONE
relay-log                               = mysql-relay-bin
loose-group-replication-group-name              = "4ea57fa3-76a1-4a4e-b119-759459629287"
loose-group-replication-single-primary-mode     = ON
loose-group-replication-recovery-use-ssl        = ON
loose-group-replication-recovery-public-key-path = /etc/my.cnf.d/server_public_keys.pem
loose-group-replication-start-on-boot           = OFF                           #NOTE: Operating change by SET PERSIST :NOTE#
loose-group-replication-bootstrap-group         = OFF                           #NOTE: Operating change by SET PERSIST :NOTE#
loose-group-replication-local-address           = "gr1.example.jp:33061"
loose-group-replication-group-seeds             = "gr2.example.jp:33061,gr3.example.jp:33061"
```

### 二台目のＭｙＳＱＬサーバーのreplication.cnfの設定例
```ini
[mysqld]
server-id                               = 2
gtid-mode                               = ON
enforce-gtid-consistency                = ON
binlog-checksum                         = NONE
relay-log                               = mysql-relay-bin
loose-group-replication-group-name              = "4ea57fa3-76a1-4a4e-b119-759459629287"
loose-group-replication-single-primary-mode     = ON
loose-group-replication-recovery-use-ssl        = ON
loose-group-replication-recovery-public-key-path = /etc/my.cnf.d/server_public_keys.pem
loose-group-replication-start-on-boot           = OFF                           #NOTE: Operating change by SET PERSIST :NOTE#
loose-group-replication-bootstrap-group         = OFF                           #NOTE: Operating change by SET PERSIST :NOTE#
loose-group-replication-local-address           = "gr2.example.jp:33061"
loose-group-replication-group-seeds             = "gr1.example.jp:33061,gr3.example.jp:33061"
```

## /etc/my.cnf.d/storage.cnf
```ini
[mysqld]
default-storage-engine          = InnoDB
innodb-dedicated-server         = TRUE
```
ここではＩｎｎｏＤＢ関連のパラメータについては議論しない。

```
innodb-log-file-in-group        = 2
```

が、一点。極めて特異なメモリ搭載量（２ＧＢ以上３ＧＢ未満）のサーバーだけで起きる現象対策として、`innodb-log-file-in-group`を設定しておく必要があったが、８．０．１９で検証するとエラーで止まる。

このパラメータは[８．０．１４](https://dev.mysql.com/doc/refman/8.0/en/innodb-dedicated-server.html)以降で自動的に調整されるパラメータとなったが、当該メモリのサーバーでは起動しなくなる現象が発生する。詳しくは後述。

なおこのサイズのメモリを搭載するサーバー上で稼働させる予定が無ければ設定しない方がいいです。というわけで、設定できなくなったのでちょっと回避が難しい。

## /etc/systemd/system/mysqld.service.d/override.conf
```shell
sudo systemctl edit mysqld
```

上記コマンド実行後に下記内容を記述して保存すること。

```ini
[Service]
LimitNOFILE=1048576
```

本設定はファイルディスクリプタ数の制限（４０９６）を緩和（１０４８５７６まで）する設定となる。

## ＳＥＬｉｎｕｘ関連設定
`semanage` コマンドは `policycoreutils-python` パッケージに含まれるコマンドである。このコマンドを呼び出せない場合は `yum -y install policycoreutils-python` を実行しておくこと。

```shell
sudo restorecon -RFv /etc/my.cnf /etc/my.cnf.d /etc/systemd

sudo semanage port -a -t mysqld_port_t -p tcp 33061
```

ＭｙＳＱＬサーバーで（デフォルトで許可され）使用できるポート番号は以下の通りである。

```shell-session
$ semanage port -l | grep mysqld_port_t
mysqld_port_t                  tcp      1186, 3306, 63132-63164
```

よって今回使用する３３０６１番ポートの追加設定を実施する。またＸプラグインを使用する場合は、同じ手順で３３０６０番ポートの追加設定を実施する。

## ファイアウォール関連設定
```shell
sudo firewall-cmd --service=mysql --add-port=33061/tcp --permanent
sudo firewall-cmd --add-service=mysql --permanent
sudo firewall-cmd --reload
```

ＭｙＳＱＬサーバーで（デフォルトで許可され）通信できるポート番号は以下の通りである。

```shell-session
$ firewall-cmd --info-service mysql
mysql
  ports: 3306/tcp
  protocols:
  source-ports:
  modules:
  destination:
```

よって今回使用する３３０６１番ポートの追加設定を実施する。またＸプラグインを使用する場合は、同じく３３０６０番ポートの追加設定を実施する。

# データベースサーバーの初期セットアップ（共通）

## ＭｙＳＱＬサーバーの起動および一時パスワードの保存
```shell
sudo systemctl start mysqld
awk '/temporary password/{ print $13 }' /var/log/mysqld.log
```

この時表示されるキーワード（一時パスワード）を以下のコマンド実行後に入力する。

```shell
mysql_config_editor set --login-path=root --user=root --password
```

一時パスワードに「`#`」が含まれているとこの設定が有効に機能してくれない（`mysql`コマンドでパスワード省略）問題を確認してるので、その場合は

* `/var/lib/mysql/`ディレクトリをクリアしてから再度立ち上げ直してみる。
* パスワード入力時に「`'`」（シングルクォート）でくくってやる。ただしパスワード中に「`'`」（シングルクォート）が存在した場合などの扱いがまた面倒。
* 実は「`\`」の扱いも面倒、なので、最初の提案に戻ったりする。

また本作業はカジュアルにパスワードを見なくても済むようにするためのもので、下記のコマンドを実行することで、容易にパスワードを確認できる点に注意。

```shell
my_print_defaults --login-path=root --show client
```

## ＭｙＳＱＬサーバーの初期セットアップ
以後ＳＱＬ文実行の際は下記コマンドを実行してから入力するモノとする。

```shell
mysql --login-path=root
```

### root パスワードの設定
```sql
SET SQL_LOG_BIN=0;
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '一時パスワード';
SET SQL_LOG_BIN=1;
```

* `SQL_LOG_BIN=1`（デフォルト）状態で `root` パスワードを設定すると、「同時期に」設定する他のサーバーにレプリケーションできなくなるので注意。
* 「同時期」かどうかはＢＩＮＬＯＧが最初から残っているかどうかで、ここではその詳細は問わない[^備考５]。
*  またここでは`'root'@'localhost'`の認証プラグインとして`caching_sha2_password`を明示したが、アプリケーションによっては`mysql_native_password`である必要があるので、どちらを使用すべきか確認すること。

バッチ的に設定するなら下記の通りでもよい。

```shell
mysql --login-path=root --connect-expired-password -e "SET SQL_LOG_BIN=0; ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '$(awk '/temporary password/{ print $13 }' /var/log/mysqld.log)'; SET SQL_LOG_BIN=1"
```

### バリデートパスワードコンポーネントの規制緩和
```sql
SET PERSIST validate_password.policy=LOW;
SET PERSIST validate_password.length=12;
```

### /etc/my.cnf.d/server_public_keys.pem の収集と配布

* このファイルは先に設定した`loose-group-replication-group-seeds`で指定されたサーバーのＳＳＬ証明書（ＰＥＭファイル）を連結したファイルである。
* 具体的には全てのサーバーの `/var/lib/mysql/server-cert.pem` ファイルの内容を`/etc/my.cnf.d/server_public_keys.pem`ファイルに連結する。

```plaintext
-----BEGIN CERTIFICATE-----
～
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
～
-----END CERTIFICATE-----
  :
```

* このファイル作成後に`restorecon`コマンドを実行しておくこと。

```shell
restorecon -Fv /etc/my.cnf.d/server_public_keys.pem
```
* 最後にこのファイルを全てのレプリケーショングループのサーバーに配布（更新）する。
* 具体的にＳＣＰコマンド使う、コピペする、など手段は問わない。また配布先サーバーで `restorecon` コマンドを実行しておくこと。
* 重要なのでまとめると、
  * `loose-group-replication-group-seeds`で指定されたサーバーの`/var/lib/mysql/server-cert.pem`を連結したものを`/etc/my.cnf.d/server_public_keys.pem`として用意する。【ＳＳＬサーバー】
  * 先に用意された`/etc/my.cnf.d/server_public_keys.pem`ファイルを、全てのレプリケーショングループサーバーで共有する。【ＳＳＬクライアント】
* なお本ファイル（＝`loose-group-replication-group-seeds`設定）の更新を行っても再起動は不要である。

### グループレプリケーションの設定
```sql
SET SQL_LOG_BIN=0;
INSTALL PLUGIN group_replication SONAME 'group_replication.so';
CREATE USER 'replication'@'%' IDENTIFIED WITH caching_sha2_password BY 'パスワード';
GRANT REPLICATION SLAVE ON *.* TO replication@'%';
SET SQL_LOG_BIN=1;

CHANGE MASTER TO MASTER_USER='replication', MASTER_PASSWORD='パスワード' FOR CHANNEL 'group_replication_recovery';
```

ここで指定するパスワードは全台共通でかつ、パスワードジェネレータを使用して生成した文字列（１２文字以上で）を設定する。

またユーザー名は取り急ぎ`replication`としている。

## ＭｙＳＱＬサーバーの初期セットアップ（１台目）
```sql
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
```

先の設定に引き続き、上記ＳＱＬを実行する。稼働状態については下記ＳＱＬ文により確認できる。

```sql
SELECT * FROM performance_schema.replication_group_members;
```

以下実行結果例：

```plaintext
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST       | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 1afe7555-1fc4-11e9-8959-005056a18228 | gr1.example.jp    |        3306 | ONLINE       | PRIMARY     | 8.0.19         |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
```

## ＭｙＳＱＬサーバーの初期セットアップ（２台目以降）
```sql
START GROUP_REPLICATION;
```
`group-replication-group-seeds` で指定されたサーバー（１台目など）のサーバーが生きてる状態で実行する。稼働状態については下記ＳＱＬ文により確認できる。

```sql
SELECT * FROM performance_schema.replication_group_members;
```

以下実行結果例：

```plaintext
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST       | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 1ac6f26d-1fc4-11e9-b5d9-005056a190c6 | gr2.example.jp    |        3306 | ONLINE       | SECONDARY   | 8.0.19         |
| group_replication_applier | 1afe7555-1fc4-11e9-8959-005056a18228 | gr1.example.jp    |        3306 | ONLINE       | PRIMARY     | 8.0.19         |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
```

もちろんどのサーバーで実行しても同じ状態が確認できれば問題無い。

## タイムゾーンのインストール（マスターサーバーでのみ実行）
```shell
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql --login-path=root mysql
```

## デフォルトタイムゾーンの設定（全てのサーバーで実行）
```sql
SET PERSIST time_zone = 'Asia/Tokyo';
```

## グループレプリケーションの自動起動（シングル構成の場合）
```sql
SET PERSIST group_replication_bootstrap_group=ON;
SET PERSIST group_replication_start_on_boot=ON;
```

## グループレプリケーションの自動起動（複数台構成の場合・全てのサーバーで実行）
```sql
SET PERSIST group_replication_start_on_boot=ON;
```

この時、`group-replication-group-seeds` で指定された、他のサーバーが生きている場合、自動でレプリケーションが始まる。

その前提が成り立ってない場合、手動で再立ち上げすることになるが、それはもうトラブルシュートの話となる。

# ＳＥＬｉｎｕｘとファイアウォールの有効化
以上で基本的なセットアップは完了したが、ＳＥＬｉｎｕｘやファイアウォールが有効化することでサービスインできる体制が整う。

その前に`/var/log/audit/audit.log`を確認すること。

```
type=AVC msg=audit(1548327681.726:594): avc:  denied  { name_connect } for  pid=10846 comm="mysqld" dest=39062 scontext=system_u:system_r:mysqld_t:s0 tcontext=system_u:object_r:ephemeral_port_t:s0 tclass=tcp_socket permissive=1
```

上記のように `tcontext=system_u:object_r:ephemeral_port_t:s0` と表示される場合は、semanage の設定が有効になっていない。

```
$ sudo semanage port -lC
SELinux Port Type              Proto    Port Number
mysqld_port_t                  tcp      33061
```

自分が検証した（ちょっと古い）環境では、上記のように設定を確認しても本現象が出ることがありました。
最新の環境ではそこまでチェックできてれば問題無く動くので、今はエンフォース運用に移行しています。
なお、ファイアウォールサイドでは問題になっていません。

上記問題が解決したならば、下記コマンドを実行するようにしましょう。

```shell
sudo setenforce 1
sudo systemctl start firewalld
```

# 動作検証
以下の手順で再起動を実施、無事レプリケーションが回復することを確認する。

## 一台ずつ止めて立ち上げる
```shell
systemctl stop mysqld
mysql --login-path=root -e "SELECT * FROM performance_schema.replication_group_members"
systemctl start mysqld
```

## 一台ずつ再起動する
```shell
systemctl reboot
mysql --login-path=root -e "SELECT * FROM performance_schema.replication_group_members"
```

# よくある質問とその答え

## Ｑ．`mysql_secure_installation`コマンドは実行しなくていいのですか？
Ａ．[Securing the MySQL Installation (for MySQL 5.6 only)](https://dev.mysql.com/doc/mysql-yum-repo-quick-guide/en/#repo-securing-mysql-installation)によれば、実行不要とのこと（`Do not run mysql_secure_installtion`のくだり）。

## Ｑ．`mysqld_safe`コマンドで立ち上げなくていいの？
Ａ．[不要](https://dev.mysql.com/doc/refman/8.0/en/using-systemd.html)です。
最近のＲＨＥＬのパッケージシステムでは `mysqld_safe` がインストールされません。

## Ｑ．`mysqladmin password`コマンドで`root`パスワードを変更しないのですか？
Ａ．`mysql`コマンドにある`--connect-expired-password`オプションが`mysqladmin`に無いため、最初の接続ができません。

またレプリケーション実施前に`SET SQL_LOG_BIN=0;` ～ `SET SQL_LOG_BIN=1;`でカバーしない変更は、レプリケーションログ（ＢＩＮＬＯＧ）がパラレルワールド化されるので、２台目以降のレプリケーションに失敗します（同期できない）。

長らく[Deploying Instances for Group Replication](https://dev.mysql.com/doc/refman/8.0/en/group-replication-getting-started-deploying-instances.html)の手順を参考にセットアップを行っていたのですが、どうにも上手くいかず。また一時パスワードを変更しないと、何もできないことから、これをしないで済む方法を試行錯誤の結果、先の保護が必要だ、という結論に達しました。

## Ｑ．バリデートパスワード機能を無効化するとはふてぇ野郎だ。何のつもりだ？
【ポリティカルコレクトネス的要請により手順を無効化から設定を緩くする方向に変更】

Ａ．おっしゃる通り、自分は`validate_password.policy=LOW`にしたかっただけだったのですが、[マニュアル](https://dev.mysql.com/doc/refman/8.0/en/validate-password-options-variables.html)によると、従来のやり方（プラグイン・`my.cnf`に設定可能）と新しいやり方（コンポーネント・ＳＱＬ文を実行しないと行けない）が

* 同時に書いてある、
* 設定してる内容は同じ、

でも機能しない（エラーになる）というトラブルを抱え込んで、解決するまでに時間がかかった結果、気がついた瞬間にキレて無効にしたというものです:-)。

## Ｑ．下記のメッセージが表示されてサーバーが起動しません！
```plaintext
[ERROR] [MY-012961] [InnoDB] Only one log file found
[ERROR] [MY-012930] [InnoDB] Plugin initialization aborted with error not found.
[ERROR] [MY-010334] [Server] Failed to initialize DD Storage Engine
[ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
[ERROR] [MY-010119] [Server] Aborting
```

８．０．１４以降で `innodb-dedicated-server = TRUE` の時、搭載メモリが２ＧＢ以上３ＧＢ未満のサーバーでこの問題が起きます。

上記条件のサーバーでは `innodb-buffer-pool-size` が１ＧＢ以上１．５ＧＢ未満に設定されるのですが、この結果を受けて `innodb-log-files-in-group` が`1`に設定されます[^備考６]。

最小値である`2`であるべきですが、処理上、この訂正がなされないため起動しません。

このバグですが、[プルリク](https://github.com/mysql/mysql-server/pull/254)したのですが、バグレポートアカウント無いと受け付けてくれないみたいなので、修正されていません。誰かフィードバックよろしく。

## Ｑ．下記のメッセージが表示されてレプリケーションが始まり（`RECOVERING`状態のまま変化しない）ません！
```plaintext
[ERROR] [MY-010584] [Repl] Slave I/O for channel 'group_replication_recovery': error connecting to master 'replication@マスターサーバー:3306' - retry-time: 60  retries: 1, Error_code: MY-002061
[ERROR] [MY-011582] [Repl] Plugin group_replication reported: 'There was an error when connecting to the donor server. Please check that group_replication_recovery channel credentials and all MEMBER_HOST column values of performance_schema.replication_group_members table are correct and DNS resolvable.'
[ERROR] [MY-011583] [Repl] Plugin group_replication reported: 'For details please check performance_schema.replication_connection_status table and error log messages of Slave I/O for channel group_replication_recovery.'
```

```plaintext
mysql> SELECT * FROM  performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST       | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 1afe7555-1fc4-11e9-8959-005056a18228 | gr1.example.jp    |        3306 | ONLINE       | PRIMARY     | 8.0.14         |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
```

本ケースを参考に丹念にセットアップを行った場合は問題ありません。
既に過去の手順ではありますが、わざと `group-replication-recovery-get-public-key=OFF`（デフォルト設定）、`group-replication-recovery-use-ssl=OFF`（デフォルト設定）とした場合に発生しえます。

これは `caching_sha2_password` の場合、認証フェーズにおいてサーバーの公開鍵を必要としますが、ＳＳＬ通信の場合はＳＳＬ証明書から、非ＳＳＬ通信の場合はサーバーが持つ公開鍵を別途必要とします。

よって「正規の」解決方法は

* ★レプリケーション時の通信をＳＳＬ化するか
* サーバーの公開鍵を取得・保存の上、指定するか
* そもそも `mysql_native_password` にするか
* サーバーから公開鍵をもらう[^備考７]

の４択になります。

`mysql_native_password`は敗北なのと、サーバーの公開鍵を取得・保存の方法はわからなかったので、ＳＳＬ通信することで回避します[^備考８]。
詳細は [Using Group Replication and the Caching SHA-2 User Credentials Plugin](https://dev.mysql.com/doc/refman/8.0/en/group-replication-user-credentials.html#group-replication-caching-sha2-user-credentials) に書いてあるのですが、全てのサーバーで、全てのサーバーの公開鍵の取得が必要なんじゃあ…とか、取得したらどうファイルに入れ込むのか…といった点が理解できず、解決には時間がかかりました。

## Ｑ．下記のメッセージが表示されてレプリケーションが始まりません！
```plaintext
[ERROR] [MY-011526] [Repl] Plugin group_replication reported: 'This member has more executed transactions than those present in the group. Local transactions: 1afe7555-1fc4-11e9-8959-005056a18228:1 > Group transactions: 1ac6f26d-1fc4-11e9-b5d9-005056a190c6:1,
[ERROR] [MY-011522] [Repl] Plugin group_replication reported: 'The member contains transactions not present in the group. The member will now exit the group.'
```

Ａ．`SET SQL_LOG_BIN=0;`せずに`root`のパスワードを変更すると発生します。このエラーからＢＩＮＬＯＧの不一致を疑っています。ただし詳細は追いかけていません。

## Ｑ．下記のメッセージが表示されてレプリケーションが回復（`RECOVERING`のまま）しません！
```plaintext
[ERROR] [MY-010584] [Repl] Slave I/O for channel 'group_replication_recovery': error connecting to master 'replication@その時のマスターサーバー:3306' - retry-time: 60  retries: 1, Error_code: MY-001045
[ERROR] [MY-011582] [Repl] Plugin group_replication reported: 'There was an error when connecting to the donor server. Please check that group_replication_recovery channel credentials and all MEMBER_HOST column values of performance_schema.replication_group_members table are correct and DNS resolvable.'
```

```plaintext
mysql> SELECT * FROM  performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST       | MEMBER_PORT | MEMBER_STATE | MEMBER_ROLE | MEMBER_VERSION |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
| group_replication_applier | 1ac6f26d-1fc4-11e9-b5d9-005056a190c6 | gr2.example.jp    |        3306 | RECOVERING   | SECONDARY   | 8.0.14         |
| group_replication_applier | 1afe7555-1fc4-11e9-8959-005056a18228 | gr1.example.jp    |        3306 | ONLINE       | PRIMARY     | 8.0.14         |
+---------------------------+--------------------------------------+-------------------+-------------+--------------+-------------+----------------+
```

Ａ．諦めろん。マスターとスレーブでズレが発生しました。マスターからダンプしてスレーブを設定し直しましょう。

検証した限りでは、設定のバックエンドがＤＢ（ユーザーやコンポーネント、プラグイン等）で、サーバー固有の設定（INSTALL COMPONENT 等）を行うと、ＢＩＮＬＯＧがパラレルワールド化してしまうのではないかと想像しています。ただし詳細は追いかけていないです。

## Ｑ．下記の警告がでますが大丈夫ですか？
```plaintext
[Warning] [MY-013168] [InnoDB] Cannot upgrade server earlier than 5.7 to 8.0 Option innodb_dedicated_server is ignored for innodb_log_files_in_group because innodb_log_files_in_group=2 is specified explicitly.
```

```plaintext
[Warning] [MY-010897] [Repl] Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
```

Ａ．とりあえず「大丈夫だ、問題無い」ことにしておく。

* 前者は `innodb-log-files-in-group` 指定をしないと起動しないので諦めるしか。
* 後者は厳密にはパスワードレスな認証の仕組みを導入する必要があるが、ＳＳＬ通信化で今は問題発生しないと思う。

## Ｑ．グループレプリケーションサーバーを追加しようと思ってるのですが、「`replication`」ユーザーのパスワードを忘れてしまいました。どうすればいいのでしょうか。
Ａ．「`replication`」ユーザーのパスワードは忘れてしまってかまわないのですが、その場合、全てのサーバーでパスワード変更（`ALTER USER`）の上、`CHANGE MASTER TO` 文を発行する必要がある。

```sql
SET SQL_LOG_BIN=0;
ALTER USER 'replication'@'%' IDENTIFIED WITH caching_sha2_password BY '新しいパスワード';
SET SQL_LOG_BIN=1;
CHANGE MASTER TO MASTER_USER='replication', MASTER_PASSWORD='新しいパスワード' FOR CHANNEL 'group_replication_recovery';
```

上記実行自体はレプリケーション中の「レプリケーション動作」に影響受けないが、再接続時および追加予定のサーバーでは本設定が参照（認証）される。

## Ｑ．ＳＳＬ証明書関連のくだりが面倒なのですが。
Ａ．Ｓｙｓｔｅｍｄスクリプトの内容を追っていくと、`/usr/bin/mysqld_pre_systemd` にてＭｙＳＱＬを初期化（`--initialize`）してるので、その内容を自前で解決する作戦に出る手はあります。
[Managing MySQL Server with systemd](https://dev.mysql.com/doc/refman/8.0/en/using-systemd.html) を読むと実はそう難しくないのかな、と思いつつ、未検証で。

# 参考文献
* [A Quick Guide to Using the MySQL Yum Repository](https://dev.mysql.com/doc/mysql-yum-repo-quick-guide/en/)
* [MySQL 8.0.4 : New Default Authentication Plugin : caching_sha2_password](https://mysqlserverteam.com/mysql-8-0-4-new-default-authentication-plugin-caching_sha2_password/)
* [Managing MySQL Server with systemd](https://dev.mysql.com/doc/refman/8.0/en/using-systemd.html)
* [MySQL8.0で新たに追加されているレプリケーション接続オプション](https://www.s-style.co.jp/blog/2020/03/5861/)
 
[^備考１]: `enforcing`モードでうまく行かない場合は`permissive`モードで運用する。
[^備考２]: ネットワーク設定や時刻同期設定など。
[^備考３]: `yum-config-manager`は`yum-utils`パッケージに収納されている。
[^備考４]: `uuidgen`は`util-linux`パッケージに収納されている。
[^備考５]: 本手順では、まとめてサーバーセットアップする前提の手順のため。
[^備考６]: マニュアルにある「ＧＢ単位のバッファプールサイズのＲＯＵＮＤ」とは、ＲＯＵＮＤ（１．０）ないしはＲＯＵＮＤ（１．５未満）の計算という意味になりますが、小数点以下を四捨五入するため、１になります。
[^備考７]: 当然セキュリティ的なトレードオフがあります。マニュアルによればＭＩＴＭに弱いです。
[^備考８]: 初期セットアップ時に作成されるＳＳＬ証明書は自己署名証明書になります。