mysqlfailoverの--exec-beforeと--exec-after

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復旧後の切り戻し方法
    • 切り戻さないでSlaveとして追加する等々
  • 上2つに関連して間違って元Masterを更新しちゃったら?
    • read_onlyオプションで回避出来るような適切な権限設定だったら上手く仕込めるか考えるのもありか?

とかぐらい?

仕事ではアプリケーションの面倒は基本的に見ていないので開発されている人から見るとこれも、ってのがあるかもしれません。とはいえ、この手の処理でアプリケーション側で手を加えるのはナンセンスだと思うのでインフラ側の処理だけでどうにかするのが良いんだろうなぁ。
自宅でcode igniter使って適当にPHPいじっていますが、database.phpを書き換えるとか台数多いと考えたくない… いくら自動化しても漏れとか起こりえるからなぁ。