From f2b1d402275109b9befc33cadc0afcdcefb88f6d Mon Sep 17 00:00:00 2001 From: mohamedalaa Date: Mon, 29 Jun 2026 12:36:36 +0300 Subject: [PATCH] fix(redis-ha): recover no-master cycle when sentinel names a replica as master After disorderly pod restarts the Sentinel quorum can settle on a master address that points at a node whose role is actually `slave`, leaving the cluster in an all-replicas / no-master cycle with HAProxy `bk_redis_master` serving 0 backends (a write outage). PR #404 (closing #397/#398) only added a `sentinel reset` for the case where `get-master-addr-by-name` returns an EMPTY master. The non-empty case (Sentinel names a real address that answers `role:slave`) instead falls into the `MASTER = ANNOUNCE_IP` branch, which calls `reinit`. For any pod that is not StatefulSet ordinal 0, `reinit` re-runs init.sh (which re-derives `slaveof`) and shuts the pod down, so it restarts straight back into a replica while Sentinel keeps naming it: an endless reinit/shutdown CrashLoop (the "unnecessary master shutdown" of #383) that never elects a master. Fix: when Sentinel names this pod as master but Redis here is a replica, promote it with `replicaof no one` instead of reinit-looping it. Sentinel has already elected this pod, and only one pod can match get-master-addr-by-name, so this cannot create two masters; the other replicas already target this address, so their links recover once it is a real master. This generalizes the self-healing of #404 to the pingable-slave variant. Refs #398, #397, #383. --- charts/redis-ha/Chart.yaml | 2 +- charts/redis-ha/templates/_configs.tpl | 27 +++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/charts/redis-ha/Chart.yaml b/charts/redis-ha/Chart.yaml index d4e21a8..85bbf73 100644 --- a/charts/redis-ha/Chart.yaml +++ b/charts/redis-ha/Chart.yaml @@ -5,7 +5,7 @@ keywords: - redis - keyvalue - database -version: 4.39.0 +version: 4.39.1 appVersion: 8.8.0 description: This Helm chart provides a highly available Redis implementation with a master/slave configuration and uses Sentinel sidecars for failover management icon: https://img.icons8.com/external-tal-revivo-shadow-tal-revivo/24/external-redis-an-in-memory-data-structure-project-implementing-a-distributed-logo-shadow-tal-revivo.png diff --git a/charts/redis-ha/templates/_configs.tpl b/charts/redis-ha/templates/_configs.tpl index 450d129..ee36980 100644 --- a/charts/redis-ha/templates/_configs.tpl +++ b/charts/redis-ha/templates/_configs.tpl @@ -493,6 +493,16 @@ set -e } + promote_self() { + set +e + if [ "$REDIS_PORT" -eq 0 ]; then + redis-cli {{ if .Values.auth }} -a "${AUTH}" --no-auth-warning{{ end }} -p "${REDIS_TLS_PORT}" --tls --cacert /tls-certs/{{ .Values.tls.caCertFile }} {{ if ne (default "yes" .Values.sentinel.authClients) "no"}} --cert /tls-certs/{{ .Values.tls.certFile }} --key /tls-certs/{{ .Values.tls.keyFile }}{{ end }} replicaof no one + else + redis-cli {{ if .Values.auth }} -a "${AUTH}" --no-auth-warning{{ end }} -p "${REDIS_PORT}" replicaof no one + fi + set -e + } + identify_announce_ip while [ -z "${ANNOUNCE_IP}" ]; do @@ -519,10 +529,21 @@ sleep {{ .Values.splitBrainDetection.retryInterval }} identify_master redis_role - echo "Redis role is $ROLE, expected role is master. No need to reinitialize." if [ "$ROLE" != "master" ]; then - echo "Redis role is $ROLE, expected role is master, reinitializing" - reinit + # Sentinel still names this pod as the master, but Redis here is + # running as a replica. reinit re-runs init.sh + shutdown, which + # for any pod that is not StatefulSet ordinal 0 just re-derives + # `slaveof`, so the pod restarts straight back into a replica and + # Sentinel keeps naming it -> an unrecoverable all-replicas / + # no-master cycle plus a shutdown/CrashLoop loop (#383). Sentinel + # has already elected THIS pod, so make reality match by promoting + # it; only one pod can match get-master-addr-by-name, so this + # cannot create two masters, and the other replicas already target + # this address so their links recover once it is a real master. + echo "Redis role is $ROLE but Sentinel names this pod as master; promoting (replicaof no one)" + promote_self + else + echo "Redis role is $ROLE, expected role is master. No need to reinitialize." fi fi elif [ "${MASTER}" ]; then