備忘録のため,内容の正当性については責任を持ちません。

Ansible で MySQL のレプリケーションを設定してみたのでまとめておく。思いのほか苦戦した。


前提

今回は以下のような条件で MySQL のレプリケーションを設定することを目的とする。

  • OS はマスタ、スレーブともに Ubuntu Server 12.04.x を使う
  • 1台のマスタと、1台以上のスレーブを設定する
  • 途中からでもスレーブを追加できる
  • スレーブでレプリケーションが停止していたら、マスタと再同期して再開させる
    • そのためにマスタを停止はしない
    • my.cnfreplicate-ignore-table に含まれるテーブルは同期から除外する
  • root パスワードはホストごとに自動生成する
  • server-id も自動生成する
  • DB やユーザの作成は含めない

完成品

できあがったものがこちらでーす。

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-idmy.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_FILEMASTER_LOG_POS を出力しておく。これらの値は mysql_replicationmode=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")

以上でレプリケーションを設定できた。

参考ページ

コメント

コメントする




CAPTCHA