mysqlfailoverコマンドの--exec-beforeと--exec-afterオプションについて。なお、動作確認したのは--exec-afterの場合のみです。
mysqlfailoverで--helpを実行すると以下のように表示されます。
--exec-after=EXEC_AFTER
name of script to execute after failover or switchover
--exec-before=EXEC_BEFORE
name of script to execute before failover or
switchover
説明にある通りfailover/switchover前後に動作するスクリプトを指定出来ます。とりあえずシェルスクリプトで書いて動かしたけど動作したので実行権等が適切に設定されていれば言語は問わなそうです。スクリプトの返り値が0なら正常終了、それ以外は異常終了と見なすのでそこだけ気をつければ大丈夫そう。
failoverが発生した際に動作するのはtopology.pyにあるdef failoverの中。--exec-beforeが1515行目、--exec-afterが1537行目。switchoverの場合はdef switchoverの中を参照して下さい。
1503 # Prepare candidate
1504 self._report("# Preparing candidate for failover.")
1505 self._prepare_candidate_for_failover(new_master, user, passwd)
1506
1507 # Create replication user on candidate.
1508 self._report("# Creating replication user if it does not exist.")
1509
1510 # Need Master class instance to check master and replication user
1511 self.master = self._change_role(new_master, False)
1512 res = self.master.create_rpl_user(host, port, user, passwd)
1513
1514 # Call exec_before script - display output if verbose on
1515 self.run_script(self.before_script, False)
1516
1517 # Stop all slaves
1518 self._report("# Stopping slaves.")
1519 self.run_cmd_on_slaves("stop", not self.verbose)
1520
1521 self._report("# Switching slaves to new master.")
1522 for slave_dict in self.slaves:
1523 slave = slave_dict['instance']
1524 # skip dead or zombie slaves
1525 if slave is None or not slave.is_alive():
1526 continue
1527 slave.switch_master(self.master, user, passwd, False, None, None,
1528 self.verbose and not self.quiet)
1529
1530 # Take the server out of the list.
1531 self._remove_slave(new_master_dict)
1532
1533 # Starting all slaves
1534 self._report("# Starting slaves.")
1535 self.run_cmd_on_slaves("start", not self.verbose)
1536
1537 # Call exec_after script - display output if verbose on
1538 self.run_script(self.after_script, False)
1539
1540 # Check slaves for errors
なお、run_scriptもtopology.pyで定義されています。
506 def run_script(self, script, quiet):
507 """Run an external script
508
509 This method executes an external script. Result is checked for
510 success (res == 0).
511
512 script[in] script to execute
513 quiet[in] if True, do not print messages
514
515 Returns bool - True = success
516 """
517 from mysql.utilities.common.tools import execute_script
518
519 if script is None:
520 return
521 self._report("# Spawning external script.")
522 res = execute_script(script)
523 if res == 0:
524 self._report("# Script completed Ok.")
525 elif not quiet:
526 self._report("ERROR: %s Script failed. Result = %s" %
527 (script, res), logging.ERROR)
mysqlfailoverが動作した際にMasterが変更されたSlaveでは未だにSQL_THREADが停止した状態になるのでシェルスクリプトを--exec-afterに設定し、SQL_THREADが開始されるか確認してみました(SQL_THREADが開始されない原因は未だによく分からず…)。
環境は以前の日記と同様です。実行コマンドとシェルスクリプトは以下のように。
実行コマンド
# /usr/local/mysql-wb/bin/mysqlfailover \
--master=failover:failover@192.168.1.237 \
--slaves=failover:failover@192.168.1.238,failover:failover@192.168.1.240 \
--candidates=failover:failover@192.168.1.238 \
--log=/tmp/failover.log \
--exec-after=/root/bin/check_slave.sh
シェルスクリプト
#!/bin/sh
LOG=/tmp/exec_after.log
while [ ${CHK_CNT:=3} -gt 0 ]; do
CNT=`/usr/local/mysql-5.6.7-rc/bin/mysql -h 192.168.1.240 -P 3306 -ufailover -pfailover -e 'show slave status\G' | egrep -i 'Slave_IO_Running|Slave_SQL_Running' | grep -i Yes | wc -l`
if [ $CNT -eq 2 ]; then
echo 'OK: slave running' >> $LOG
exit
else
echo 'NG: slave not running' >> $LOG
/usr/local/mysql-5.6.7-rc/bin/mysql -h 192.168.1.240 -P 3306 -ufailover -pfailover -e 'start slave'
fi
sleep 3
CHK_CNT=`expr $CHK_CNT - 1`
done
実行時の/tmp/failover.log
2012-11-10 03:39:12 AM INFO Master may be down. Waiting for 3 seconds.
2012-11-10 03:39:15 AM INFO Cannot reconnect to master.
2012-11-10 03:39:18 AM CRITICAL Master is confirmed to be down or unreachable.
2012-11-10 03:39:18 AM INFO Failover starting in 'auto' mode...
2012-11-10 03:39:18 AM INFO Candidate slave 192.168.1.238:3306 will become the new master.
2012-11-10 03:39:18 AM INFO Preparing candidate for failover.
2012-11-10 03:40:10 AM INFO Creating replication user if it does not exist.
2012-11-10 03:40:10 AM INFO Stopping slaves.
2012-11-10 03:40:10 AM INFO Performing STOP on all slaves.
2012-11-10 03:40:10 AM INFO Switching slaves to new master.
2012-11-10 03:40:10 AM INFO Starting slaves.
2012-11-10 03:40:10 AM INFO Performing START on all slaves.
2012-11-10 03:41:01 AM INFO Spawning external script.
2012-11-10 03:41:04 AM INFO Script completed Ok.
2012-11-10 03:41:04 AM INFO Checking slaves for errors.
2012-11-10 03:41:04 AM INFO Failover complete.
--exec-afterとかの動作確認用なので色々ザルなスクリプトです。ソース、ログからも分かる通り--exec-afterは切り替え完了後に動作するスクリプトとなります。上記スクリプトは192.168.1.240がSlaveとなっていることを前提として、レプリケーションのSQL_THREAD, IO_THREADのどちらか、または両方が停止してたらstart slaveするだけの簡単な処理です。
--candidatesで複数サーバを指定している場合は適切にfailover後のMaster, Slaveを判定する事が必要。ソース見る限りはスクリプトに対して引数とかは渡して無いようなのでdef run_scriptを改造してその辺り判定しやすいようにするのが良さそう。
一応、--exec-afterでstart slaveしたSlaveのMySQLのエラーログ。1回エラーになったのをstart slaveで無理矢理開始。
121110 3:41:01 [ERROR] Error writing relay log configuration.
121110 3:41:01 [ERROR] Slave SQL: Failed during slave workers initialization, Error_code: 1593
121110 3:41:01 [Note] Slave SQL thread initialized, starting replication in log 'FIRST' at position 0, relay log './mysql-relay-bin.000001' position: 4
MasterをDNSで切り替えたりする場合には--exec-afterで実行するスクリプト内で処理させる事になりそう。VIPの遷移もやろうと思えば出来るだろうけど、適切に元Masterのサーバを処理する必要があるのでVIPは個人的にはあまりやりたいと思わない…
実運用で使おうと思うと色々考慮しないといけない点が多いけど上手く使えば便利なのは間違い無いわけで。ざっくり考慮しないといけないことを考えると
- アプリケーション側からの参照切り替え方法
- DNS, またはVIP? DNSの場合はDNSキャッシュは大丈夫? VIPの場合は元Masterが勝手に起動して来た場合は大丈夫?
- 元Master復旧後の切り戻し方法
- 上2つに関連して間違って元Masterを更新しちゃったら?
- read_onlyオプションで回避出来るような適切な権限設定だったら上手く仕込めるか考えるのもありか?
とかぐらい?
仕事ではアプリケーションの面倒は基本的に見ていないので開発されている人から見るとこれも、ってのがあるかもしれません。とはいえ、この手の処理でアプリケーション側で手を加えるのはナンセンスだと思うのでインフラ側の処理だけでどうにかするのが良いんだろうなぁ。
自宅でcode igniter使って適当にPHPいじっていますが、database.phpを書き換えるとか台数多いと考えたくない… いくら自動化しても漏れとか起こりえるからなぁ。