Posted at

MySQL の準同期レプリケーション + マルチスレッドレプリケーションを ansible で組む

More than 1 year has passed since last update.

MySQL 5.6 から入った以下の2つの機能を試すための環境構築を自動化するために ansible で書いた。


  • 準同期レプリケーション

  • マルチスレッドレプリケーション

CentOS7 向けに書いてるので、CentOS7 が予め入った環境に site.yml を流すことで出来るだろう。なお、Ansible 2.3.2 を使ったが、もしかしたらもうちょっと低いバージョンでもいけるかもしれない。

# 必要に応じて --user と --private-key を入れる

ansible-playbook -i inventories/mysql site.yml

以下 ansible のコードを解説付きで記述する。


site.yml

MySQL ホストに MySQL ロールを流す。inventories/mysql の中身に依存しているので変更に弱い問題がある。


site.yml

---

- hosts: mysql
roles:
- mysql
become: yes
serial: 1


group_vars/all.yml

内部 IP とレプリカのマスター IP のマッピング情報を格納する。


group_vars/all.yml

internal_ip_map:

mysql-01: "(マスターとなる内部の IP に差し替えること)"
mysql-02: "(レプリカとなる内部の IP に差し替えること)"

replication_master_map:
mysql-02: "(マスターとなる内部の IP に差し替えること)"



inventories/mysql

ホスト名 (mysql-01mysql-02)は適当につけてるので、変更してよいが、[mysql-master][mysql-replica] の名前部分、[mysql:children] の名前部分及びその中身はいじらないこと(動かなくなる)。また、ansible_host の部分の書き換えもお忘れなく。


inventories/mysql

mysql-01 mysql_server_id=1 ansible_host=(マスターとなる IP アドレスに差し替えること)

mysql-02 mysql_server_id=2 ansible_host=(レプリカとなる IP アドレスに差し替えること)

[mysql-master]
mysql-01

[mysql-replica]
mysql-02

[mysql:children]
mysql-master
mysql-replica



roles/mysql/defaults/main.yml

パスワードはここではべた書きしてるが、本来は Ansible Vault 使って外部化するべきである。


roles/mysql/defaults/main.yml

mysql_user_uid: 10000

mysql_user_gid: 10000

mysql_root_password: root
mysql_repl_password: repl



roles/mysql/handlers/main.yml

systemd を使って MySQL の再起動を行う


roles/mysql/handlers/main.yml

---

- name: restart mysql
systemd:
name: mysqld.service
state: restarted
daemon_reload: yes


roles/mysql/tasks/main.yml

メインとなる部分。これは以下のフローで MySQL サーバを構築する。


  • MySQL ユーザとグループの作成

  • yum レポジトリ経由で MySQL 5.7 系の最新版のクライアント及びサーバをインストール


    • 最初から内蔵されてる MariaDB とのパッケージ衝突対策のため予め MariaDB のライブラリを削除する



  • MySQL のデータディレクトリを確認し、初期化されてなければ初期化する


    • パッケージデフォルトの/var/lib/mysql 直指定



  • サーバ側の my.cnf デプロイ

  • MySQL サーバ起動

  • root パスワード変更

  • クライアント側の my.cnf デプロイ

  • マスター及びレプリカの処理に分岐


roles/mysql/tasks/main.yml

---

- name: create mysql group
group:
name: mysql
gid: "{{ mysql_user_gid }}"
state: present

- name: create mysql user
user:
name: mysql
uid: "{{ mysql_user_uid }}"
group: mysql
shell: /bin/false
state: present

- name: fetch yum repository
yum:
name: https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

- name: remove mariadb library due to confliction
yum:
name: mariadb-libs
state: absent

- name: install mysql packages
yum:
name: "{{ item }}"
state: installed
with_items:
- mysql-community-client
- mysql-community-libs
- mysql-community-libs-compat
- mysql-community-server
- MySQL-python

- name: check mysql database is initialized
find:
paths: /var/lib/mysql
register: mysql_database_directory

- name: set initialization state
set_fact:
mysql_has_been_initialized: "{{ mysql_database_directory.files | length > 0 }}"

- name: deploy server my.cnf
template:
src: server-my.cnf.j2
dest: /etc/my.cnf
mode: 0600
owner: mysql
group: mysql
notify: restart mysql

- name: ensure mysql database initialized
shell: mysqld --user=mysql --initialize-insecure
when: 'not mysql_has_been_initialized'

- name: start mysql server
systemd:
name: mysqld.service
state: started
daemon_reload: yes

- name: change root user password
mysql_user:
name: root
password: "{{ mysql_root_password }}"
state: present
login_host: localhost
login_user: root
host_all: yes

- name: deploy client my.cnf for current user
template:
src: client-my.cnf.j2
dest: "/home/{{ ansible_user }}/.my.cnf"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: 0600

- name: deploy client my.cnf for root user
template:
src: client-my.cnf.j2
dest: "/root/.my.cnf"
owner: root
group: root
mode: 0600

- include: master.yml
when: '"mysql-master" in group_names'

- include: replica.yml
when: '"mysql-replica" in group_names'



roles/mysql/tasks/master.yml

マスター側は以下の処理を行う


  • レプリケーションユーザの作成

  • 匿名ユーザとテスト用データベースの削除


roles/mysql/tasks/master.yml

---

- name: create replication user
mysql_user:
name: repl
password: "{{ mysql_repl_password }}"
host: "{{ internal_ip_map[item] }}"
priv: '*.*:SUPER,REPLICATION SLAVE'
state: present
with_items:
- "{{ groups['mysql-replica'] }}"

- name: remove anonymous mysql user
mysql_user:
name: ""
state: absent
host_all: yes

- name: remove test database
mysql_db:
name: test
state: absent



roles/mysql/tasks/replica.yml

レプリカ(スレーブとも)側は以下の処理を行う


  • レプリケーション停止

  • マスターのバイナリログのファイル名と位置を保存

  • 上記の情報から CHANGE MASTER を発行

  • レプリケーション再開


roles/mysql/tasks/replica.yml

---

- name: stop replication
mysql_replication:
mode: stopslave

- name: get replication master status
mysql_replication:
mode: getmaster
login_host: "{{ replication_master_map[inventory_hostname] }}"
login_user: repl
login_password: "{{ mysql_repl_password }}"
register: replication_master_info

- name: change replication master
mysql_replication:
mode: changemaster
master_host: "{{ replication_master_map[inventory_hostname] }}"
master_log_file: "{{ replication_master_info.File }}"
master_log_pos: "{{ replication_master_info.Position }}"
master_user: repl
master_password: "{{ mysql_repl_password }}"

- name: start replication
mysql_replication:
mode: startslave



roles/mysql/templates/client-my.cnf.j2

root ユーザでログインするための機密情報を格納している


roles/mysql/templates/client-my.cnf.j2

# {{ ansible_managed }}


[client]
user = root
password = {{ mysql_root_password }}


roles/mysql/templates/server-my.cnf.j2

今回のキモとなる部分。

準同期レプリケーションのために以下の項目を入れている。http://qiita.com/fetaro/items/f56c3f4e5efc66692705 を参考にした。


  • plugin_load = "rpl_semi_sync_master=semisync_master.so"


  • rpl_semi_sync_master_enabled


    • マスターでの準同期レプリケーションを有効かつ plugin_load により構築時から有効にする



  • plugin_load = "rpl_semi_sync_slave=semisync_slave.so"


  • rpl_semi_sync_slave_enabled


    • レプリカでの準同期レプリケーションを有効かつ plugin_load により構築時から有効にする



マルチスレッドレプリケーションのために以下の項目を入れている。マルチスレッドレプリケーションについての情報は http://qiita.com/smallpalace/items/e9fd924078b663d7616b がまとまってる。



  • slave_parallel_workers


    • マルチスレッドレプリケーションが有効になる



  • slave_parallel_type

  • slave_preserve_commit_order

  • log_slave_updates

  • gtid_mode

あくまで準同期レプリケーションとマルチスレッドレプリケーションを有効にするための設定なので特にチューニングしていない


roles/mysql/templates/server-my.cnf.j2

# {{ ansible_managed }}


[mysqld]
server_id = {{ mysql_server_id }}
gtid_mode = on
enforce-gtid-consistency = on
skip_name_resolve = on
symbolic_links = 0

{% if 'mysql-master' in group_names %}
# replication master
log_bin = mysql-bin
expire_logs_days = 7
binlog_format = ROW
master_info_repository = TABLE

# semi-sync replication
plugin_load = "rpl_semi_sync_master=semisync_master.so"
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 10000
{% elif 'mysql-replica' in group_names %}
# replication replica
read_only = on
relay_log = relay-bin
relay_log_index = relay-bin.index
relay_log_info_repository = TABLE
relay_log_recovery = on

# semi-sync replication
plugin_load = "rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled = 1

# multi-threaded replication
slave_parallel_workers = 2
slave_parallel_type = LOGICAL_CLOCK
slave_preserve_commit_order = on
log_slave_updates = on
{% endif %}

# innodb
innodb_large_prefix = on
innodb_file_per_table = on

# performance schema
performance_schema = on