2014年3月24日月曜日

Redis Sentinelをセットアップして、お手軽にRedisのフェイルオーバーを実現する方法


言わずと知れた、Redisのフェイルオーバーツール、Redis Sentinel(http://redis.io/topics/sentinel)をお手軽に使う方法を紹介します。


フェイルオーバー時に、アプリサーバからRedisへの接続先を変更する方法として、”アプリサーバへ /etc/hosts を rsync で配布する” といった方法をとります。
いままでは、RedisサーバのIPアドレスの付け替えや、HAProxyの設定変更を自動で行う、などといった方法がありましたが、これは、敷居低めで実装できるのが嬉しいです。

動作イメージは下のようになります。
redis1が壊れたとき、redis2が自動でマスタに昇格してサービスを継続します。


詳しく説明します。
app1、app2の /etc/hosts には、Redisマスタのホスト名 redis-master.myservice.com と redis1 のIPアドレスを紐づける記述しておきます。
app1、app2のアプリケーションは、このホスト名 redis-master.myservice.com 宛にRedis接続をします。
フェイルオーバー時には、redis-master.myservice.com の IPアドレスを redis2 のものに変更した /etc/hosts を rsync で差し替えます。

サーバIDとIPアドレスはこのようになっているとします。

app1 (100.100.100.1): アプリサーバ1 + Redis Sentinel
app2 (100.100.100.2): アプリサーバ2
redis1 (200.200.200.1): Redisマスタ
redis2 (200.200.200.2): Redisスレーブ

試験環境は、CentOS 6.5、Redis 2.8.7です。
Redisは、既にインストールされている状態ではじめます。
Redis Sentinelは、app1サーバにセットアップします。
iptablesなどのアクセスコントロールは便宜設定してください。

◆ 事前準備

1)app1からapp2サーバへrootユーザでログインできるようにしておく。

フェイルオーバーするときに、hostsファイルを転送できるようにしておきます。

2)Redisマスタ -> スレーブのレプリケーションを構築しておく。

redis2サーバで設定します。

Redisスレーブの設定ファイルに、マスタの情報を記述
# vim /etc/redis/redis.conf
---
slaveof 200.200.200.1 6379
---

Redisリスタート
# /etc/init.d/redis stop
# /etc/init.d/redis start
Starting Redis server...

infoを確認
# redis-cli -p 6379 info | grep role -3
latest_fork_usec:0

# Replication
role:slave
master_host:200.200.200.1
master_port:6379
master_link_status:up
-> リンクステータスがupなのでおkです。

ログを確認
# tail -f /var/log/redis.log
[3888] 24 Mar 13:05:36.291 # Server started, Redis version 2.8.7
[3888] 24 Mar 13:05:36.291 * DB loaded from disk: 0.000 seconds
[3888] 24 Mar 13:05:36.291 * The server is now ready to accept connections on port 6379
[3888] 24 Mar 13:05:36.293 * Connecting to MASTER 200.200.200.1:6379
[3888] 24 Mar 13:05:36.293 * MASTER <-> SLAVE sync started
[3888] 24 Mar 13:05:36.329 * Non blocking connect for SYNC fired the event.
[3888] 24 Mar 13:05:36.351 * Master replied to PING, replication can continue...
[3888] 24 Mar 13:05:36.375 * Partial resynchronization not possible (no cached master)
[3888] 24 Mar 13:05:36.376 * Full resync from master: 895edcc4655424517d5a9ddc25fc86a4aadb7f91:1
[3888] 24 Mar 13:05:36.465 * MASTER <-> SLAVE sync: receiving 18 bytes from master
[3888] 24 Mar 13:05:36.465 * MASTER <-> SLAVE sync: Flushing old data
[3888] 24 Mar 13:05:36.465 * MASTER <-> SLAVE sync: Loading DB in memory
[3888] 24 Mar 13:05:36.465 * MASTER <-> SLAVE sync: Finished with success

-> success と出ていますね。

マスタに何かsetして、スレーブでgetしてみましょう。
ちゃんと、レプリケーションできているでしょうか?

3) Redisマスタのホスト名を /etc/hosts に記述する。(app1, app2)

# vim /etc/hosts
---
200.200.200.1 redis-master.myservice.com
---

アプリケーションからは、redis-master.myservice.com 宛に接続するようにしておきましょう。

◆ Redis Sentinelのセットアップ

app1サーバでRedis Sentinelを設定します。

sentinel.confファイルを編集。
# vim /etc/redis/sentinel.conf

マスタのIPアドレスを設定、quorumは1とします。
---
sentinel monitor mymaster 127.0.0.1 6379 2
->
sentinel monitor mymaster 200.200.200.1 6379 1
---

以下を追記
---
daemonize yes
pidfile /var/run/redis_sentinel.pid
loglevel notice
logfile /var/log/redis_sentinel.log
sentinel client-reconfig-script mymaster /usr/share/redis_sentinel/master_ip_failover.sh
---

◆ フェイルオーバー時に実行するスクリプトを用意

app1サーバで用意します。
※ rootユーザによるapp2サーバへのssh疎通を要チェックしておきましょう。

rsyncで /etc/hosts を配布するスクリプトを設置します。
このスクリプトが、フェイルオーバー時に実行されます。
# mkdir -p /usr/share/redis_sentinel/
# vim /usr/share/redis_sentinel/master_ip_failover.sh
---
#!/bin/bash
NEW_HOSTS=/usr/share/redis_sentinel/hosts

# app1
cp ${NEW_HOSTS} /etc/hosts

# app2
rsync -avz ${NEW_HOSTS} 100.100.100.2:/etc/hosts
---

パーミッションを変更。
# chmod 755 /usr/share/redis_sentinel/master_ip_failover.sh

フェイルオーバー時に app1,app2 サーバへ配布する /etc/hosts ファイルを設置します。
mysql-master.myservice.com のIPアドレスを redis2 スレーブのモノにします。
# vim /usr/share/redis_sentinel/hosts
---
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
200.200.200.2 redis-master.myservice.com
---

◆ Redis Sentinelをスタート

app1サーバでRedis Sentinelを実行します。
# /usr/bin/redis-server /etc/redis/sentinel.conf --sentinel

プロセスを確認
# pgrep -fl 26379
3921 /usr/bin/redis-server *:26379

ログを確認
# cat /var/log/redis_sentinel.log
[3921] 24 Mar 13:47:15.111 * Max number of open files set to 10032
                _._
           _.-``__ ''-._
      _.-``    `.  `_.  ''-._           Redis 2.8.7 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 3921
  `-._    `-._  `-./  _.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |           http://redis.io
  `-._    `-._`-.__.-'_.-'    _.-'
 |`-._`-._    `-.__.-'    _.-'_.-'|
 |    `-._`-._        _.-'_.-'    |
  `-._    `-._`-.__.-'_.-'    _.-'
      `-._    `-.__.-'    _.-'
          `-._        _.-'
              `-.__.-'

[3921] 24 Mar 13:47:15.112 # Sentinel runid is 4b6ddc264813bafaf9142dcb1f53a7b446e47d75
[3921] 24 Mar 13:47:15.112 # +monitor master mymaster 200.200.200.1 6379 quorum 1
[3921] 24 Mar 13:47:15.114 * +slave slave 200.200.200.2:6379 200.200.200.2 6379 @ mymaster 200.200.200.1 6379

Redis Sentinel起動完了です。

◆ Redisフェイルオーバー実験

redis1サーバを落として、ちゃんとフェイルオーバーするか見てみましょう。
iptablesなどを使って接続できないようにするといいかもしれません。

1)redis1でiptablesを使ってポートを閉じる。

# /etc/init.d/iptables start
iptables: ファイアウォールルールを適用中:                  [  OK  ]

2)app1でRedis Sentinelのログを確認。

15秒くらい待つと、フェイルオーバーが走る。
# tail -f /var/log/redis_sentinel.log
[4015] 24 Mar 14:30:19.127 # +sdown master mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:19.127 # +odown master mymaster 200.200.200.1 6379 #quorum 1/1
[4015] 24 Mar 14:30:19.127 # +new-epoch 3
[4015] 24 Mar 14:30:19.127 # +try-failover master mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:19.127 # +vote-for-leader 1aaecabcb27ff0f1491c64d98d4201162cb7746b 3
[4015] 24 Mar 14:30:19.128 # +elected-leader master mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:19.128 # +failover-state-select-slave master mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:19.194 # +selected-slave slave 200.200.200.2:6379 200.200.200.2 6379 @ mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:19.194 * +failover-state-send-slaveof-noone slave 200.200.200.2:6379 200.200.200.2 6379 @ mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:19.270 * +failover-state-wait-promotion slave 200.200.200.2:6379 200.200.200.2 6379 @ mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:20.187 # +promoted-slave slave 200.200.200.2:6379 200.200.200.2 6379 @ mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:20.187 # +failover-state-reconf-slaves master mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:20.264 # +failover-end master mymaster 200.200.200.1 6379
[4015] 24 Mar 14:30:20.264 # +switch-master mymaster 200.200.200.1 6379 200.200.200.2 6379
[4015] 24 Mar 14:30:20.264 * +slave slave 200.200.200.1:6379 200.200.200.1 6379 @ mymaster 200.200.200.2 6379
[4015] 24 Mar 14:30:50.354 # +sdown slave 200.200.200.1:6379 200.200.200.1 6379 @ mymaster 200.200.200.2 6379

3)redis2がマスタに昇格した事を確認

# redis-cli -p 6379 info | grep role -3
latest_fork_usec:0

# Replication
role:master
connected_slaves:0
master_repl_offset:1852
repl_backlog_active:0

4)app1のhostsが書き変わったことを確認

# cat /etc/hosts
200.200.200.2 redis-master.myservice.com

5)app2のhostsが書き変わったことを確認

# cat /etc/hosts
200.200.200.2 redis-master.myservice.com

フェイルオーバー完了です。
お疲れさまでした!


※ 注意
フェイルオーバーが実行されたあとに、redis1を復活させると、redis1の設定ファイルが書き変わり、スレーブとして起動するようになっています。

redis1
# cat /etc/redis/redis.conf
# Generated by CONFIG REWRITE
slaveof 200.200.200.2 6379
-> redis2がマスター、redis1がスレーブになっている。

redis2
# cat /etc/redis/redis.conf
-> slaveofの記述が削除されている

また、app1のRedis Sentinelの設定ファイルも書き変わっています。
案外、ハマりどころかもしれません。注意しましょう。

◆まとめ

Redis Sentinelをセットアップして、/etc/hostsや、rsyncなどの枯れたモノを使ってRedisをフェイルオーバできるようにしました。
インフラ構成が比較的ちいさなサービスなどで、”RedisのHA構成が欲しい”という要望があったときに、使えたりしそうです。
試してみてください。

参考