RedisをKeepalivedでフェイルオーバーする構成案

master slave 構成を取っている Redis で、master が落ちた場合に slave を昇格させてフェイルオーバーしたいという要件がありまして、Keepalived と組み合わせて構成してみました。Redis の運用経験がないのでご意見などいただければありがたいです。

前提

  • Redis のレプリケーションではマルチマスター構成を取ることができない
  • Redis の slave は起動時に master に接続し、全データを取得してコピーを取る
  • その後は順次 master で更新されたデータをコピーする
  • redis-cli で slaveof コマンドを実行することで、動的に master, slave を切り替えることが可能

このような作りになっているため、2ホスト間で VIP を持たせ、アプリケーションからは常に VIP に接続。VIP を持っているホストが Redis の master になり、もう片方が slave、という状態を作ります。


  • VIP を持っている master (のホスト、ネットワークなど) が落ちた場合
    • slave の Keepalived が VIP をフェイルオーバーして引き継ぐ
    • slave redis-server を master 状態に変更する
  • master の redis-server プロセスが落ちた場合
    • master でプロセスの停止を検知し、Keepalived を停止して強制的にフェイルオーバーさせる

Keepalived の設定

keepalived.conf で VIP の設定と、redis-server に対する死活監視の設定を行います。

vrrp_instance VI_1 {
    state BACKUP
    interface eth0
    virtual_router_id 11
    priority 100
    advert_int 1
    notify_master /usr/local/sbin/redis-to-master.sh
    notify_backup /usr/local/sbin/redis-to-slave.sh
    authentication {
        auth_type PASS
        auth_pass xxxx
    }
    virtual_ipaddress {
        192.168.1.10
    }
}

virtual_server 127.0.0.1 16379 {
    delay_loop 5
    lb_algo rr
    real_server 127.0.0.1 6379 {
        MISC_CHECK {
            misc_path "/usr/local/sbin/redis-check.sh"
            misc_timeout 30
        }
    }
}

notify_master, notify_backup で VIP の状態が変わった際に、Redis に対して master, slave に移行する指示を発行するスクリプトを指定しています。ついでに redis.conf も書き換えてしまいます。

#!/bin/sh
# redis-to-master.sh

redis-cli slaveof no one
perl -pi -e 's/^slaveof.*/slaveof no one/' /etc/redis.conf
#!/bin/sh
# redis-to-slave.sh

PEER_HOST="192.168.1.11" # 相手のホスト
PEER_PORT=6379
redis-cli slaveof $PEER_HOST $PEER_PORT
perl -pi -e "s/^slaveof.*/slaveof $PEER_HOST $PEER_PORT/" /etc/redis.conf 

virtual_server 127.0.0.1 16379 の設定はダミーで、MISC_CHECK で Redis の死活監視を行うスクリプトを定期的に実行するためのものです。

#!/bin/sh
# redis-check.sh

for TRIES in `seq 1 3`
do
  RESULT=`redis-cli ping`
  if [ "${RESULT}" = "PONG" ]; then
    exit 0
  fi
  echo "ping failed ${TRIES}" | logger -t redis-check
  sleep 1
done
echo "redis server was down. shutdown keepalived."  | logger -t redis-check
service keepalived stop
exit 1

PING を発行し、PONG が返ってくれば正常終了。3回試行して成功しなければ keepalived stop することで強制的にフェイルオーバーを行います。

動作

master になっているホストが突然死した場合

KVM仮想マシン host1 を virsh destroy で落とした時の、host2 側のログです。

==> /var/log/messages <==
Aug  2 12:30:15 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Transition to MASTER STATE
Aug  2 12:30:16 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Entering MASTER STATE
Aug  2 12:30:16 host2 Keepalived_vrrp: VRRP_Instance(VI_1) setting protocol VIPs.
Aug  2 12:30:16 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 192.168.1.10
Aug  2 12:30:16 host2 Keepalived_healthcheckers: Netlink reflector reports IP 192.168.1.10 added

==> /var/log/redis/redis.log <==
[2332] 02 Aug 12:30:16 * MASTER MODE enabled (user request)

バックアップホストで動作している Keepalived が MASTER に切り替わり、そのタイミングで Redis も master 状態に移行します。

master の redis-server が死んだ場合

host1 で pkill -KILL redis-server した場合のログです。

==> /var/log/messages <==
Aug  2 12:39:30 host1 redis-check: ping failed 1
Aug  2 12:39:31 host1 redis-check: ping failed 2
Aug  2 12:39:32 host1 redis-check: ping failed 3
Aug  2 12:39:33 host1 redis-check: redis server was down. shutdown keepalived.
Aug  2 12:39:33 host1 Keepalived: Terminating on signal
Aug  2 12:39:33 host1 Keepalived: Stopping Keepalived v1.2.2 (03/20,2012)
Aug  2 12:39:33 host1 kernel: IPVS: set_ctl: invalid protocol: 0 127.0.0.1:16379 rr
Aug  2 12:39:33 host1 kernel: IPVS: set_ctl: invalid protocol: 0 127.0.0.1:16379 rr
Aug  2 12:39:33 host1 Keepalived_healthcheckers: Terminating Healthchecker child process on signal
Aug  2 12:39:33 host1 Keepalived_vrrp: Terminating VRRP child process on signal
Aug  2 12:39:33 host1 Keepalived_vrrp: VRRP_Instance(VI_1) removing protocol VIPs.

ヘルスチェックが3回連続で失敗し、host1 の Keepalived が停止。

==> /var/log/redis/redis.log <==
[2332] 02 Aug 12:39:28 * Connecting to MASTER...
[2332] 02 Aug 12:39:28 * MASTER <-> SLAVE sync started
[2332] 02 Aug 12:39:28 * Non blocking connect for SYNC fired the event.
[2332] 02 Aug 12:39:28 # I/O error writing to MASTER: Connection refused
(略)
[2332] 02 Aug 12:39:38 * Connecting to MASTER...
[2332] 02 Aug 12:39:38 * MASTER <-> SLAVE sync started
[2332] 02 Aug 12:39:38 * Non blocking connect for SYNC fired the event.
[2332] 02 Aug 12:39:38 # I/O error writing to MASTER: Connection refused
[2332] 02 Aug 12:39:39 * MASTER MODE enabled (user request)

==> /var/log/messages <==
Aug  2 12:39:38 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Transition to MASTER STATE
Aug  2 12:39:39 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Entering MASTER STATE
Aug  2 12:39:39 host2 Keepalived_vrrp: VRRP_Instance(VI_1) setting protocol VIPs.
Aug  2 12:39:39 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 192.168.1.10
Aug  2 12:39:39 host2 Keepalived_healthcheckers: Netlink reflector reports IP 192.168.1.10 added
Aug  2 12:39:44 host2 Keepalived_vrrp: VRRP_Instance(VI_1) Sending gratuitous ARPs on eth0 for 192.168.1.10

host2 側で Keepalived と redis が MASTER になります。

まとめ

Keepalived で MASTER になっているほうを Redis の MASTER にすることで、フェイルオーバー構成を作ることができました。

まだ実戦投入前なので、なにか見逃している問題点などあれば教えていただけると嬉しいです。