Percona XtraDB ClusterをDockerで構築する


# はじめに
Percona XtraDB Clusterのクラスタ周りの検証をするのにDockerを使ってます。
素早く作りなおしたり、いい感じに使えてるので紹介します。

検証用として使ってください。

# 環境
今回の環境はこんな感じ。

* Percona XtraDB Cluster 5.5
* Docker 1.3.1
* (Dockerホスト) CentOS 6.5
* (Vagrantホスト) Mac OS X

# Percona XtraDB Cluster
## Percona XtraDB Clustertって？
[Percona XtraDB Cluster](http://www.percona.com/software/percona-xtradb-cluster)(PXC)は、マルチマスタでクラスタ構築が可能なMySQL互換RDBMSです。
主に[Percona社](http://www.percona.com/)がOSSとして開発しています。

基礎になっているのは[Percona Server](http://www.percona.com/software/percona-server)というオープンソースのMySQLフォークです。
PXCはPercona Serverをベースに、Codership Oy社が開発している[Galera library](http://galeracluster.com/)というマルチマスタ同期レプリケーションを実現するためのライブラリを利用しています。

同じくMySQLのフォークでGaleraを利用したものにMariaDB Galera Clusterがあります。
日本ではMariaDBのほうが有名でしょうか。

PerconaはMariaDBよりもMySQLとの互換性を重視しているため、今までMySQLを利用してた人は乗り換えやすいと思います。

## マルチマスタレプリケーション

一般的にMySQLのレプリケーションはMaster、Slave構成です。
書き込みはマスターのみで、Masterがダウンした時はFailoverが発生します。

Galeraを使ったマルチマスタ構成では、クラスタを構成する全ノードがMasterであり書き込み可能です。
（擬似）同期レプリケーションにより、ノード間をまたいだトランザクション処理も可能です。
また、全ノードがマスタなので障害時にFailoverが発生しません。

ノードの追加や削除も楽にできるので、運用が非常に楽になります。

性能面では、読み込み性能はスケールアウトにより向上しますが、書き込みはそれほど効果はありません。
あるノードへの書き込みは、同時に全ノードへ書き込むという同期レプリケーションの性質のためです。

# 構成
この記事ではPercona XtraDB Clusterのコンテナを3台立ち上げ、クラスタを構築します。
（Percona XtraDB Clusterの推奨最小ノード数が3台）

DockerホストにはCentOS 6を使います。

![20141208 PXC_docker.001.png](https://qiita-image-store.s3.amazonaws.com/0/43208/f7d2d912-7065-e4d0-38ee-8320ef416afa.png "20141208 PXC_docker.001.png")

# Dockerホスト準備
では実際に作業していきます。
まずDockerホストのCentOSをVagrantで立ち上げ、もろもろを準備します。

## Vagrant
Dockerが動けばなんでもいいですが、今回はVagrantを使います。

```bash
$ vagrant add cent65 https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box
$ mkdir ~/pxctest
$ cd ~/pxctest
$ vagrant init cent65
$ vagrant up
$ vagrant ssh
```

## Dockerインストール
ここから、CentOS上での操作になります。

まずはDockerをインストールします。

```bash
$ sudo yum update -y
$ sudo yum install -y epel-release
$ sudo yum install -y docker-io
```

Dockerを起動して、自動起動も設定しておきます。

```bash
$ sudo service docker start
$ sudo chkconfig docker on
```

vagrantユーザからDockerが使えるように、vagrantユーザをdockerグループに所属させます。

```bash
$ sudo gpasswd -a vagrant docker
```

# Dockerイメージ作成
Percona XtraDB Cluster用のイメージを作成します。
イメージはDocker Hubにもあるので、Pullすれば使えます。

https://registry.hub.docker.com/u/nownabe/test-pxc/

## git clone
起動スクリプトとか色々一式をクローンします。

```bash
$ git clone https://github.com/nownabe/docker-test_pxc.git
$ cd docker-test_pxc
```

### イメージをビルドする場合
#### SSH鍵作成
コンテナにSSH接続するための鍵ペアを作成します。

```bash
$ ssh-keygen -t rsa -N '' -f id_rsa
```

#### ビルド
```bash
$ docker build --rm=true -t nownabe/test_pxc .
```

### docker pullする場合
```bash
$ docker pull nownabe/test_pxc
```

### Dockerfile
Dockerfileはこんな感じです。

```:Dockerfile
FROM centos:centos6
MAINTAINER nownabe

RUN yum -y update
RUN echo 'root:password' | chpasswd

## Setup SSH
RUN yum install -y openssh-server
RUN sed -ri 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config
RUN mkdir -p /root/.ssh
RUN chmod 700 /root/.ssh
ADD id_rsa.pub /root/.ssh/authorized_keys
RUN chmod 600 /root/.ssh
RUN service sshd start && service sshd stop

## Setup Supervisor for SSH
RUN yum install -y python-setuptools
RUN easy_install supervisor
ADD supervisord.conf /etc/supervisord.conf

## Setup Percona XtraDB Cluster
RUN yum install -y epel-release
RUN rpm -ivh http://www.percona.com/redir/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
RUN yum install -y compat-readline5 which
RUN yum install -y Percona-XtraDB-Cluster-55
ADD my.cnf /etc/my.cnf

## Port
EXPOSE 22 3306 4444 4567 4568

CMD ["/usr/bin/supervisord"]
```

epelはPerconaインストールするときにsocatのインストールを求められるので追加します。
compat-readline5はsocatのインストールに必要でした。

whichがないとSSTでrsyncのパスを見つけられず失敗するためインストールが必要です。

検証用ということでSSHを使えるようにしています。
sshdの起動にはsupervisordを使ってます。

Docker 1.3より前の環境で作成したので、SSHでPerconaの起動をしてたりします。
1.3が入っていれば`docker exec`コマンドで事足りると思います。

# Percona XtraDB Cluster 起動
## 起動スクリプト
gitレポジトリに同梱されてる`bootstrap.sh`というスクリプトを使用します。
このスクリプトでは、

* コンテナ起動
* コンテナ内でPerconaサービス起動
	* 1台目：`--wsrep_cluster_address=gcomm://`オプションで最初のノードとして起動します
	* 2台目以降：既に起動しているコンテナのIPはわかるので、`--wsrep_cluster_address=gcomm://<起動済みコンテナのIP>`オプションでクラスタに参加します
* 全コンテナを起動したら、全コンテナの`/etc/my.cnf`の`wsrep_cluster_address`の書き換え

を行います。

## 起動
使い方は、単純に実行するだけです。ノード数などを変更する場合はスクリプトの先頭を書き換えてください。

```bash
$ sh bootstrap.sh
```

## 実行例

以下実行例です。

```bash
[vagrant@vagrant-centos65 docker-test_pxc]$ sh bootstrap.sh
==== Setup container test_pxc0 ====
* exec: docker run -d --name="test_pxc0" nownabe/test_pxc
08aaecd1590fd2851e0b3b446a00a7f5aedf040817f8603b98b38203f399640d
ID of test_pxc0: 08aaecd1590f
IP address of test_pxc0: 172.17.0.11
Waiting...
Start MySQL service in test_pxc0
* exec: ssh root@172.17.0.11 -i ./id_rsa -oStrictHostKeyChecking=no "service mysql start --wsrep_cluster_address=gcomm://"
Warning: Permanently added '172.17.0.11' (RSA) to the list of known hosts.
Starting MySQL (Percona XtraDB Cluster).. SUCCESS!
==== Finish to setup container test_pxc0 ====
==== Setup container test_pxc1 ====
* exec: docker run -d --name="test_pxc1" nownabe/test_pxc
ce8fd87cdf03ca5cf93e4686a2788672fd7dd2cc2b8fd3a82a4e3445f05a38e1
ID of test_pxc1: ce8fd87cdf03
IP address of test_pxc1: 172.17.0.12
Waiting...
Start MySQL service in test_pxc1
* exec: ssh root@172.17.0.12 -i ./id_rsa -oStrictHostKeyChecking=no "service mysql start --wsrep_cluster_address=gcomm://172.17.0.11,"
Warning: Permanently added '172.17.0.12' (RSA) to the list of known hosts.
Starting MySQL (Percona XtraDB Cluster)..State transfer in progress, setting sleep higher
. SUCCESS!
==== Finish to setup container test_pxc1 ====
==== Setup container test_pxc2 ====
* exec: docker run -d --name="test_pxc2" nownabe/test_pxc
7902bae8ed8c14e245cb39a174b168cb7e27fd6ba303eee2c8251dee5babafa2
ID of test_pxc2: 7902bae8ed8c
IP address of test_pxc2: 172.17.0.13
Waiting...
Start MySQL service in test_pxc2
* exec: ssh root@172.17.0.13 -i ./id_rsa -oStrictHostKeyChecking=no "service mysql start --wsrep_cluster_address=gcomm://172.17.0.12,172.17.0.11,"
Warning: Permanently added '172.17.0.13' (RSA) to the list of known hosts.
Starting MySQL (Percona XtraDB Cluster)..State transfer in progress, setting sleep higher
. SUCCESS!
==== Finish to setup container test_pxc2 ====
Rewrite my.cnf of 172.17.0.11
* exec: ssh root@172.17.0.11 -i ./id_rsa -oStrictHostKeyChecking=no "sed -i 's|gcomm://|gcomm://172.17.0.13,172.17.0.12,172.17.0.11,|' /etc/my.cnf"
Rewrite my.cnf of 172.17.0.12
* exec: ssh root@172.17.0.12 -i ./id_rsa -oStrictHostKeyChecking=no "sed -i 's|gcomm://|gcomm://172.17.0.13,172.17.0.12,172.17.0.11,|' /etc/my.cnf"
Rewrite my.cnf of 172.17.0.13
* exec: ssh root@172.17.0.13 -i ./id_rsa -oStrictHostKeyChecking=no "sed -i 's|gcomm://|gcomm://172.17.0.13,172.17.0.12,172.17.0.11,|' /etc/my.cnf"
```

## レプリケーション確認
実際にクラスタが組めてるか確認します。

### ステータス確認
コンテナにSSHで接続して、`show status`してみます。
SSHの接続先は、`bootstrap.sh`のログを参照してください。

```bash
$ ssh root@172.17.0.11 -i ./id_rsa
[root@08aaecd1590f ~]# mysql -uroot -e'show status like "wsrep%"'
+----------------------------+----------------------------------------------------+
| Variable_name              | Value                                              |
+----------------------------+----------------------------------------------------+
| wsrep_local_state_uuid     | 85f1c3f9-8079-11e4-8426-0ae04c8da14d               |
| wsrep_protocol_version     | 4                                                  |
| wsrep_last_committed       | 0                                                  |
| wsrep_replicated           | 0                                                  |
| wsrep_replicated_bytes     | 0                                                  |
| wsrep_received             | 10                                                 |
| wsrep_received_bytes       | 728                                                |
| wsrep_local_commits        | 0                                                  |
| wsrep_local_cert_failures  | 0                                                  |
| wsrep_local_replays        | 0                                                  |
| wsrep_local_send_queue     | 0                                                  |
| wsrep_local_send_queue_avg | 0.000000                                           |
| wsrep_local_recv_queue     | 0                                                  |
| wsrep_local_recv_queue_avg | 0.000000                                           |
| wsrep_flow_control_paused  | 0.000000                                           |
| wsrep_flow_control_sent    | 0                                                  |
| wsrep_flow_control_recv    | 0                                                  |
| wsrep_cert_deps_distance   | 0.000000                                           |
| wsrep_apply_oooe           | 0.000000                                           |
| wsrep_apply_oool           | 0.000000                                           |
| wsrep_apply_window         | 0.000000                                           |
| wsrep_commit_oooe          | 0.000000                                           |
| wsrep_commit_oool          | 0.000000                                           |
| wsrep_commit_window        | 0.000000                                           |
| wsrep_local_state          | 4                                                  |
| wsrep_local_state_comment  | Synced                                             |
| wsrep_cert_index_size      | 0                                                  |
| wsrep_causal_reads         | 0                                                  |
| wsrep_incoming_addresses   | 172.17.0.11:3306,172.17.0.12:3306,172.17.0.13:3306 |
| wsrep_cluster_conf_id      | 3                                                  |
| wsrep_cluster_size         | 3                                                  |
| wsrep_cluster_state_uuid   | 85f1c3f9-8079-11e4-8426-0ae04c8da14d               |
| wsrep_cluster_status       | Primary                                            |
| wsrep_connected            | ON                                                 |
| wsrep_local_bf_aborts      | 0                                                  |
| wsrep_local_index          | 0                                                  |
| wsrep_provider_name        | Galera                                             |
| wsrep_provider_vendor      | Codership Oy <info@codership.com>                  |
| wsrep_provider_version     | 2.11(r318911d)                                     |
| wsrep_ready                | ON                                                 |
| wsrep_thread_count         | 3                                                  |
+----------------------------+----------------------------------------------------+
```

ちゃんと3台でクラスタが組めています。

### 書き込み
適当に書き込んでレプリケーションを確認します。

まず、先ほどログインしたコンテナのローカルでDB作成、テーブル作成、挿入を行います。

```bash
[root@08aaecd1590f ~]# mysql -uroot -e'create database pxctest'
[root@08aaecd1590f ~]# mysql -uroot -e'create table dockers (id int)' pxctest
[root@08aaecd1590f ~]# mysql -uroot -e'insert into dockers values (1)' pxctest
[root@08aaecd1590f ~]# exit
```

別のコンテナにログインして、`select`してみます。

```bash
$ ssh root@172.17.0.12 -i ./id_rsa
[root@ce8fd87cdf03 ~]# mysql -uroot -e'select * from dockers' pxctest
+------+
| id   |
+------+
|    1 |
+------+
```

ばっちり同期できてます。

# おわりに
以上です。
Galera Clusterの性質上コンテナの起動後にPerconaサービスを起動したり、設定ファイルを書き換えたりしてます。
Dockerの使い方としてはトリッキーなんだと思いますが、いい方法があれば教えて下さい。

Percona XtraDB Clusterのそもそもの構築方法や`/etc/my.cnf`の設定などもがっつり省いてしまったので、そのうちやろうと思います。
（順番逆というツッコミが聞こえます）

# 参考
* [Docker file/image to deploy Percona XtraDB Cluster 5.6 for dev/tests.](http://alexyu.se/content/2014/03/docker-fileimage-deploy-percona-xtradb-cluster-56-devtests)