docker compose で構築済みの開発環境に MySQL スレーブを追加したい

やりたいこと

docker compose で構築された mysql 環境にスレーブを追加したい。 (レプリケーションの遅延に起因するバグが報告されたので、開発環境で再現したい。)

チームに展開するので、手順は可能な限り簡単にしたい。

前提

以下のような環境が存在し、それなりにデータが入っている状態とします。

docker-compose.yml

services:
    master-db:
        image: mysql:5.6
        volumes:
            - ./master/data:/var/lib/mysql
            - ./master/conf.d:/etc/mysql/conf.d
            - ./master/initdb.d:/docker-entrypoint-initdb.d
        env_file:
            - ./master/master.env

master/conf.d/my.cnf

[mysqld]
log-bin = mysql-bin
server-id = 1

マスタとするため、 log-bin と server-id の設定が必要です。 (スレーブ追加時に設定しても問題ないはず。)

master/master.env

MYSQL_ROOT_PASSWORD=password
MYSQL_DATABASE=hoge
MYSQL_USER=master
MYSQL_PASSWORD=password

mysql スレーブを追加する

なにはともあれ、 docker-compose.yml にスレーブのサービスを追加します。

docker compose up すれば普通にサービス自体は追加されますよね。

docker-compose.yml

# ...
    slave-db:
        image: mysql:5.6
        volumes:
            - ./slave/data:/var/lib/mysql
            - ./slave/conf.d:/etc/mysql/conf.d
            - ./slave/init.sh:/usr/local/bin/init.sh
        env_file:
            - ./slave/slave.env

slave/conf.d/my.cnf

[mysqld]
server-id = 11

スレーブとするため、 server-id の設定が必要です。

slave/slave.env

MASTER_HOST=master-db
MASTER_USER=root
MASTER_PASSWORD=password

MYSQL_ROOT_PASSWORD=password
MYSQL_DATABASE=hoge
MYSQL_USER=slave
MYSQL_PASSWORD=password

通常の環境変数に加えて、マスタの情報である MASTER_* を記述します。

slave/init.sh

#!/bin/sh

mysqldump -h $MASTER_HOST -u $MASTER_USER -p$MASTER_PASSWORD --master-data --all-databases > /tmp/master.dump

until mysql -u root -p$MYSQL_ROOT_PASSWORD -e ''; do
  echo -n .
  sleep 1
done

mysql -u root -p$MYSQL_ROOT_PASSWORD -e 'STOP SLAVE'
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "CHANGE MASTER TO MASTER_HOST = '$MASTER_HOST', MASTER_USER = '$MASTER_USER', MASTER_PASSWORD = '$MASTER_PASSWORD'"
mysql -u root -p$MYSQL_ROOT_PASSWORD < /tmp/master.dump
mysql -u root -p$MYSQL_ROOT_PASSWORD -e 'START SLAVE'

rm /tmp/master.dump

docker compose up で環境を立ち上げたあと、 docker compose exec slave-db sh /usr/local/bin/init.sh で叩きます。 ここでマスタからスレーブへデータをコピーし、レプリケーションの設定を行っています。

このスクリプトがキモになりますので、内容を順を追って説明します。

マスタからデータをダンプする

mysqldump -h $MASTER_HOST -u $MASTER_USER -p$MASTER_PASSWORD --master-data --all-databases > /tmp/master.dump

--master-data オプションでダンプする内容にバイナリログの座標を含めています。 (具体的には CHANGE MASTER TO MASTER_LOG_FILE = '...', MASTER_LOG_POS = ### が出力されます。)

MySQL :: MySQL 5.6 リファレンスマニュアル :: 17.1.1.5 mysqldump を使用したデータスナップショットの作成

スレーブにマスタ情報をセットする

mysql -u root -p$MYSQL_ROOT_PASSWORD -e 'STOP SLAVE'
mysql -u root -p$MYSQL_ROOT_PASSWORD -e "CHANGE MASTER TO MASTER_HOST = '$MASTER_HOST', MASTER_USER = '$MASTER_USER', MASTER_PASSWORD = '$MASTER_PASSWORD'"

マスタ情報を変更するため、 STOP SLAVE でスレーブを止めます。

その後、 CHANGE MASTER TO ... でマスタ情報(ホスト、ユーザ、パスワード)をセットします。

MASTER_HOST (及び MASTER_PORT)を変更するとバイナリログの座標がリセットされてしまうため、データをインポートする前に行う必要があります。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.4.2.1 CHANGE MASTER TO 構文

MASTER_HOST または MASTER_PORT オプションを指定すると、スレーブは (そのオプション値が現在の値と同じ場合でも) マスターサーバーが以前とは異なっていると見なします。この場合、マスターバイナリログファイルの名前と位置の古い値は適用されなくなったと見なされるため、このステートメントで MASTER_LOG_FILE と MASTER_LOG_POS を指定しないと、そのあとに MASTER_LOG_FILE='' と MASTER_LOG_POS=4 が暗黙のうちに付加されます。

スレーブにデータをインポートする

mysql -u root -p$MYSQL_ROOT_PASSWORD < /tmp/master.dump
mysql -u root -p$MYSQL_ROOT_PASSWORD -e 'START SLAVE'

データに CHANGE MASTER TO MASTER_LOG_FILE = '...', MASTER_LOG_POS = '...' が含まれていますので、マスターのデータがインポートされると同時にバイナリログの座標が正しくセットされます。

これでスレーブ側のセットアップができたので、 START SLAVE でスレーブを再開させます。

おわりに

これらの変更によってファイル群を git pull で更新したあと、以下の手順でスレーブを追加できるようになりました。 スレーブ・サービスで1コマンド叩くだけでセットアップできるので、それなりに上手くできたかなと思います。

docker compose up -d
docker compose exec slave-db sh /usr/local/bin/init.sh

検証に使ったコードが↓にあります。

github.com

間違いや問題がありましたらご指摘いただけると幸いです。

※ 説明を簡単にするためにサービス同士の依存関係、認証、権限周りなどは簡略化してあります。実用する際はご注意ください。

参考

MySQL :: MySQL 5.6 リファレンスマニュアル :: 17.1.1 レプリケーションのセットアップ方法