Ansible で MySQL のレプリケーションを設定してみたのでまとめておく。思いのほか苦戦した。
前提
今回は以下のような条件で MySQL のレプリケーションを設定することを目的とする。
- OS はマスタ、スレーブともに Ubuntu Server 12.04.x を使う
- 1台のマスタと、1台以上のスレーブを設定する
- 途中からでもスレーブを追加できる
- スレーブでレプリケーションが停止していたら、マスタと再同期して再開させる
- そのためにマスタを停止はしない
my.cnf
のreplicate-ignore-table
に含まれるテーブルは同期から除外する
- root パスワードはホストごとに自動生成する
server-id
も自動生成する- DB やユーザの作成は含めない
完成品
できあがったものがこちらでーす。
- akagisho/mysql-replication-ansible – GitHub
Vagrantfile
を置いてあるので、vagrant up
するだけで手軽に試せるようになっている。
ファイル構成
各ファイルは以下のように Best Practices っぽく配置している。
$ tree -a
.
├── group_vars
│ ├── all
│ ├── master
│ └── slave
├── roles
│ └── mysql-repl
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ │ └── usr
│ │ └── local
│ │ └── bin
│ │ └── mysqlrepldump.sh
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ ├── etc
│ │ └── mysql
│ │ └── my.cnf.j2
│ └── root
│ └── .my.cnf.j2
├── site.yml
└── tmp
vars, inventory ファイル
以降で説明する tasks で使う、vars ファイルや inventory ファイルを作成しておく。
group_vars/all
:
replicate_databases:
- レプリケーション対象の DB のリスト
- ...
replicate_ignore_tables:
- レプリケーションから除外するテーブルのリスト
- ...
mysql_repl_master: (マスタサーバの IP アドレス)
mysql_repl_net: (スレーブサーバのネットワークレンジ)
group_vars/master
:
mysql_repl_role: master
group_vars/slave
:
mysql_repl_role: slave
roles/mysql-repl/defaults/main.yml
:
mysql_repl_user: mysql_repl
mysql_repl_pass: (レプリケーション用ユーザのパスワード)
インベントリファイル hosts
:
[master]
(マスタのホスト名 or IP アドレス)
[slave]
(スレーブのホスト名 or IP アドレス 1つめ)
(スレーブのホスト名 or IP アドレス 2つめ)
...
tasks
Playbook roles/mysql-repl/tasks/main.yml
の記述内容について順に説明していく。
パッケージのインストール
必要なパッケージ群をインストールする。
- name: install depences
apt: pkg={{ item }}
with_items:
- mysql-server
- libmysqlclient-dev
- python-pip
- python-dev
- name: install pip modules
pip: name={{ item }}
with_items:
- mysql-python
root パスワードの設定
ランダムな root パスワードを shell
で生成 (2回目以降は .my.cnf
から読取り) し、register
に格納する。
- name: get mysql root password
shell:
test -f /root/.my.cnf
&& (grep ^password /root/.my.cnf | head -1 | awk '{print $3}')
|| (cat /dev/urandom | tr -dc "[:alnum:]" | head -c 32)
register: mysql_root_pass
生成したパスワードを MySQL に設定し、その後 .my.cnf
に保存する。
- name: set mysql root password
mysql_user:
name=root
host={{ item }}
password={{ mysql_root_pass.stdout }}
with_items:
- "{{ ansible_hostname }}"
- localhost
- 127.0.0.1
- ::1
- name: put .my.cnf.j2
template:
src=root/.my.cnf.j2
dest=/root/.my.cnf
mode=600
roles/mysql-repl/templates/root/.my.cnf.j2
:
[client]
user = root
password = {{ mysql_root_pass.stdout }}
[mysqladmin]
user = root
password = {{ mysql_root_pass.stdout }}
レプリケーション用ユーザの作成
レプリケーション用のユーザを作成する。
- name: create replication user
mysql_user:
name={{ mysql_repl_user }}
password={{ mysql_repl_pass }}
host={{ mysql_repl_host }}
priv=*.*:"REPLICATION SLAVE"
when: mysql_repl_role == "master"
server-id の設定
server-id
を設定する。この値はホストごとに一意でなければならないので、IP アドレスを基にして自動生成する。
- name: generate server-id
shell:
hostname -I | sed -e 's/ /n/' | grep -v '^$'
| tail -1 | awk -F. '{print $3 * 256 + $4}'
register: mysql_server_id
生成した server-id
を my.cnf
に反映する。
- name: put my.cnf
template:
src=etc/mysql/my.cnf.j2
dest=/etc/mysql/my.cnf
register: last_result
テンプレートファイル roles/mysql-repl/templates/etc/mysql/my.cnf.j2
は、デフォルトの my.cnf
に以下の記述を追加する。
server-id = {{ mysql_server_id.stdout }}
log-bin = mysql-bin
{% for replicate_database in replicate_databases -%}
binlog-do-db = {{ replicate_database }}
{% endfor -%}
{% for replicate_ignore_table in replicate_ignore_tables -%}
replicate-ignore-table = {{ replicate_ignore_table }}
{% endfor -%}
my.cnf
を変更したあとは MySQL を再起動する。ここは notify
, handler
を使いたいところだが、全ての tasks が終わったあとではなく即座に実行する必要があるので、大人しく task で済ます。
- name: restart mysql
service:
name=mysql
state=restarted
when: last_result.changed
マスタのダンプ
「途中からスレーブを追加できる」という要件を満たすために、mysqldump
コマンドで、マスタのデータをダンプしてスレーブに持っていく。ただし、対象の DB のみをダンプしたり replicate-ignore-table
を除外することを考えると一筋縄ではいかないので、次のようなラッパスクリプトを作る。
このとき mysqldump
の --master-data=1
オプションを使い、マスタの MASTER_LOG_FILE
と MASTER_LOG_POS
を出力しておく。これらの値は mysql_replication
の mode=getmaster
でも取得できるが、ダンプしてから取得するまでに値が変わっているかも知れないので、今回は使わない。
roles/mysql-repl/files/usr/local/bin/mysqlrepldump.sh
:
#!/bin/sh
set -e
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
CONF=/etc/mysql/my.cnf
CMD=mysqldump
ARG="$@ --single-transaction --master-data=2 --databases"
for DB in `grep ^binlog-do-db $CONF | awk '{print $3}'`; do
ARG="$ARG $DB"
done
for IGNORE_TABLE in `grep ^replicate-ignore-table $CONF | awk '{print $3}'`; do
ARG="$ARG --ignore-table=$IGNORE_TABLE"
done
$CMD $ARG
作ったスクリプトを配置する。
- name: put mysqlrepldump.sh
copy:
src=usr/local/bin/mysqlrepldump.sh
dest=/usr/local/bin/mysqlrepldump.sh
mode=755
マスタでデータをダンプし、一旦コントロールノードを経由して、スレーブにコピーする。
- name: export dump file
shell:
mysqlrepldump.sh | gzip > /tmp/mysqldump.sql.gz
when: mysql_repl_role == "master"
- name: fetch dump file
fetch:
src=/tmp/mysqldump.sql.gz
dest=tmp/mysqldump.sql.gz
flat=yes
when: mysql_repl_role == "master"
- name: put dump file
copy:
src=tmp/mysqldump.sql.gz
dest=/tmp/mysqldump.sql.gz
when: mysql_repl_role == "slave"
スレーブの設定状況を確認する。設定されていない場合は Ansible がエラーになるが、処理を続行するために ignore_errors
を使う。
- name: check status of slaves
mysql_replication: mode=getslave
ignore_errors: true
register: slave_status
when: mysql_repl_role == "slave"
スレーブでレプリケーションが動いていなければ、コピーしてきたダンプファイルをインポートする。
- name: stop replication
mysql_replication: mode=stopslave
when: mysql_repl_role == "slave" and (slave_status|failed or slave_status.Slave_SQL_Running != "Yes")
- name: import dump file
shell:
zcat /tmp/mysqldump.sql.gz | mysql
when: mysql_repl_role == "slave" and (slave_status|failed or slave_status.Slave_SQL_Running != "Yes")
レプリケーションの開始
やっとこさレプリケーションを開始できる。mysql_replication
モジュールの mode=changemaster
を使う。
- name: set replication
mysql_replication:
mode=changemaster
master_host={{ mysql_repl_master }}
master_user={{ mysql_repl_user }}
master_password={{ mysql_repl_pass }}
when: mysql_repl_role == "slave" and (slave_status|failed or slave_status.Slave_SQL_Running != "Yes")
以上でレプリケーションを設定できた。
参考ページ
- bennojoy/ansible-roles/mysql – GitHub
- MySQL の server-id の振り方 – blog.nomadscafe.jp
コメント