How Redis Pods Find the Master using init containers (Master Election and Failover) - Part 1

Redis replication depends on one node being treated as the master, while the remaining Redis nodes act as replicas. If that master role is assigned incorrectly, especially when pods are starting, restarting, or recovering from a failure, the cluster can easily end up in an inconsistent state.

For example, imagine three Redis pods starting at the same time:

redis-0 redis-1 redis-2

If all three simply started as independent Redis servers without knowing their role, we could temporarily end up with:

redis-0 → MASTER redis-1 → MASTER redis-2 → MASTER

That is not the topology we want.

Normally we want something closer to:

redis-0 MASTER / \ redis-1 redis-2 REPLICA REPLICA

And later, if redis-0 fails, Sentinel may promote one of the replicas:

redis-0 → DOWN redis-1 → REPLICA redis-2 → MASTER

At that point, redis-0 is no longer the source of truth for who the master is.

This creates an important startup problem.

A Redis pod may need to answer:

Who is the master right now?

During the very first deployment, Sentinel may not even be running yet. In that case, the Redis pods still need some predictable way to bootstrap the cluster.

But after the cluster is already running and Sentinel has performed a failover, blindly assuming that redis-0 is always the master would be wrong.

That is exactly why the Redis init container in this setup exists.

What Is an Init Container?

An init container runs first, executes in a strict step-by-step order and stops when only exited with a success code before the main application starts.

Suppose our Redis StatefulSet creates three pods:

redis-0 redis-1 redis-2

Each pod gets its own init container.

So it's not:

StatefulSet | └── one init container | ├── redis-0 ├── redis-1 └── redis-2

Instead, it's:

redis-0 ├── init container └── Redis container redis-1 ├── init container └── Redis container redis-2 ├── init container └── Redis container

For every pod, Kubernetes follows roughly this sequence:

Pod created ↓ Init container starts ↓ Init container does setup work ↓ Init container exits successfully ↓ Main container starts

So if we're talking about redis-2:

redis-2 pod created ↓ redis-2's init container runs ↓ redis.conf is prepared ↓ init container finishes ↓ actual Redis server starts

If the pod gets recreated later, its init container runs again.

That's important because the Redis init container can make a new decision about who the current master is every time a Redis pod starts.

In our case Redis StatefulSet has three Redis pods:

redis-0 redis-1 redis-2

Normally we want something like:

redis-0 MASTER / \ / \ redis-1 redis-2 REPLICA REPLICA

We also have three Sentinel pods:

sentinel-0 sentinel-1 sentinel-2

Their job is to monitor Redis.

So overall:

redis-0 MASTER / \ redis-1 redis-2 REPLICA REPLICA ▲ │ monitor │ │ Sentinel sentinel-0 sentinel-1 sentinel-2

The interesting question is:

How does each Redis pod know whether it should start as a master or replica?

That's where the Redis init container comes in.

Redis Init Container

Here is the important script:

cp /tmp/redis/redis.conf /etc/redis/redis.conf MY_FQDN="$(hostname -f)" SEED_MASTER="$(hostname -f | sed -e 's/redis-[0-9]./redis-0./')" # announce by stable FQDN so Sentinel never tracks an ephemeral pod IP echo "replica-announce-ip $MY_FQDN" >> /etc/redis/redis.conf echo "finding master..." if [ "$(redis-cli -h sentinel -p 5000 ping 2>/dev/null)" != "PONG" ]; then echo "sentinel unreachable -> cold-start default" if [ "$(hostname)" = "redis-0" ]; then echo "this is redis-0: starting as master" else echo "replicaof $SEED_MASTER 6379" >> /etc/redis/redis.conf fi else MASTER="$(redis-cli -h sentinel -p 5000 sentinel get-master-addr-by-name mymaster | head -n1)" echo "sentinel reports master: '$MASTER'" if [ -z "$MASTER" ]; then [ "$(hostname)" = "redis-0" ] || echo "replicaof $SEED_MASTER 6379" >> /etc/redis/redis.conf elif [ "$MASTER" = "$MY_FQDN" ]; then echo "this is the current master: starting clean (no replicaof)" else echo "replicaof $MASTER 6379" >> /etc/redis/redis.conf fi fi

This looks intimidating, but the entire thing is asking one question:

Who should I replicate from?

If the answer is "nobody, because I am the master," it doesn't add a replicaof line.

If another Redis pod is master, it adds:

replicaof <master-address> 6379

Let's go through it line by line.

First:

cp /tmp/redis/redis.conf /etc/redis/redis.conf

This means:

Take our starting Redis configuration and make a working copy.

Conceptually:

/tmp/redis/redis.conf | | copy ↓ /etc/redis/redis.conf

Why copy it?

Because the init container wants to dynamically add settings to the configuration before Redis starts.

The main Redis container later starts with:

redis-server /etc/redis/redis.conf

So /etc/redis/redis.conf is the important final configuration.

Next:

MY_FQDN="$(hostname -f)"

hostname -f asks Linux for the pod's fully qualified hostname.

Suppose we're inside redis-2.

It might return something like:

redis-2.redis.redis.svc.cluster.local

So now our variable contains:

MY_FQDN=redis-2.redis.redis.svc.cluster.local

In simple terms:

MY_FQDN = "my own address"

If this same script runs inside redis-1, it gets redis-1's address instead.

Remember: the exact same init script runs inside every Redis pod.

The result is different because each pod has a different hostname.

Next comes the slightly ugly-looking line:

SEED_MASTER="$(hostname -f | sed -e 's/redis-[0-9]\./redis-0./')"

This does not rename the pod.

That's worth repeating:

It does not rename redis-2 to redis-0.

It's only manipulating a string.

Suppose we're inside:

redis-2

and:

hostname -f

returns:

redis-2.redis.redis.svc.cluster.local

The sed command changes the redis-2. part of that string to redis-0..

So:

redis-2.redis.redis.svc.cluster.local

becomes

redis-0.redis.redis.svc.cluster.local

The pod itself is still:

redis-2

We simply created a variable containing redis-0's address:

MY_FQDN = redis-2.redis.redis.svc.cluster.local SEED_MASTER = redis-0.redis.redis.svc.cluster.local

Why do we need this?

Because if Sentinel isn't available yet, the setup needs some initial rule for deciding who should be master.

The rule chosen here is:

If we don't know who the master is, use redis-0 as the starting master.

That's why the variable is called SEED_MASTER.

Next:

echo "replica-announce-ip $MY_FQDN" >> /etc/redis/redis.conf

Suppose we're inside redis-2.

This adds something like:

replica-announce-ip redis-2.redis.redis.svc.cluster.local

to redis.conf.

The >> means:

Append this line to the end of the file.

Why do this?

Kubernetes pod IPs can change.

For example, redis-2 could currently have:

10.42.1.17

After the pod is recreated, it might get:

10.42.3.24

But its StatefulSet DNS identity remains stable:

redis-2.redis.redis.svc.cluster.local

So this setup wants Redis/Sentinel to work with that stable hostname rather than depending on an ephemeral pod IP.

Next:

echo "finding master..."

That's just a log message.

Then:

redis-cli -h sentinel -p 5000 ping

This is basically the Redis pod asking:

"Hey Sentinel, are you there?"

If Sentinel is alive, it should return:

PONG

So this condition:

if [ "$(redis-cli -h sentinel -p 5000 ping 2>/dev/null)" != "PONG" ]; then

means:

If Sentinel does NOT answer with PONG, run the cold-start logic.

The:

2>/dev/null

just hides error output from the failed connection attempt.

Now we have two possible paths:

Can I contact Sentinel? | +---+---+ | | NO YES | | fallback ask Sentinel logic who master is

Suppose Redis starts before Sentinel.

The script reaches:

echo "sentinel unreachable -> cold-start default"

Now we have a problem.

We can't ask Sentinel:

"Who is the master?"

because Sentinel isn't running.

So we use our fallback rule:

redis-0 is the initial master.

The script checks:

if [ "$(hostname)" = "redis-0" ]; then

Meaning:

Am I redis-0?

If the hostname is:

redis-0

the script does:

echo "this is redis-0: starting as master"

Notice something important:

It doesn't actually run a command saying:

MAKE_ME_MASTER

There isn't one.

Instead, it simply does not add a replicaof setting.

So the final Redis configuration does not contain:

replicaof ...

Redis therefore starts without being configured as anyone's replica.

In this setup, that means it starts as the master.

Now suppose the script is running inside redis-2.

This condition:

if [ "$(hostname)" = "redis-0" ]; then

is false.

So it goes into:

else

and runs:

echo "replicaof $SEED_MASTER 6379" >> /etc/redis/redis.conf

Remember:

SEED_MASTER = redis-0.redis.redis.svc.cluster.local

So the actual line added to redis.conf is roughly:

replicaof redis-0.redis.redis.svc.cluster.local 6379

That tells Redis:

I am a replica. Connect to redis-0 and replicate from it.

Again, redis-2 does not become or rename itself to redis-0.

It remains:

redis-2

It's simply configured to follow:

redis-0

What If All Redis Pods Start Before Sentinel?

This was an interesting scenario we discussed.

Imagine Sentinel isn't running yet.

redis-0 runs its init container:

Sentinel available? NO Am I redis-0? YES → Don't add replicaof → Start as master

Then redis-1:

Sentinel available? NO Am I redis-0? NO → Add replicaof redis-0 → Start as replica

Then redis-2:

Sentinel available? NO Am I redis-0? NO → Add replicaof redis-0 → Start as replica

We end up with:

redis-0 MASTER / \ / \ redis-1 redis-2 REPLICA REPLICA

So Redis can bootstrap even when Sentinel hasn't started yet.

That's exactly why the SEED_MASTER fallback exists.

What If redis-2 Starts Before redis-0?

Conceptually, if redis-2 runs this cold-start logic while redis-0 isn't available yet, it still configures itself with:

replicaof redis-0.redis.redis.svc.cluster.local 6379

That does not mean redis-2 becomes redis-0.

It means:

I am redis-2.

My master should be redis-0.

I'll try to connect to it.

So conceptually:

redis-2 starts ↓ config says: "replicaof redis-0" ↓ tries redis-0 ↓ redis-0 isn't available ↓ cannot replicate yet ↓ redis-0 eventually becomes available ↓ redis-2 connects ↓ replication can begin

In the supplied StatefulSet, podManagementPolicy is OrderedReady, so normal StatefulSet creation is intended to bring up the pods in ordinal order rather than starting redis-2 first. But the distinction above is still useful for understanding what the script itself does.

What Changes Once Sentinel Exists?

Now imagine Sentinel is running.

This check:

redis-cli -h sentinel -p 5000 ping

returns:

PONG

So instead of using the redis-0 fallback, we go into the second half of the script.

This is where things become more interesting.

Ask Sentinel Who the Current Master Is

The script runs:

MASTER="$(redis-cli -h sentinel -p 5000 sentinel get-master-addr-by-name mymaster | head -n1)"

This looks complicated, but conceptually it's:

Redis → Sentinel

"Who is the current master for mymaster?"

Sentinel might respond with:

redis-2.redis.redis.svc.cluster.local 6379

The:

head -n1

takes only the first line.

So we get:

MASTER=redis-2.redis.redis.svc.cluster.local

Then:

echo "sentinel reports master: '$MASTER'"

simply prints that information into the init-container logs.

What If Sentinel Doesn't Return a Master?

Next:

if [ -z "$MASTER" ]; then

-z checks whether the string is empty.

So this means:

Sentinel is reachable, but it didn't give me a master address.

The script then runs:

[ "$(hostname)" = "redis-0" ] || echo "replicaof $SEED_MASTER 6379" >> /etc/redis/redis.conf

This is just a shorter shell way of saying:

If I am redis-0: don't add replicaof Otherwise: use redis-0 as the fallback master

So again:

redis-0 → master redis-1 → replica of redis-0 redis-2 → replica of redis-0

What If Sentinel Says "You Are the Master"?

Next:

elif [ "$MASTER" = "$MY_FQDN" ]; then

Suppose this script is running inside redis-2.

We have:

MY_FQDN = redis-2.redis.redis.svc.cluster.local

Sentinel also says:

MASTER = redis-2.redis.redis.svc.cluster.local

They match.

Therefore:

Sentinel says redis-2 is master.

I am redis-2.

Therefore I am the current master.

The script prints:

echo "this is the current master: starting clean (no replicaof)"

Again, notice what it doesn't do.

It doesn't add:

replicaof ...

So Redis starts as the master.

What If Sentinel Says Someone Else Is Master?

Finally:

else echo "replicaof $MASTER 6379" >> /etc/redis/redis.conf fi

Suppose we're starting redis-1.

Our identity is:

MY_FQDN = redis-1...

But Sentinel says:

MASTER = redis-2...

The script therefore writes:

replicaof redis-2.redis.redis.svc.cluster.local 6379

into redis.conf.

Meaning:

redis-1 | | replicate from ↓ redis-2

This is what makes the script useful after a failover.

Why Ask Sentinel Instead of Always Using redis-0?

Imagine the cluster originally looks like:

redis-0 MASTER redis-1 REPLICA redis-2 REPLICA

Then redis-0 fails.

Sentinel performs a failover and promotes redis-2.

Now:

redis-0 DOWN redis-1 REPLICA redis-2 MASTER

Later redis-1 restarts.

If its init script blindly assumed:

redis-0 is always master

it would configure itself incorrectly.

Instead it asks Sentinel:

Who is master?

Sentinel says:

redis-2

So redis-1 gets:

replicaof redis-2... 6379

That's the important difference between:

SEED_MASTER

and:

MASTER

SEED_MASTER means:

"Our fallback starting point when Sentinel can't tell us anything."

MASTER means:

"The master Sentinel currently knows about."

Now Let's Look at the Sentinel Init Container

#k8s #redis #sentinel

0 Comments