Dockerの公式MySQLイメージでデータベースをスケールアウトする

発端

Dockerにswarm modeが追加されてから、スケールアウトの難易度がいっそう下がったように思います。

ただ、公式のmysqlイメージにはスケールアウトに対応しておらず、データベースの負荷分散は未だに難易度が高いようです。

そこで、なるべく公式のmysqlのイメージを使いながらswarm modeにあった設定を行えたらと思い、調査および構築を行ってみました。

なお、mysqlのスケールアウトを行ったのは今回が初めてですので、きれいな書き方とかは全然できてません。

あとシェルスクリプトもあんまり上手に書けませんので、そのあたりはご容赦ください。。

前提

MySQLにはgaleraやmysql clusterなどの冗長化構成もあるようですが、今回はなるべくmysqlでスケールアウトを行うことを考えます。

そのため、簡単に構築できるマスター、スレーブでのレプリケーションを設定します。

マスター・スレーブのレプリケーションのストラテジは次のようになるようです。

  • マスターとスレーブを用意し、DBの変更はすべてマスターで行う
  • マスターでの変更はすべてレプリケーション専用ユーザーを通してスレーブに転送され、適用される
  • スレーブでの変更は、マスターには反映されない
  • レプリケーション設定後でも新しいユーザーやデータベースを作成でき、それらは正しくスレーブに転送される

今回はマスターが1台、スレーブがスケールアウト可能となる構成を行います。

設定

docker-stack.yml

environmentとvolumesの値は適宜設定してください。

複数ノードの場合はマスターのデータボリュームはnfsで共有するなど、どこからでも参照できるようにしておくといいと思います。

また、設定ファイルはすべてのノードから参照できるようにしておいてください。

version: '3'

services:
  master:
    image: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_DATABASE=
      - MYSQL_USER=repl
      - MYSQL_PASSWORD=
    volumes:
      - /path/to/data:/var/lib/mysql
      - /path/to/set_master.sql:/docker-entrypoint-initdb.d/set_master.sql
    command: mysqld --server-id=101 --log-bin=/var/lib/mysql/mysql-bin.log
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == worker

  slave:
    image: mysql
    environment:
      - MYSQL_ROOT_PASSWORD=
      - MYSQL_DATABASE=
      - MYSQL_USER=repl
      - MYSQL_PASSWORD=
    volumes:
      - /path/to/docker-entrypoint.sh:/usr/local/bin/docker-entrypoint.sh
      - /path/to/set_slave.sh:/docker-entrypoint-initdb.d/set_slave.sh
    deploy:
      mode: global
      placement:
        constraints:
          - node.role == worker

追加で必要なファイルは

  • set_master.sql
  • docker-entrypoint.sh(デフォルトのものを上書きします)
  • set_slave.sh

となります。

set_master.sql

GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

docker-entrypoint.sh

このファイルは、先頭にちょっとだけ追加するだけです。

公式イメージからdocker-entrypoint.shを取ってきて、先頭を以下のように修正します。

#!/bin/bash
set -eo pipefail
shopt -s nullglob

# 〜 ここから追加 〜
# set server_id
IPADDR=`cat /etc/hosts | grep -o '10.0.[0-9]*.[0-9]*' | cut -d . -f 4`
SERVER_ID=`expr 200 + ${IPADDR}`
if [ ! -f /etc/mysql/conf.d/server_id.cnf ]; then
    touch /etc/mysql/conf.d/server_id.cnf
    echo "[mysqld]" > /etc/mysql/conf.d/server_id.cnf
    echo "server-id=${SERVER_ID}" >> /etc/mysql/conf.d/server_id.cnf
    echo "read_only" >> /etc/mysql/conf.d/server_id.cnf
fi
# 〜 追加ここまで 〜

# if command starts with an option, prepend mysqld

set_slave.sh

最初のMYSQL_PWDと最後の方のMASTER_PASSWORDを適切に設定してください。

#!/bin/bash
export MYSQL_PWD=
mysqldump -h master -u root \
--all-databases \
--events \
--single-transaction \
--flush-logs \
--master-data=2 \
--hex-blob \
--default-character-set=utf8 | mysql -u root
LOG=`mysql -s -u root -h master -e "show master status" | tail -n 1 | cut -f 1`
POS=`mysql -s -u root -h master -e "show master status" | tail -n 1 | cut -f 2`
mysql -u root -e "CHANGE MASTER TO MASTER_HOST='master', MASTER_PORT=3306, MASTER_USER='repl', MASTER_PASSWORD='', MASTER_LOG_FILE='${LOG}', MASTER_LOG_POS=${POS};"
mysql -u root -e "start slave;"

こちらには、

$ chmod a+x set_slave.sh

して実行可能にしておいてください。

実行

適切に設定できたら、docker stack deployでデプロイを行います。

docker stack deploy --compose-file ./docker-stack.yml db

確認してみます。

$ docker stack ps db
ID            NAME                                      IMAGE         NODE       DESIRED STATE  CURRENT STATE              ERROR  PORTS
XXXXXXXXXXXX  db_slave.XXXX  mysql:latest  node1  Running        Running about an hour ago
XXXXXXXXXXXX  db_slave.XXXX  mysql:latest  node2  Running        Running about an hour ago
XXXXXXXXXXXX  db_master.1                         mysql:latest  node1  Running        Running about an hour ago

スレーブで SHOW SLAVE STATUS\G などを実行したりマスターにデータを登録したりして、スレーブが適切に動いているかを確認してください。

ポイント

今回最も苦労した点は、各サーバーにserver_idを設定する部分でした。

サーバーIDは、マスターは固定ですが、スレーブは200にIPアドレスの最下位の値を足しています。

この値はコンテナが実行されてから初めて設定可能になるので、volumesでcnfファイルを指定することはできません。

また、composeのcommandディレクティブで環境変数の展開も試しましたが、どうやらコンテナ起動時のスクリプトではcommandで指定した環境変数を展開してくれず、うまく行きませんでした。

そこで、いっそのことコンテナ起動時に実行されるdocker-entrypoint.shに設定ファイルを動的に作成するスクリプトを仕込もうというのが、今回行っている対応策です。

docker-stack.ymlでは、volumesにdocker-compose.shを上書きする記述を行っています。

あとがき

正直このやり方が良いとは到底思えませんし、シェルスクリプトも試行錯誤で書いたので改善の余地ありありですね・・・

改善点やアドバイスなどあればぜひコメントお願い致します。。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする