Redis Sentinel はマスターサーバーの障害を検知し、動的にレプリカをマスターに昇格することできる高可用性ソリューションです。しかし、アプリケーションからの視点では一つ気になることがあります。アプリケーションからどうやってマスターサーバーの IP を知るかです。Redis Sentinel にはクライアントの構築についてドキュメントが公開されています。
この方法を使えば、Sentinel と通信することで、動的にマスターサーバーの IP を知ることができます。しかし、実際にはアプリケーションの仕様を変更したくないケースは多いです。本記事では HAProxy を利用して、アプリケーションの仕様変更を不要とし、常時マスターサーバーに接続できるようにする方法について説明します。
今回紹介する例では、HAProxy が SPOF になります。HAProxyをクラスタ化し IP Sharing 機能を使うことで SPOF を排除できます。IP Sharing については以下の記事を参考にしてください。
Redis Sentinel
Redis Sentinel の環境を用意します。今回は下記の記事で紹介した環境で行います。
アプリケーション (Redis Client) から見える Redis Sentinel
HAProxy を導入しないパターンを説明します。
最初にクライアントはマスターサーバーのIPを静的に保持して接続できています。
マスターサーバーがダウンし、クライアントがマスターサーバーに接続できないケースを考えます。
3 台のマスター・レプリカ構成を取っていた場合、レプリカのどちらがマスターになるかを知る必要があります。そのためには誰かが Sentinel か Redis に問い合わせする必要があります。
本問題を解決するために本記事では HAProxy を使う例を紹介します。
HAProxy を動作するインスタンスの準備
Linode を作成します。
- Tokyo Region
- Ubuntu 22.04 LTS
- Linode 2GB Plan
- Private IP をオンにする (後からでも追加可能)
- Linode Label: haproxy-redis-tokyo
- Tags: Redis Sentinel と同じ
設定した Private IP をメモします。後で利用します。
localhost:~# ip -4 a show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
inet 172.104.79.130/24 brd 172.104.79.255 scope global eth0
valid_lft forever preferred_lft forever
inet {PUBLIC_IP_ADDRESS}/17 brd 192.168.255.255 scope global eth0
valid_lft forever preferred_lft forever
インスタンスがセキュアになるように設定します。
Redis Server に接続できるように /etc/hosts
に IP アドレスを登録します。
# BEGIN redis servers
# Redis
192.168.198.87 redissentinel-tokyo1 redis_1
192.168.198.103 redissentinel-tokyo2 redis_2
192.168.198.112 redissentinel-tokyo3 redis_3
# END redis servers
Redis Server の設定
HAProxy がアクセスできるように firewalld の設定をします。
<source address="{PUBLIC_IP_ADDRESS}"/>
最終的には全ての Redis Server が動作している Linux で設定する必要がありますが、まずは、マスターのサーバー環境で実施します。
systemctl restart firewalld
HAProxy のインストール
www.linode.com のドキュメントを参照して、HAProxy をインストールします。
apt update && apt upgrade
sudo apt-get install haproxy
インストールに成功したらバージョンを確認します。
haproxy-redis-tokyo:/etc/haproxy# haproxy -v
HAProxy version 2.4.22-0ubuntu0.22.04.1 2023/03/22 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-2.4.22.html
Running on: Linux 5.15.0-73-generic #80-Ubuntu SMP Mon May 15 15:18:26 UTC 2023 x86_64
HAProxy の Config
/etc/haproxy/
にコンフィグファイルがあります。
haproxy-redis-tokyo:~# cd /etc/haproxy/
haproxy-redis-tokyo:/etc/haproxy# ls
errors haproxy.cfg
haproxy.cfg
を確認します。このファイルに追記することになります。
haproxy-redis-tokyo:/etc/haproxy# cat haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
Redis 用の設定
以下の設定を追記します。この例ではマスターとして動作している redis_2 を登録する例です。
# Specifies TCP timeout on connect for use by the frontend ft_redis
# Set the max time to wait for a connection attempt to a server to succeed
# The server and client side expected to acknowledge or send data.
defaults REDIS
mode tcp
timeout connect 3s
timeout server 6s
timeout client 6s
# Specifies listening socket for accepting client connections using the default
# REDIS TCP timeout and backend bk_redis TCP health check.
frontend ft_redis
bind *:6379 name redis
default_backend bk_redis
# Specifies the backend Redis proxy server TCP health settings
# Ensure it only forward incoming connections to reach a master.
backend bk_redis
server redis_6379 redis_2:6379 check inter 1s
Stats ページのための設定
この設定を入れると HAProxy がフォワードする Redis サーバーの状態を表示できます。後ほど使用する例を紹介します。
#
listen stats
mode http
bind {HAproxy の Public IP}:7000
stats uri /
stats auth {ユーザー名}:{パスワード}
stats refresh 10s
stats show-legends
stats auth
には Stats にアクセスするためのクレデンシャルとなります。
使わないときはコメントアウトしてオフにしましょう。
redis-cli
設定を確認するために redis-cli を入手します。
apt install redis
エラーがでました。
haproxy-redis-tokyo:/etc/haproxy# apt install redis
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
E: Unable to locate package redis-cli
以下のコマンドの実行で解決できました。
add-apt-repository universe
再度インストールを試みます。
apt install redis
redis-server も同時にインストールされ、プロセスが起動します。不要なので停止してください。手順は後ほど説明します。
インストールされたら、バージョンを確認します。
haproxy-redis-tokyo:~# redis-cli -v
redis-cli 6.0.16
redis-cli は TLS 接続する必要があるので、Redis Server の /etc/redis/tls/ca.crt
をローカルのファイルにコピーします。
haproxy-redis-tokyo:/etc/haproxy# pwd
/etc/haproxy
haproxy-redis-tokyo:/etc/haproxy# ls -l tls
total 4
-r--r----- 1 haproxy haproxy 2029 Jul 1 02:49 redissentinel-tokyo_ca.crt
REdISCLI_AUTH
も環境変数に設定します。
export REDISCLI_AUTH=`Redis に接続するためのパスワード`
現在マスターが redis_2 のホストとして登録しているので、-h
で指定します。証明書は事前にコピーしたファイルを指定します。ここでは redis-cli の動作確認をするだけで、HAProxy 経由での接続確認ではありません。
haproxy-redis-tokyo:~# redis-cli -h redis_2 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt
redis_2:6379> ping
PONG
PING PONG ができれば、認証された接続に成功しています。
書き込みができるかも確認します。
haproxy-redis-tokyo:~# redis-cli -h redis_2 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt
redis_2:6379> get scott
"tiger"
redis_2:6379> set scott lion
OK
redis_2:6379> get scott
"lion"
scott のキーの値を変更できました。これは、クライアントがマスターサーバーに接続できているからです。
HAProxy 上で Redis-server の停止
redis-cli
を使うために Redis をインストールしましたが、Redis サーバーが動いていると後ほど問題が起きる可能性がありますので、停止します。
systemctl stop redis-server
systemctl disable redis-server
実際に発生した問題は、HAProxy の Bind 設定との衝突です。今回は実施しませんが、HAProxy を冗長化したときに、redis-server が 6379
ポートを Listen していると、次のような bind 設定がエラーとなって HAProxy が起動できません。
bind *:6379
そのため、特定の IP を指定する必要がありました。例えば次のように設定する必要がありました。
bind {Server の IP address}:6379
このように設定することで問題は解消されるのですが、別の記事で紹介する keepalive
を使った VIP の設定では、バックアップとして動作するサーバー側ではその VIP が動いていないので、HAProxy 起動時に VIP でバインドすることができません。よって keepalived
でフェールオーバーしても、VIP にバインドされていないので HAProxy につなぐことができませんでした。これも HAProxy で redis-server
を停止することで bind *:6379
のように設定も楽になり問題も解消されます。
HAProxy に監視の機能を入れる
external-check
HAProxy の external-check
は外部のコマンドを起動してステータス監視をするときに便利な機能です。この機能を利用し、スクリプトを登録します。スクリプトの中では、以下のコマンドを起動し、どのサーバーがマスターなのかを常に監視します。
tcp-check
を使う方法もありますが、TLS で接続する必要があるため、external-check
を使います。
haproxy-redis-tokyo:~# /usr/bin/redis-cli --no-auth-warning -a $password -h redis_1 -p 6379 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt INFO REPLICATION | egrep role
role:slave
haproxy-redis-tokyo:~# /usr/bin/redis-cli --no-auth-warning -a $password -h redis_2 -p 6379 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt INFO REPLICATION | egrep role
role:master
exernal_check で指定するスクリプトを配置します。
haproxy-redis-tokyo:/etc/haproxy# ls -l bin
total 4
-rwxr-x--- 1 haproxy haproxy 459 Jul 1 12:52 check_redis_master.sh
external-check で起動されるスクリプトです。
#!/bin/bash
password='YOUR_REDIS_SERVER_PASSWORD'
ip_adr=$(echo $@ | awk '{print$3}')
command='INFO REPLICATION'
redis_server_role=$(/usr/bin/redis-cli --no-auth-warning -a $password -h $ip_adr -p 6379 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt $command | grep role | awk -F ':' '{print $2}' | tr -d '\r')
if [[ $redis_server_role == 'master' ]]; then
exit 0
else
exit 1
fi
haproxy.cfg
haproxy.cfg の global を次のように変更します。insecure-fork-wanted
、insecure-setuid-wanted
、external-check
を追記します。
global
insecure-fork-wanted
insecure-setuid-wanted
external-check
log /dev/log local0
log /dev/log local1 notice
# chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
backend の設定を次のように external-check
を使うように設定します。
# Specifies the backend Redis proxy server TCP health settings
# Ensure it only forward incoming connections to reach a master.
backend bk_redis
option external-check
external-check path "/usr/bin:/bin:/usr/local/bin"
external-check command /etc/haproxy/bin/check_redis_master.sh
server redis_6379 redis_2:6379 check inter 10s
HAProxy を再起動します。
systemctl restart haproxy
/var/log/haproxy.log
には redis_2 に対してステータスを確認しているログが表示されています。
haproxy-redis-tokyo:/etc/haproxy# tail -f /var/log/haproxy.log
Jul 1 04:22:33 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:22:33.071] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:22:43 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:22:43.143] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:22:53 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:22:53.255] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:23:03 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:23:03.337] stats stats/<STATS> 0/0/0/0/0 200 17810 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:23:13 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:23:13.435] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:23:23 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:23:23.539] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:23:33 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:23:33.626] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:23:43 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:23:43.698] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:23:53 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:23:53.802] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:24:03 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:24:03.879] stats stats/<STATS> 0/0/0/0/0 200 17810 - - LR-- 2/2/0/0/0 0/0 "GET / HTTP/1.1"
Jul 1 04:24:13 haproxy-redis-tokyo haproxy[15891]: XX.XX.XX.XX:64282 [01/Jul/2023:04:24:13.954] stats stats/<STATS> 0/0/0/0/0 200 17812 - - LR-- 1/1/0/0/0 0/0 "GET / HTTP/1.1"
redis-cli も動作しています。
haproxy-redis-tokyo:~# redis-cli -h redis_2 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt
redis_2:6379> ping
PONG
redis_2:6379> get scott
"lion"
redis_2:6379> set dixson
(error) ERR wrong number of arguments for 'set' command
redis_2:6379> set scott dixson
OK
redis_2:6379> get scott
"dixson"
redis_2:6379> quit
Stats
http://{HAProxy サーバーの PublicIP}:7000/
にアクセスします。次のようなページが表示されます。まずは、表示されたことを確認してください。haproxy.cfg
の auth
で指定したユーザー名とパスワードを入力してください。
次に haproxy.cfg
に全ての Redis サーバーを登録します。次のように書き換えます。
変更前
server redis_6379 redis_2:6379 check inter 10s
変更後
server redis_6379_1 redis_1:6379 check inter 1s
server redis_6379_2 redis_2:6379 check inter 1s
server redis_6379_3 redis_3:6379 check inter 1s
Redis Server の Firewall 設定も忘れないでください。
HAProxy をリスタートします。
systemctl restart haproxy
Stat をブラウザで見ます。bk_redis の表に3つのサーバーの状態が見えます。
マスターとレプリカはそれぞれ、緑と赤で表示されています。レプリカのサーバーは実際には落ちているわけではなく、先程登録した external-check
で動いているスクリプトでステータス 1
で返るようにしているからです。HAProxy からはステータス 0
で返るマスターサーバーが有効としてフォワードしています。
Master の切り替え
HAProxy サーバーのクライアントからの確認
マスターとして動作している Redis_2 のサーバーを止めます。
systemctl stop redis-server
HAProxy のホストの Redis-cli を使って接続します。接続されたサーバーは role:master となっています。
haproxy-redis-tokyo:~# redis-cli --tls --cacert redissentinel-tokyo_ca.crt info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.198.87,port=6379,state=online,offset=29561919,lag=1
master_failover_state:no-failover
master_replid:a284a16594fa8ce12b0db3b2d52eb4a185132ed5
master_replid2:4831258c3aa4c7d8d6c9e0eb1244acb86c511c8a
master_repl_offset:29562064
second_repl_offset:29497534
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:28493428
repl_backlog_histlen:1068637
最終的には、以下のように HAProxy は Redis_3 にフォワードしています。
Stats は以下のように見えます。Redis_3 が緑色になっています。
Redis Client アプリケーションからの接続
先程までは、HAProxy のサーバー内から接続していましたが、ここからはリモートにある Redis Client から接続を確認します。
/etc/hosts
に HAProxy の Private IP を登録します。
{PUBLIC_IP_ADDRESS} redis-haproxy
REDISCLI_AUTH
環境変数をセットします。Redis Sentinel がインストールされたときに作成されたユーザーのホームディレクトリ配下に作成された deployment-secrets.txt
に記載されている redis password が該当します。
HAproxy に接続します。
redisclient:~# redis-cli -h redis-haproxy --tls --cacert redissentinel-tokyo_ca.crt ping
PONG
HAProxy経由では必ず Master ロールのサーバーに接続されます。
redisclient:~# redis-cli -h redis-haproxy --tls --cacert redissentinel-tokyo_ca.crt info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.198.87,port=6379,state=online,offset=29619571,lag=1
master_failover_state:no-failover
master_replid:a284a16594fa8ce12b0db3b2d52eb4a185132ed5
master_replid2:4831258c3aa4c7d8d6c9e0eb1244acb86c511c8a
master_repl_offset:29619585
second_repl_offset:29497534
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:28554748
repl_backlog_histlen:1064838
redisclient:~# redis-cli -h redis-haproxy --tls --cacert redissentinel-tokyo_ca.crt role
1) "master"
2) (integer) 29621755
3) 1) 1) "192.168.198.87"
2) "6379"
3) "29621321"
止めていた Redis_2 の redis-server を起動します。
redissentinel-tokyo2:~# systemctl start redis-server
マスターではなくレプリカとして起動していることも分かります。
haproxy-redis-tokyo:~# /usr/bin/redis-cli --no-auth-warning -a $password -h redis_2 -p 6379 --tls --cacert /etc/haproxy/tls/redissentinel-tokyo_ca.crt INFO REPLICATION | egrep role
role:slave
マスターサーバーの停止
Redis Client で redis-cli を HAproxy に接続した状態にします。
redisclient:~# redis-cli -h redis-haproxy --tls --cacert redissentinel-tokyo_ca.crt
redis-haproxy:6379> ping
PONG
redis-haproxy:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.198.87,port=6379,state=online,offset=29954946,lag=0
slave1:ip=192.168.198.103,port=6379,state=online,offset=29954946,lag=0
master_failover_state:no-failover
master_replid:a284a16594fa8ce12b0db3b2d52eb4a185132ed5
master_replid2:4831258c3aa4c7d8d6c9e0eb1244acb86c511c8a
master_repl_offset:29955235
second_repl_offset:29497534
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:28902228
repl_backlog_histlen:1053008
マスターとして動作している Redis_3 を停止します。
redissentinel-tokyo3:~# systemctl stop redis-server
マスターが変わったことを確認します。今回再度 Redis_2 がマスターになりました。
Redis Client にて redis-cli で接続した状態から再度コマンドを実行します。
redis-haproxy:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=192.168.198.87,port=6379,state=online,offset=29971306,lag=1
master_failover_state:no-failover
master_replid:fbb1e8bf9574889409d0bdfdcb78d4d56387877b
master_replid2:a284a16594fa8ce12b0db3b2d52eb4a185132ed5
master_repl_offset:29971306
second_repl_offset:29967061
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:29765770
repl_backlog_histlen:205537
redis-haproxy:6379> role
1) "master"
2) (integer) 29974806
3) 1) 1) "192.168.198.87"
2) "6379"
3) "29974806"
redis-cli は接続した状態でマスターの IP が変更されていることが分かります。
Redis-server のマスターがダウンしたときにログやファイルにどのように記録されているかは下記を参考にしてください。
まとめ
Redis Sentinel と HAProxy を使うことで、Redis Server の高可用性を保ちながら、アプリケーションは常に Redis のマスターサーバーに接続できることを確認しました。
HAProxy を H/A 構成にすれば、SPOF を排除できますので、以下の記事も参考にしてください。