【MySQL 8.0.22】 レプリケーション on Docker

Dockerを使ってMySQLのレプリケーション環境を構築していきたいと思います。

Dockerfileやdocker-composeを使った構築方法に関しては、結構たくさん記事があるので、
趣向を変えてコマンドだけで構築していきたいなと思います。

- 環境
    - macOS Catalina ver: 10.15.17
    - Docker Engine ver: 19.03.13

## 完成図

![スクリーンショット 2020-11-29 21.19.07のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329786/0e7cb26b-c089-1317-1ed8-952ba8f6168f.png)

## ネットワークの作成
「192.168.220.0/24」の範囲で「mysql_net」という名前のネットワークを構築します。
コマンドは以下です。

```shell
docker network create -d bridge --subnet=192.168.220.0/24 mysql_net
```

作成されたことを確認したい場合は以下のコマンドを使ってみてください。

```shell
docker network ls -f "NAME=mysql_net"
```

bridgeモードについては以下の方の記事がわかりやすいです。

[Docker network 概論 - Qiita](https://qiita.com/TsutomuNakamura/items/ed046ee21caca4a2ffd9#bridge)

## ボリュームの作成
replicationを行うのに必要なダンプを効率よく共有できるように共有するvolumeを準備します。
コマンドは以下です。

```shell
docker volume create --name shared-dump
```

作成されたことを確認したい場合は以下のコマンドを使ってみてください。

```shell
docker volume ls -f "NAME=shared-dump"
```

## コンテナの作成
今回は、現在最新バージョンである8.0.22を利用します。
以下のコマンドでイメージをpullしてください。

```shell
docker pull mysql:8.0.22
```

以下のコマンドでイメージがpullできていることを確認して下さい。

```shell
docker images mysql:8.0.22
```

MasterとSlaveのコンテナを一つずつ作成します。

```shell
# Master
docker run -d \
           -v shared-dump:/dump \
           -e MYSQL_ROOT_PASSWORD=root \
           --network mysql_net --ip 192.168.220.50 \
           --name master \
           mysql:8.0.22

# Slave
docker run -d \
           -v shared-dump:/dump \
           -e MYSQL_ROOT_PASSWORD=root \
           --network mysql_net --ip 192.168.220.100 \
           --name slave \
           mysql:8.0.22
```

MasterとSlaveによるコマンドの違いはほとんどありません。
一応、どちらがMasterでどちらがSlaveであるかをわかりやすくするためにコンテナの名前を変えています。異なる点としてはこのぐらいです。

ちょっとした検証でコンテナを立ち上げる際は、`--rm`オプションをつけて立ち上げるのが個人的に好きです（諸事情があって今回は付与できませんでしたが）。`--rm`オプションは停止と同時にコンテナを削除してくれるのでゴミが残らないところがポイントです。

## 設定ファイルを置き換える

設定ファイルの置き換えが`run`サブコマンドでできればいいんですけどね。

Dockerfileやdocker-composeだとこの辺りの処理がとても楽ですよね。
この辺りがコマンドだと辛いですねー。

というわけで、レプリケーションに必要なserver-idの設定値をそれぞれの環境に入れていきたいと思います。

```shell
# master
docker exec master bash -c "echo 'server-id = 101' >> /etc/mysql/my.cnf"

# slave
docker exec slave bash -c "echo 'server-id = 102' >> /etc/mysql/my.cnf"
```

設定を更新するためにmysqlを再起動する必要があります。
ただ、dockerを再起動するとコンテナは停止しちゃうのでコンテナの再起動が必要です。
そのため今回は`--rm`オプションが使えませんでした。

以下のコマンドを実行して再起動します。

```shell
docker restart $(docker ps -aq -f "NAME=master" -f "NAME=slave")
```

確認には以下を実行してみてください。

```shell
for role in master slave; do
  docker exec ${role} bash -c "mysql -uroot -proot -e 'select @@server_id;'"
done
```

## レプリケーション

レプリケーション以外の環境は概ねできました。

ここからはあまりdocker関係ないですが、レプリケーションの検証まで行ってみます。

### 検証用にデータベースを作成する

```shell
for role in master slave; do
  docker exec ${role} bash -c "mysql -uroot -proot -e 'create database repl;'"
done
```

### レプリケーションを開始するための準備

ここから先の作業はcommandを渡して作業すると面倒なのでコンテナに入ることにします。

```shell
# 以下のコマンドでmasterコンテナに入ります。
docker exec -it master bash

# rootユーザでmysqlに入ります。
mysql -uroot -proot
```

適当なテーブルを作成し、データを投入します。

```sql
-- 適当なデータを投入します。
create table repl.users (id int unsigned not null auto_increment, name varchar(255) not null, primary key(id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
insert into repl.users (name) values ("sample taro"), ("sample hanako");

-- slaveが接続するために必要なレプリケーションユーザを作成します
create user 'repl'@'192.168.220.100' identified with mysql_native_password by 'pQ8gDdzQHEtX@b8';
grant replication slave, replication client on *.* to 'repl'@'192.168.220.100';

-- 現在のマスターの状態を確認します。FileとPosition列の値をメモしてください
show master status;

quit
```

ダンプを取得します。

```shell
mysqldump -uroot -proot repl> /dump/repl_dump.sql
```

### レプリケーションを開始する

次はslaveコンテナでの操作となりますので、まずはコンテナに入りましょう。

```shell
docker exec -it slave bash
```

レプリケーションを開始するまでの流れとしては以下です。

1. dumpを取り込む
2. レプリケーションの設定を行う
3. レプリケーションを開始する

まずは、ダンプを取り込みます。

```shell
mysql -uroot -proot repl < /dump/repl_dump.sql
```

レプリケーションの設定を行う前にmasterと差分があることを確認しましょう(mysqlへログインしている前提です)。
masterでの作業でメモした「File」と「Position」の値を利用します。

```sql
-- replicationの設定を行います。
change master to
master_host='192.168.220.50',
master_user='repl',
master_password='pQ8gDdzQHEtX@b8',
master_log_file='binlog.000003',
master_log_pos=1518;

-- レプリケーションを開始します。
start replica;
```

`start slave`コマンドは消されるようで今後は`start replica`を使うようです。

### 同期を確認する

現在の`repl.users`テーブルの状態は以下です。

```sql
mysql> select * from repl.users;
+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
+----+---------------+
2 rows in set (0.00 sec)
```

適当なデータを投入し、master,slave両方でテーブルの状態を確認してみます。

```sql
mysql> insert into repl.users (name) values ("takeshi"), ("tanaka"), ("sasaki");
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

-- master
mysql> select @@server_id; select * from repl.users;
+-------------+
| @@server_id |
+-------------+
|         101 |
+-------------+
1 row in set (0.00 sec)

+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
|  3 | takeshi       |
|  4 | tanaka        |
|  5 | sasaki        |
+----+---------------+
5 rows in set (0.00 sec)

-- slave
mysql> select @@server_id; select * from repl.users;
+-------------+
| @@server_id |
+-------------+
|         102 |
+-------------+
1 row in set (0.00 sec)

+----+---------------+
| id | name          |
+----+---------------+
|  1 | sample taro   |
|  2 | sample hanako |
|  3 | takeshi       |
|  4 | tanaka        |
|  5 | sasaki        |
+----+---------------+
5 rows in set (0.00 sec)
```

大丈夫そうですね。

## 最後に

本来はもう少しひねりたかったのですが、普通の検証になってしまいました（すみません）。

インフラ業務では、本番により近い環境で作業を行うかどうかで品質が大きく変わってしまいます。
なので、dockerをもっとうまく使いこなせればなーと日々感じますねー。