Skip to content

Commit 6464ca2

Browse files
lmicciniclaude
andcommitted
Reject stale master identity when rejoining redis cluster after restart
When the former-master pod restarts after deletion, peer sentinels respond to "sentinel master redis" immediately but still report the restarting pod as master (within the down-after-milliseconds window). Both the redis and sentinel containers would accept this stale answer and configure themselves as master-of-self, creating a split-brain. Consolidate peer discovery, retry, and stale-master detection into a single wait_for_master() function in common.sh. If any peer sentinel reports us as master, the function skips that answer and keeps retrying until failover completes and a different master is elected. This also removes the duplicated retry logic from both startup scripts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a99ba4d commit 6464ca2

3 files changed

Lines changed: 43 additions & 52 deletions

File tree

templates/redis/bin/common.sh

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,25 +84,40 @@ function remove_pod_label() {
8484
configure_pod_label $pod "$patch" "(200|422)"
8585
}
8686

87-
# Contact peer sentinels to discover an existing cluster.
88-
# Tries each peer pod individually by FQDN (skipping self) to avoid
89-
# connecting to our own uninitialized sentinel through the headless
90-
# service DNS, which can resolve to any pod including ourselves.
91-
# Prints the sentinel master output on success.
92-
function discover_master() {
87+
# Wait for a peer sentinel to report a valid master for the cluster.
88+
# Contacts each peer pod individually by FQDN (skipping self) to avoid
89+
# the headless service DNS resolving to our own uninitialized sentinel.
90+
# If a peer still reports US as master (stale info before
91+
# down-after-milliseconds triggers failover), keeps retrying until
92+
# failover completes and a different master is elected.
93+
# Prints the master address on success (FQDN or IP).
94+
function wait_for_master() {
95+
local retries=${SENTINEL_RETRIES:-10}
96+
local delay=${SENTINEL_RETRY_DELAY:-3}
9397
local pod_ordinal=${POD_NAME##*-}
9498
local pod_base=${POD_NAME%-*}
95-
local ordinal=0
96-
while [ $ordinal -le 9 ]; do
97-
if [ "$ordinal" != "$pod_ordinal" ]; then
98-
local peer="${pod_base}-${ordinal}.${SVC_FQDN}"
99-
local output
100-
output=$(timeout ${TIMEOUT} $REDIS_CLI_CMD -h ${peer} -p 26379 sentinel master redis 2>/dev/null) && {
101-
echo "$output"
102-
return 0
103-
}
104-
fi
105-
ordinal=$((ordinal + 1))
99+
100+
for i in $(seq 1 $retries); do
101+
local ordinal=0
102+
while [ $ordinal -le 9 ]; do
103+
if [ "$ordinal" != "$pod_ordinal" ]; then
104+
local peer="${pod_base}-${ordinal}.${SVC_FQDN}"
105+
local output
106+
output=$(timeout ${TIMEOUT} $REDIS_CLI_CMD -h ${peer} -p 26379 sentinel master redis 2>/dev/null) && {
107+
local master
108+
master=$(echo "$output" | awk '/^ip$/ {getline; print $0; exit}')
109+
# If the peer still thinks WE are master, it has stale
110+
# pre-failover info — try remaining peers before waiting.
111+
if ! echo "$master" | grep -q "^${POD_NAME}\."; then
112+
echo "$master"
113+
return 0
114+
fi
115+
}
116+
fi
117+
ordinal=$((ordinal + 1))
118+
done
119+
log "Attempt $i/$retries: no valid master found, retrying in ${delay}s..."
120+
sleep $delay
106121
done
107122
return 1
108123
}

templates/redis/bin/start_redis_replication.sh

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,11 @@ generate_configs
66
sudo -E kolla_set_configs
77

88
# 1. check if a redis cluster is already running by contacting peer sentinels
9-
# Try each peer pod individually (skipping self) to avoid the race where
10-
# the headless service DNS resolves to our own pod, whose sentinel isn't
11-
# ready yet. Retry multiple times so that peers have time to become
12-
# reachable while still allowing fresh cluster bootstrap when no sentinel
13-
# exists at all.
14-
SENTINEL_RETRIES=${SENTINEL_RETRIES:-10}
15-
SENTINEL_RETRY_DELAY=${SENTINEL_RETRY_DELAY:-3}
16-
for i in $(seq 1 $SENTINEL_RETRIES); do
17-
output=$(discover_master)
18-
if [ $? -eq 0 ]; then
19-
master=$(echo "$output" | awk '/^ip$/ {getline; print $0; exit}')
20-
log "Connecting to the existing Redis cluster (master: ${master})"
21-
exec redis-server $REDIS_CONFIG --protected-mode no --replicaof "$master" 6379
22-
fi
23-
log "Attempt $i/$SENTINEL_RETRIES: could not contact any peer sentinel, retrying in ${SENTINEL_RETRY_DELAY}s..."
24-
sleep $SENTINEL_RETRY_DELAY
25-
done
9+
master=$(wait_for_master)
10+
if [ $? -eq 0 ]; then
11+
log "Connecting to the existing Redis cluster (master: ${master})"
12+
exec redis-server $REDIS_CONFIG --protected-mode no --replicaof "$master" 6379
13+
fi
2614

2715
# 2. else bootstrap a new cluster (assume we should be the first redis pod)
2816
if is_bootstrap_pod $POD_NAME; then

templates/redis/bin/start_sentinel.sh

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,12 @@ generate_configs
66
sudo -E kolla_set_configs
77

88
# 1. check if a redis cluster is already running by contacting peer sentinels
9-
# Try each peer pod individually (skipping self) to avoid the race where
10-
# the headless service DNS resolves to our own pod, whose sentinel isn't
11-
# ready yet. Retry multiple times so that peers have time to become
12-
# reachable while still allowing fresh cluster bootstrap when no sentinel
13-
# exists at all.
14-
SENTINEL_RETRIES=${SENTINEL_RETRIES:-10}
15-
SENTINEL_RETRY_DELAY=${SENTINEL_RETRY_DELAY:-3}
16-
for i in $(seq 1 $SENTINEL_RETRIES); do
17-
output=$(discover_master)
18-
if [ $? -eq 0 ]; then
19-
master=$(echo "$output" | awk '/^ip$/ {getline; print $0; exit}')
20-
log "Connecting to the existing sentinel cluster (master: $master)"
21-
echo "sentinel monitor redis ${master} 6379 ${SENTINEL_QUORUM}" >> $SENTINEL_CONFIG
22-
exec redis-sentinel $SENTINEL_CONFIG
23-
fi
24-
log "Attempt $i/$SENTINEL_RETRIES: could not contact any peer sentinel, retrying in ${SENTINEL_RETRY_DELAY}s..."
25-
sleep $SENTINEL_RETRY_DELAY
26-
done
9+
master=$(wait_for_master)
10+
if [ $? -eq 0 ]; then
11+
log "Connecting to the existing sentinel cluster (master: $master)"
12+
echo "sentinel monitor redis ${master} 6379 ${SENTINEL_QUORUM}" >> $SENTINEL_CONFIG
13+
exec redis-sentinel $SENTINEL_CONFIG
14+
fi
2715

2816
# 2. else let the pod's redis server bootstrap a new cluster and monitor it
2917
# (assume we should be the first redis pod)

0 commit comments

Comments
 (0)