Smart Failover With Redis Sentinel and Keepalived

This post is quite old, and may not be up-to-date or maintained. Please take the information here with a grain of salt.

Through some Google-fu and some other great tutorials, I’ve successfully setup a groups of Redis machines with automatic failover detection via keepalived and sentinel. This sounds mundane, but lets you setup another layer of protection for your Redis cluster without lots of extra configuration.

Background – Redis Sentinel and Keepalived

Redis Sentinel makes it pretty easy to setup a group of replicated Redis machines and elect new master nodes when others are offline. Sentinel is nice because it actually checks if the Redis instance is running and responding to requests. This makes it nice for clients, who can now be assured that the master node set by sentinel is truly active.

Many clients support sentinel out of the box, including several PHP clients. These query the sentinel server to check with node is master then connect to the master node. In some cases though, you have to rely on another service, such as keepalived in this case, to float an IP address between the redis nodes so clients can always connect.

The problem arises when Redis fails on a machine, but keepalived is in the dark about the failure. Keepalived in its default configuration only checks if the node’s interface is up, so Redis could be failed on a node or a different node elected master and clients connecting to the virtual IP are connecting to the wrong host.

Solution – Keepalived Monitor Scripts

The solution is elegantly simply and built right into keepalived, monitor scripts. Basically, these are scripts that run periodically and tell keepalived if the current node should be promoted or demoted based on more advanced criteria outside of what keepalived monitors.

To enable this, we first need to create a script that will run and check if a redis instance is the current master of a pool. We do this with a simple shell script

#!/bin/bash

# This is a helper script to check if the current redis instance is the master of the replication scheme.
# This accounts for cases when the local redis instance is down. Since that means this node can't be the master, we handle
# that gracefully.

REPLICATION_DATA=`redis-cli -a $1 info replication 2> /dev/null`

if [ $? -ne 0 ]; then
 exit 1;
fi

# Check the data for master information
echo $REPLICATION_DATA | grep -q 'role:master'

exit $?

Basically, this script just checks the replication status and returns 0 if the node is master, and 1 if the node is not master. For reference, the script is called as check_master redis-cli-password In our case, we require authentication, so we add -a $1 to the redis-cli command to handle it in one go.

Now we need to setup keepalived as we would before.

vrrp_instance redis_failover {
  state BACKUP
  virtual_router_id 100
  interface ens18
  priority 100
  authentication {
     auth_type PASS
     auth_pass ....
  }
  virtual_ipaddress {
    xxx.xxx.xxx.xxx
  }
  track_script {
    redis_failover_track
  }
}

The only real change in that file is the track_script section, which tells keepalived to run the script we setup in the next file.

vrrp_script redis_failover_track {
  script "/usr/bin/redis_is_master ...."
  interval 30
  weight 50
  fall 2
  rise 2
}

This is the config that actually tells keepalived how to handle the script. Basically, we give it which script we’d like to run, set how often we’d like it to run (interval), the change in weight and rise and fall which describe how many failures are required before the node is promoted or demoted.

Theory of Operation

Basically, keepalived uses the script to either promote or demote the server by setting a new priority. When the server is promoted, the value of weight is added to the default priority of 100, making it 150. Since Redis only elects one master at a time, only one node will have a priority of 150 and will become the master of the vrrp pool.

When the script exits with an exit code of 0, the node is promoted. Whenever a non-zero exit code is returned, the node is demoted.

This way, your Redis clients only have to connect to the virtual IP address and they’ll always be connecting to the master node and will be able to issue writes.