VagrantでUbuntuサーバを2台立ててMySQLでマスタスレーブ構成にしてみた

久々の更新。
土曜日は これまで何となく使っていたVagrantを本格的にいじっていたけど、知れば知る程便利だなあ。
Vagrantfileって Gruntfileみたいに何となくいじるのが面倒くさそうな印象があったんだけど、実は全くそんな事がなくて 寧ろちょろっとやれば誰でも簡単にいじれるようになるくらい学習コストが低かった(まだChefと絡めていないので Chefと連携させると若干話が変わってくるかもしれないけど)。
というわけで今回は Vagrantを使ってUbuntuサーバを2台立てて MySQLでレプリケーションを構成してみた話を。
今回に関してはどちらかというとVagrantよりMySQL寄りの話になります。
Vagrantに関しては boxの構造とかVagrantfileの事とか 色々と整理できたので 後日 初心者向けにvagrantの基本的な事柄についてまとめます。

Vagrantfileの設定

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
    config.vm.box = "Ubuntu_13"
    config.vm.box_url = "http://puppet-vagrant-boxes.puppetlabs.com/ubuntu-1310-x64-virtualbox-puppet.box"

  config.vm.define :ubuntu_1 do |ubuntu_1|
    ubuntu_1.vm.network :private_network, ip: "192.168.33.10"
  end  

  config.vm.define :ubuntu_2 do |ubuntu_2|
    ubuntu_2.vm.network :private_network, ip: "192.168.33.11"
  end  
end

こんな感じで Ubuntuの13.10を2台立てて、IPは192.168.33.10 と 192.168.33.11 の連番で割り振る。

$ vagrant up

ポートフォワードでこけるかなーと思ったら、Vagrantが勝手に衝突の解決をしてくれて localhost:2222 と localhost:2200 の2つを立ててくれた。うーん便利。
うまくサーバが2つ立ち上がったかを確認するために pingを飛ばしてみる。

$ ping 192.168.33.10
PING 192.168.33.10 (192.168.33.10): 56 data bytes
64 bytes from 192.168.33.10: icmp_seq=0 ttl=64 time=0.294 ms
64 bytes from 192.168.33.10: icmp_seq=1 ttl=64 time=0.273 ms
64 bytes from 192.168.33.10: icmp_seq=2 ttl=64 time=0.450 ms
64 bytes from 192.168.33.10: icmp_seq=3 ttl=64 time=0.463 ms
^C
--- 192.168.33.10 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.273/0.370/0.463/0.087 ms

$ ping 192.168.33.11
PING 192.168.33.11 (192.168.33.11): 56 data bytes
64 bytes from 192.168.33.11: icmp_seq=0 ttl=64 time=0.453 ms
64 bytes from 192.168.33.11: icmp_seq=1 ttl=64 time=0.669 ms
64 bytes from 192.168.33.11: icmp_seq=2 ttl=64 time=0.749 ms
64 bytes from 192.168.33.11: icmp_seq=3 ttl=64 time=0.601 ms
^C
--- 192.168.33.11 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.453/0.618/0.749/0.109 ms

うん、うまく立ち上がってくれてる。

実際にsshでサーバに入るときは vagrantユーザで以下のように入る。また、vagrantユーザのデフォルトのパスワードは vagrant。

$ ssh vagrant@192.168.33.10
$ ssh vagrant@192.168.33.11

今回はセキュリティの話ではないので、秘密鍵の設定やiptablesの設定等は省略。

MySQLをインストール

何はともあれマスタサーバ、スレーブサーバにMySQLが入っている必要がある。立ち上げたばかりの Ubuntuサーバであれば、まずはapt-get updateを打つ必要があるので、2つのサーバに対してこんな感じでコマンドを打つ。

$ sudo apt-get update
$ sudo apt-get install mysql-server

親切にもインストールの際にMySQLのrootログイン用パスワードの設定画面が出てきてくれるので、お好みのパスワードを設定しておく。

マスタサーバ側の設定

my.cnfの設定をする。

$ sudo vi /etc/mysql/my.cnf

mysqld の項目に以下の2つを加える。

[mysqld]
log-bin
server-id=1

log-bin は「スレーブサーバと同期を取る為に必要なバイナリログを出力する」という設定。
server-idは任意の数値で構わないが、スレーブサーバがマスタサーバのデータベースを参照するのに利用するので、スレーブサーバと重複しないようユニークなものを設定する必要がある。

また、mysqld設定の下にbind-addressに関する以下のような設定がある。このままだと外部サーバからの接続を受け付けてくれないため、ここではコメントアウトしてしまう( セキュリティを考慮するのであれば、別途 allow-hostsやdeny-hostsの設定が必要になりそう )。

# bind-address = 127.0.0.1

一連の設定が終わった所でmysqlを起動(デフォだとインストール後立ち上がっているみたいなのでrestartに)。

$ sudo mysql restart

とりあえず「test」っていうデータベースを作成して、こんな感じで「user」テーブルを作成する。話を簡潔にするために内部エンコーディング等諸々の設定は省略。

mysql> CREATE DATABASE test;
mysql> USE test;
mysql> CREATE TABLE users ( user_id INT PRIMARY KEY AUTO_INCREMENT NOT NULL, user_name VARCHAR(30) UNIQUE NOT NULL );

スレーブサーバがアクセスできるようにユーザをつくる。

mysql > GRANT REPLICATION SLAVE ON *.* TO 任意のユーザ名@192.168.33.11 identified by '任意のパスワード';

「test.*」じゃ駄目なの?って思ったけど、「レプリケーションはグローバルな設定だから駄目よ」みたいなニュアンスの事で怒られるから駄目みたい。

スレーブサーバへのコピーを取る際にデータベースに更新がかからないようテーブルをロック。
ロックしたら、マスタのステータスを見る。

mysql> FLUSH TABLES WITH READ LOCK;
mysql> SHOW MASTER STATUS;

ここで表示されたマスタステータスのFileとPositionは 「どの時点からレプリケーション取り始めればいいの?」という事をスレーブに教えてくれる大事な情報になるのでメモしておくこと。僕の場合、Fileはmysql-bin.000001、Positionは742。

その後 tarで固めてスレーブサーバにtarファイルをコピーする。

$ tar cvf test.tar.gz /var/lib/mysql/test

なお、InnoDBの場合 データベースのディレクトリだけコピーしても「show tablesするとテーブルが出るのに、desc や selectをしようとすると table doesn’t exist と怒られてしまう」…という問題があるらしく、僕も若干ハマった。解決方法としては /var/lib/mysql/ 下にある「ib」から始まるファイルもコピーする必要がある(さらに、コピーした後mysqlを再起動する必要がある)ということらしく(また、この際 権限がmysqlにある事をチェックすること)僕の場合はこれで解決した。
(参考: stackoverflow: Mysql > Table doesn’t exist. But it does(or it should))

コピーを取り終えたらロックの解除を忘れずに。

mysql> UNLOCK TABLES;

なお、今回はディレクトリをそのままコピーする方法でやったけれど、mysqldumpを使う方法もあるらしいので、ディレクトリコピーでうまくいかなかった人はこちらの方法を試してみると良いかもしれない。
( 2014.10.12 18:00 追記 – 試してみた所、mysqldump の方が ib系のデータを書き換えたり権限を書き換えたりする手間が省けるので総合的に楽でした。また、mysqldumpでの方法については以下のリンクを参考にさせていただきました )

(参考: MySQL レプリケーションのセットアップ手順)

スレーブサーバ側の設定

こちらも /etc/mysql/my.cnf を編集して mysqldにserver-idを付加しておく。この際マスタのserver-idと重複しないよう注意すること。

server-id=2

マスタサーバでコピーしたtarファイルを /var/lib/mysql/下で展開。

$ tar xvf test.tar.gz

権限まわりの確認は必ず行うこと。また、上にも書いたように InnoDBの場合は ibから始まるファイルもコピーしておかないと「テーブルが表示されるのに参照できない」問題が発生する可能性があるので注意。

準備ができたらMySQLを立ち上げる。

$ service mysql restart

いよいよマスタサーバと同期させるぞう。
…とその前に、念のためにtelnetを叩いてスレーブからマスタサーバにアクセスできるか確認しておこう(3306はMySQLのデフォのポート)。

$ telnet 192.168.33.10 3306

これで通ればOK。

mysql> CHANGE MASTER TO
          MASTER_HOST='192.168.33.10',
          MASTER_USER='上記のGRANTオプションで設定したユーザ名',
          MASTER_PASSWORD='上記のGRANTオプションで設定したパスワード',
          MASTER_LOG_FILE='SHOW MASTER STATUSで確認したファイル名',
          MASTER_LOG_POS=SHOW MASTER STATUSで確認したposition;
mysql> Query OK, 0 rows affected (0.00 sec)
mysql> START SLAVE;
mysql> Query OK, 0 rows affected (0.00 sec)

スレーブのステータスを見て Slave_SQL_Runnningが YES になっていることを確認すること。
また Last_IO_Errorあたりに変なエラーが出ていないか確認すること。
例えば 上手くコネクションが貼れていない場合は「error connecting to master」なんてエラーが出る。
↓ (例) マスタに上手く繋げていない時の スレーブのステータス。
9

レプリケーションのテストをしてみる

早速 レプリケーションが上手く構成できているかどうかテストしてみる。
まずはマスタサーバのtestデータベースにアクセス。

念には念を入れて ちゃんとマスタにアクセスしているかどうかを確認。

mysql> SHOW MASTER STATUS;
mysql> SHOW SLAVE STATUS;

ちゃんとマスタにいることが確認できたら、ドキドキしながらデータを入れてみる。

mysql> INSERT INTO users (user_name) VALUES ('hogeman');

次にスレーブサーバのtestデータベースにアクセス。

mysql> SELECT * FROM users;

スレーブサーバにも’hogeman’のデータが入っていたら成功。正常にレプリケーションが構成できていることになる。
「あれ…?反映されないぞ?」という場合は再度 SHOW SLAVE STATUS; でエラーが出ていないか確認してみる。

その他の処方箋

Q. MySQLをstartさせたり stopさせたりしてたら MySQLが起動できなくなった。

「Job failed to start.」とか「Can’t connect to local MySQL server through socket.」とか怒られる場合は 該当ディレクトリにsocketファイルがあるかどうかを確認する。socketファイルが無ければ touchコマンドか何かで socketファイルを作成し、権限を mysql:mysql に設定してあげる。
その後「sudo /etc/init.d/mysql start」「sudo mysql start」「sudo service mysql start」をそれぞれ試してみる。
大体はこれで解決するらしいけど、それでも駄目なようなら my.cnf ファイルが間違っている可能性があるのでチェックすること。
(例えば bind-address を複数設定しようとしていたり。bind-addressは一つのホストしか受けつけない)

Q. あれこれいじってたら スレーブ側で duplicate entryが出るようになってしまった。

スレーブ側でレプリ遅延が起きている。
こういう場合はSHOW SLAVE STATUS; でエラーログを追跡しながら 親と整合性を合わせる必要がある。
一個ずつ慎重にレプリ遅延している箇所を見ながら SQL_SLAVE_SKIP_COUNTER を回していく作業。

スレーブをストップして、

mysql> STOP SLAVE;

整合性を合わせながら SLAVE_SKIP_COUNTERを回して( SLAVE側に直接insert処理をかけてしまったり 整合性が合わなくなってしまった所は 別途 スレーブに追加してしまったデータを削除して AUTO_INCREMENTのスタート値を調整して…等の作業が発生する感じになるのかな…? )、

mysql> SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;

再開する。

mysql> START SLAVE;

ただしskipに関しては トランザクション処理等を行う場合の挙動について若干注意が必要みたい。
(参考: Another reason why SQL_SLAVE_SKIP_COUNTER is bad in MySQL)

Written by Nisei Kimura ( 木村 仁星 )

- Sponsored Links -

<<

Top

>>