備忘録として、DSRによるLVSとkeepalivedの設定を段階的に書く。
前提は以下とする。
- LVSの転送方式はDSR
- ネットワークは単一セグメントで超簡単なもの
- OSはCentOS6.5
今回はMySQLスレーブの死活監視+管理について。システム構成はLVS+keepalivedの設定 3を参照。
TCPポートの監視:TCP_CHECK
もっとも簡単な方法。LVS+keepalivedの設定 2で使った。
keepalived.confの一部を示す。
… 略 … real_server 192.168.1.201 3306 { weight 1 inhibit_on_failure # サーバ監視: TCPポートにアクセスするだけの超簡易的方法 TCP_CHECK { connect_port 3306 connect_timeout 30 nb_get_retry 3 delay_before_retry 3 } } real_server 192.168.1.202 3306 { weight 1 inhibit_on_failure # サーバ監視: TCPポートにアクセスするだけの超簡易的方法 TCP_CHECK { connect_port 3306 connect_timeout 30 nb_get_retry 3 delay_before_retry 3 } }
この方法の根本的な欠点は、障害検出が間接的ということである。
「ポートにアクセスできない=サービス停止」とは限らない訳で、サーバは稼働しているが一時的に通信ができなくて障害と判断されてしまう可能性がある。しかしもっとマズいのは、すでに過負荷でサービスは停止していたり、max_connection超えで接続できないにも関らず、3306ポートが生きていれば、LVSはサーバの障害発生を検出できないことである。
クライアントはアクセスできない状態なのに、それを認識できないのはバランサとして存在価値がないに等しい。
自前のプログラムで監視:MISC_CHECK
TCP_CHECKはあまりに低機能なので、MISC_CHECKを利用するためのチェックプログラムを自前で実装してみる。
最初にMISC_CHECKを使うためのkeepalived.confの一部を示す。
… 略 … real_server 192.168.1.201 3306 { weight 1 inhibit_on_failure # サーバ監視: MISC_CHECK { # 自前のチェックスクリプト。 Usage: mysql-check.sh host timeout misc_path "/usr/local/bin/mysql-check.sh 192.168.1.201 10" # チェックスクリプトのタイムアウト misc_timeout 15 } } real_server 192.168.1.202 3306 { weight 1 inhibit_on_failure # サーバ監視: MISC_CHECK { misc_path "/usr/local/bin/mysql-check.sh 192.168.1.202 10" misc_timeout 15 } }
パラメータについて*1:
ここでタイムアウトは大事。delay_loopで指定した周期でmisc_pathのプログラムが実行される。もしもそのプログラムの実行がdelay_loopよりもかかると、チェックプロセスがどんどん立ち上がってしまう。
よって、可能ならばチェックプログラムにもタイムアウトを指定し、次の関係が成り立つように値を調整する。
misc_pathプログラムのタイムアウト < misc_timeout < delay_loop
以下、順に説明する。
mysql-check.sh
以下のプログラムをLVS1とLVS2の/usr/local/bin以下に作成する。
cat /usr/local/bin/mysql-check.sh #!/bin/bash # # mysql server check script # # Usage: mysql-check.sh host timeout MYSQL=/usr/bin/mysql USER=root CONF=/root/.my.cnf HOST=localhost TIMEOUT=10 if [ $# -ne 2 ]; then exit -1 else HOST=$1; TIMEOUT=$2 fi $MYSQL --defaults-extra-file=$CONF -h $HOST -u $USER --connect-timeout=$TIMEOUT -e 'SELECT 100' 2>&1 > /dev/null if [ $? -eq 0 ]; then # check:OK exit 0 else # check:NG exit 1 fi
mysqlクライアントでHOSTに接続し、”SELECT 100”を実行する。これにより、少なくともTCPポート監視よりはマシな死活監視が可能である。
通信路がおかしいと接続に時間がかかる場合があるので、--connect-timeoutで明示的にタイムアウトを設定する。パスワードなどは(後述する)/root/.my.cnfに設定する。
作成したらパーミッションを設定する。
# chmod 700 /usr/local/bin/mysql-check.sh
LVS1, LVS2に/root/.my.cnfを準備
スレーブにアクセスするためのパスワードを/root/.my.cnfに設定する。
# cat /root/.my.cnf [client] user=root password=XXXX [mysqladmin] user=root password=XXXX
作成したらパーミッションを変更し、root以外は読み書きできないようにする。
# chmod 600 /root/.my.cnf
Slave側でユーザ登録
LVS1,LVS2からアクセスできるようにユーザを登録する。
mysql> GRANT ALL PRIVILEGES on *.* to root@192.168.1.100 identified by PASWORD(‘XXXX’); mysql> GRANT ALL PRIVILEGES on *.* to root@192.168.1.101 identified by PASWORD(‘XXXX’); mysql> FLUSH PRIVILEGES;
これは権限を与え過ぎだが、後で説明する”mysqladmin shutdown”のためにこうしてある。
動作確認
これでうまくいくはず。うまく行かない場合はパーミッション関係を見直すこと。
スクリプトの問題点
ところでこのmysql-check.shは、スレーブとの接続前ならconnect-timeoutでタイムアウトするが、接続後にスレーブがとても重くなるとそのまま”SELECT 100”の実行が待たされる可能性がある。
よって以下のような関係になる場合がある。
misc_pathプログラムのタイムアウト > misc_timeout
この場合、keepalivedは接続失敗とみなすが実はスレーブは稼働しているわけで、とにかく障害検出はとてもとても難しい。
これはレアケースだから考慮する必要がないかといえば、そんなことはない。
本当にサーバが不調になった発端を検出したのかもしれないし、単に一時的な過負荷かもしれないし、ネットワーク機器の不調やリコンフィグレーションによってレスポンスが遅れているだけかもしれない。
大抵のシステムでは、何回かリトライしてN回連続失敗したら障害発生とみなすようになっている。実用上、数回ダメならそのノードは切り離した方がいいし、障害発生と見なすのは合理的だろう。
しかし、「keepalivedのMISC_CHECKではretry指定ができないから、上記レアケースを受け入れるしか無い」というのは、かなり不満が残る。
せいぜいmisc_timeoutとloop_delayの値を大きく取って、障害検出ミスを避ける=レアケースがほとんど起きる可能性がないような設定を選ぶしかない。
課題
上の問題に限らず、障害検出はとても難しい(非同期分散システムにおいて完全な障害検出は原理的に不可能)。
でもそこは1億歩譲って、上記の障害検出スクリプトが1(異常値)を返し、”何かおかしなこと”が起きたとしよう。
実は、障害検出後の処理も難しい。”何かおかしなこと”が起きた結果としてmysqlクライアントが"SELECT 100"を時間内に実行できなかったわけだが、その原因である”何かおかしなこと”はそれこそ無数に考えられるし、その原因の結果が"SELECT 100"の遅れだけとは限らないから、どんな対処をすべきかなんて、とてもじゃないが網羅できるはずがないのだ。
MySQLサーバはもちろん玩具みたいに脆いが、同様にHDDをはじめとするサーバのハードウエア、スイッチやケーブルなどのネットワーク機器もちょっとした不調は起こり得る。そもそも不調ではなく、ネットワーク管理者が他のノードの設定中に誤って通信不可になってしまうとか、設定をリセットするために軽い気持ちでスイッチやルータの電源をOFF/ONするとか、keepalivedやMySQLにとっての異常事態はわりと簡単に起きる。このようなちょっとしたことが原因でどんな結果になってしまうのか、予め全てを想定するのは不可能。
ただ一つ確実なのは、なんらかの障害が検出されたということは、システムのどこかが正常でない挙動を示したということで、とにかく”何かおかしなことが少なくとも一度は起きた”のである。
すると、マスターとスレーブのデータの不一致が起きてしまった可能性も否定はできない。特にレプリケーション*2はちょっとしたことがキッカケで不調になるので油断ならない。
フェールセーフ
では、サーバの状態が障害検出されたりまた正常に戻ったりしたらどうだろう? その都度、LVSがサーバを切り離したり繋いだりしては困る。障害検出が完全でない以上、例え障害後に正常と判断しても、マスターとスレーブでデータの不一致等、不具合が発生している可能性は否定できない。
ということで、障害検出だけでは冗長化したDBクラスタを護れない。障害検出後の処理もとても重要だ。
DBを管理するLVSは「障害検出したスレーブを積極的に停止させる=安全側に倒した構成(フェールセーフ)」にすべきだろう。もちろん、過度に安全側に設定を倒しすぎると、障害に対してあまりにセンシティブになって、ちょっとしたことでサービス全停止なんて事態に行き着くのであるが。
難しいことはさておき、とりあえず障害検出したら”mysqladmin shutdown”を実行する設定とスクリプトの例を以下に示す。
shutdownスクリプト
以下のスクリプトを作成する。
cat /usr/local/bin/mysql-shutdown.sh #!/bin/bash # # mysql server shutdown script # # Usage: mysql-shutdown.sh host MYSQLADMIN=/usr/bin/mysqladmin USER=root CONF=/root/.my.cnf HOST=localhost if [ $# -ne 1 ]; then exit -1 else HOST=$1; fi $MYSQLADMIN --defaults-extra-file=$CONF -h $HOST -u $USER shutdown exit 0
作成したらパーミッションを設定する。
# chown root:root /usr/local/bin/mysql-shutdown.sh # chmod 700 /usr/local/bin/mysql-shutdown.sh
keepalived.confの設定
real_serverにnotify_downの設定を追加する。
… 略 … real_server 192.168.1.201 3306 { weight 1 inhibit_on_failure # サーバ監視: MISC_CHECK { # 自前のチェックスクリプト。 Usage: mysql-check.sh host timeout misc_path "/usr/local/bin/mysql-check.sh 192.168.1.201 10" # チェックスクリプトのタイムアウト misc_timeout 15 } # 実サーバの障害を検出したら、mysql-shutdown.shを実行。スレーブを積極的にshutdownする notify_down “/usr/local/bin/mysql-shutdown.sh 192.168.1.201” } real_server 192.168.1.202 3306 { weight 1 inhibit_on_failure # サーバ監視: MISC_CHECK { misc_path "/usr/local/bin/mysql-check.sh 192.168.1.202 10" misc_timeout 15 } notify_down “/usr/local/bin/mysql-shutdown.sh 192.168.1.202” }
動作確認
これで障害検出したらスレーブサーバを強制的に(mysqladminで)shutdownする。
またまた課題
そもそもの話だがmysqlクライアントで接続できないサーバに、mysqladminで接続できるのかという根本的な問題がある。
より確実にサーバを停止させるには、sshで”/etc/init.d/mysqld stop”を実行するとか、”kill -9 mysqld.pid”を実行するほうがましだろう。それでも確実ではないが。
しかし、スクリプトにsshを実行させるにはパスワード入力をさけるためにssh-agentを動かさねばならない等、システム全体のセキュリティがどんどん低下してしまう。
しかも、それでもダメな場合だってある。LVS1とLVS2の障害が重なるイヤらしいシナリオとして「LVS1がスレーブ1の障害を検出した直後にダウンしたのでスレーブ1を停止できなかった。その後LVS2がマスターに昇格したときには、なぜかスレーブ1も動作していたが、実はデータがおかしくなっていた」とか。悪い状況はいくらでも想定できる*3。
どこまでやるかは仕様次第というか、決めの問題。確実さを求めるほどどんどん手間が増えるし、どこまでいっても終わりはない。
参考URL
- インストールから構築まで全般的な情報
- LVS構築手順 :DR、1段構成の構築手順。lo:0方式。
- LVSロードバランサ : DR、一段構成の構築手順。lo:0でなくiptables REDIRECT。細々した解説引用が参考になった。
- 【Linux】LVS(DSR方式)構築手順書 : DR、1段構成の構築手順書。lo:0でなくiptables REDIRECT
- keepalivedを利用する際に気をつけておくこと : DR、2段構成の構築で、VRRPが届かないとスプリットブレインになることを明記。
- KeepalivedによるロードバランサLVS構築 :DR、2段構成の構築手順
- Linux Virtual ServerとKeepalivedで作る冗長化ロードバランサ : NAT、2段構成の構築手順
- その他
- Linux Virtual Server Tutorial : ウルトラモンキーのチュートリアル
- Webサーバーのクラスターをセットアップする5つの簡単なステップ : heartbeatとkeepalivedを使ったWEBサーバの冗長化
- KeepalivedのMASTERの遷移表: 非常に役に立つ。
- DRBDとKeepalivedで、障害性の高いストレージサーバの構築: notify_master、notify_backupの非常に詳しい説明。
- Keepalived講習会 : notify_master、notify_backupの使い方など
- MySQL5.5+Keepalivedを使った、2台構成型準同期レプリケーション : notify_downの使い方など。
- keepalived.confの書式
- MISC_CHECKの注意点