MySQLのレプリケーション環境をDockerでシュッと構築する

はじめに

GMOペパボ Advent Calendar 2017の15日目の記事です。

昨日の担当は@kurotakyさんによる RubyでBancor protocolのシミュレーションをするライブラリ"Bancor"を作っています - mo-fu note でした。 ブロックチェーン技術や仮想通貨はだいぶ前から話題になっていますが、それに関連した面白そうな取り組みをしているようなので、興味のある方はぜひご覧下さい。

さて、今回はMySQLレプリケーションについて書いていこうと思います。

なぜMySQLの話なのか?

私のTwitterアカウントを見ている人ならご存知かもしれませんが、ここ数ヶ月ほぼMySQL(とカレー🍛)のことしかツイートしていないくらいにはMySQLを触っていたからです。

ということです。

少し真面目に書くと、最近某のMySQLアップグレードをするためにMySQLのことをいろいろ学んで、それらを一度整理したいと思ったからです。

今回はその整理したいことの1つである「レプリケーション」についてです。

レプリケーションの流れを何となく理解する

レプリケーションとは、1つのDBサーバ(Master)のデータを別のDBサーバ(Slave)に複製できる機能のことをいいます。

レプリケーションには非同期のものや準同期のものがありますが、今回はデフォルトで設定されている非同期のレプリケーションについて説明します。

レプリケーションの流れを何となく理解するために以下のような図を用意しました。

f:id:purple_jwl:20171215084503j:plain

まずMasterのDBに更新系クエリが実行されると、バイナリログにその情報が格納されます。これについては以前調べたもの(MySQLのバイナリログについて調べた - ぱーぽーの日々)があるので、気になる方は参照してください。

データの複製はこのバイナリログを元に行われます。

SlaveはMasterからバイナリログの差分を取得する必要があるのですが、それをやってくれるのがマスタースレッドとスレーブI/Oスレッドです。スレーブI/OスレッドがMasterに接続し、COM_BINLOG_DUMPというコマンドを使うことで、継続的にマスタースレッドがバイナリログを差分を送ってくれます。ちなみにこのコマンドを実行するにはREPLICATION SLAVE権限が必要になります。

スレーブI/Oスレッドが受け取ったバイナリログはそのままSlaveに反映されるわけではなく、一度リレーログに書き込まれます。

そしてスレーブSQLスレッドがリレーログを読み込み、そのログに含まれるクエリを実行することでデータが複製されます。

これが一連の流れになります。

Dockerで環境構築してみる

レプリケーションについて分かってきたところで、実際にDockerでシュッと環境構築できるサンプルを用意してみました。

GitHub - purple-jwl/mysql-master-slave-replication-sample

[追記(2017/12/16)]
サンプルはGTIDを用いないレプリケーションについてですが、GTIDを用いたレプリケーション設定をGitHub上のリポジトリのみでコメントアウトで追加していますので、興味のある方はご覧下さい。
ここでは簡単な紹介のみに留めますが、MySQL5.6からGTID(Global Transaction ID)という機能が使えるようになり、これを用いてレプリケーション設定することもできるようになりました。

このサンプルではMaster、Slaveが1台ずつの構成になっていて、以下で各ファイルがやっていることを簡単に説明します。

master/my.cnf

[client]
loose-default_character_set=utf8mb4

[mysqld]
character_set_server=utf8mb4
explicit_defaults_for_timestamp=true
server_id=1
log_bin=mysql-bin
binlog_format=MIXED

雑に設定を書いています。レプリケーションで必要なバイナリログはデフォルトでは生成されないため、server_idなどを設定する必要があります。

slave/my.cnf

master/my.cnfとほぼ同じなので省略。 (server_idが異なるだけなので、もっといい感じにファイル管理できそう)

docker-compose.yml

特に触れることがないので省略。

master/setup.sh

#!/bin/bash

mysql -u root -e "GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%' IDENTIFIED BY 'repl'"

Masterのコンテナ起動時に実行させるスクリプトで、レプリケーション用ユーザーをMasterのDBに作っています。レプリケーションではSlaveからMasterのDBに接続するため、この処理が必要となります。(一応rootをレプリケーション用ユーザーとして使用することもできますが、あまりよろしくなさそう)

slave/setup.sh

#!/bin/bash

until mysqladmin ping -h master --silent; do
    sleep 1
done

log_file=`mysql -u root -h master -e 'SHOW MASTER STATUS \G' | grep File: | awk '{print $2}'`
pos=`mysql -u root -h master -e 'SHOW MASTER STATUS \G' | grep Position: | awk '{print $2}'`

mysql -u root -e "CHANGE MASTER TO MASTER_HOST='master', MASTER_USER='repl', MASTER_PASSWORD='repl', MASTER_LOG_FILE='${log_file}', MASTER_LOG_POS=${pos}"

mysql -u root -e 'START SLAVE'

Slaveのコンテナ起動時に実行させるスクリプトで、バイナリログの読み取り位置を設定し、レプリケーションの開始します。最初のsleep処理は、Masterのコンテナ上でMySQLが起動する前にレプリケーションの設定をしようとするとエラーになるので入れています。

さいごに

いかがだったでしょうか。このようにレプリケーションの設定だけなら意外と簡単にできるのが分かったと思います。

私はここ数ヶ月はMySQLな日々を送っていましたが、それまではMySQLについて学ぶ機会がほとんどありませんでした。調べてみると知らない機能や罠などが無限に出てきてとても面白いです。

あとMySQLについて学ぶとき、yoku0825さんのブログが非常に役に立っているので感謝したいです。

以上! (ちなみに今回の記事で、本当はMHAの設定までをDockerでシュッと構築してフェイルオーバーの実験などをやりたかったのですができませんでした。今後の課題にします。という宣言だけしておきます。)

明日の担当は@nyanyamiさんです。どうぞお楽しみに!


参考資料