Resumo rápido: neste guia você monta um Redis altamente disponível no Kubernetes usando StatefulSet + Sentinel + HAProxy, com failover automático e um endpoint estável para as aplicações.
Atualizado em setembro de 2026: incluí os manifests completos do Sentinel e do HAProxy (com autenticação e resolução DNS dos pods), que faltavam na primeira versão.
Cenário real: o que quebra primeiro
Se você roda Redis em produção, cedo ou tarde vai acontecer:
- um nó cai;
- um pod reinicia no pior horário possível;
- a aplicação começa a tomar timeout.
A boa notícia é que dá para reduzir esse caos com uma arquitetura simples e pragmática:
- Redis em StatefulSet: identidade e volume persistente por pod;
- Sentinel: detecta a falha e promove uma réplica;
- HAProxy: entrega um endpoint estável e aponta sempre para o master ativo.
Ou seja: a aplicação continua falando com o mesmo host e porta, e “quem é o master” vira problema da infraestrutura, como deveria ser.
Arquitetura
- 3 pods Redis (
redis-0,redis-1,redis-2) em StatefulSet: um master e duas réplicas; - 3 pods Sentinel, com quorum 2, que é o mínimo saudável;
- 2 pods HAProxy, para não criar um novo ponto único de falha;
- Services: um headless para o Redis e outro para o Sentinel (DNS estável por pod), e um Service normal para o HAProxy, que é o endpoint das aplicações.

Por que StatefulSet e não Deployment?
Porque o StatefulSet foi feito exatamente para workloads com estado. Você ganha:
- identidade de rede estável:
redis-0.redis.default.svc.cluster.localé sempre o mesmo pod, mesmo que o IP mude; - storage persistente por pod, com um PVC por réplica;
- ordem previsível de start, stop e rolling update.
Para Redis, isso é ouro. Em Deployment “com sorte funciona”; em StatefulSet “funciona por desenho”.
Sentinel: o cérebro do failover
O Sentinel faz quatro coisas:
- monitora o master e as réplicas;
- notifica eventos;
- promove uma réplica a master quando o master some;
- informa aos clientes qual é o master atual.
Boas práticas: rode no mínimo 3 sentinels, espalhe-os em nós diferentes (anti-affinity) e teste o failover periodicamente, inclusive no ambiente real.
Atenção: o Sentinel não transforma replicação assíncrona em mágica. Numa falha no meio de uma escrita, ainda existe uma janela em que o último dado escrito no master antigo se perde. Se isso importa, veja min-replicas-to-write e min-replicas-max-lag.
HAProxy: endpoint único para suas aplicações
Nem todo cliente Redis é sentinel-aware, e, mesmo quando é, muita equipe prefere simplificar. O HAProxy resolve isso com um health check TCP que conversa com o Redis:
- envia
AUTHe espera+OK; - envia
PINGe espera+PONG; - envia
INFO replicatione esperarole:master.
Só o pod que responde role:master fica “UP” no backend, então o tráfego vai sempre para o master ativo, e as conexões com o master antigo são derrubadas quando ele deixa de ser master (on-marked-down shutdown-sessions).

Os manifests
Tudo abaixo usa o namespace default. Se usar outro, troque o .default.svc nos nomes DNS.
1) Senha e Services
apiVersion: v1
kind: Secret
metadata:
name: redis-auth
type: Opaque
stringData:
password: "troque-esta-senha"
---
apiVersion: v1
kind: Service
metadata:
name: redis # headless: redis-0.redis, redis-1.redis...
spec:
clusterIP: None
selector:
app: redis
ports:
- name: redis
port: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-sentinel # headless: redis-sentinel-0.redis-sentinel...
spec:
clusterIP: None
selector:
app: redis-sentinel
ports:
- name: sentinel
port: 26379
2) Scripts e configuração
O truque está no script de start do Redis. Antes de subir, cada pod pergunta ao Sentinel quem é o master atual. Se ninguém responder, é o primeiro boot, e o redis-0 assume. Isso evita que um pod reiniciado volte achando que é master e cause um split-brain.
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
data:
start-redis.sh: |
#!/bin/sh
set -e
ME="$(hostname).redis.default.svc.cluster.local"
MASTER="redis-0.redis.default.svc.cluster.local"
# Se o Sentinel já conhece um master, obedece a ele
CUR=$(redis-cli -h redis-sentinel -p 26379 sentinel get-master-addr-by-name mymaster 2>/dev/null | head -1 || true)
[ -n "$CUR" ] && MASTER="$CUR"
ARGS="--requirepass $REDIS_PASSWORD --masterauth $REDIS_PASSWORD --replica-announce-ip $ME --appendonly yes"
if [ "$ME" != "$MASTER" ]; then
ARGS="$ARGS --replicaof $MASTER 6379"
fi
exec redis-server $ARGS
sentinel.conf: |
port 26379
sentinel resolve-hostnames yes
sentinel announce-hostnames yes
sentinel monitor mymaster redis-0.redis.default.svc.cluster.local 6379 2
sentinel auth-pass mymaster __PASSWORD__
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
start-sentinel.sh: |
#!/bin/sh
set -e
# O Sentinel reescreve o próprio arquivo, então ele precisa ser gravável
cp /config/sentinel.conf /data/sentinel.conf
sed -i "s/__PASSWORD__/$REDIS_PASSWORD/" /data/sentinel.conf
echo "sentinel announce-ip $(hostname).redis-sentinel.default.svc.cluster.local" >> /data/sentinel.conf
exec redis-sentinel /data/sentinel.conf
3) StatefulSet do Redis
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
spec:
serviceName: redis
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: redis
containers:
- name: redis
image: redis:7
command: ["sh", "/config/start-redis.sh"]
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-auth
key: password
ports:
- containerPort: 6379
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { memory: 512Mi }
volumeMounts:
- name: data
mountPath: /data
- name: config
mountPath: /config
volumes:
- name: config
configMap:
name: redis-config
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
4) StatefulSet do Sentinel
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-sentinel
spec:
serviceName: redis-sentinel
replicas: 3
selector:
matchLabels:
app: redis-sentinel
template:
metadata:
labels:
app: redis-sentinel
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: redis-sentinel
containers:
- name: sentinel
image: redis:7
command: ["sh", "/config/start-sentinel.sh"]
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-auth
key: password
ports:
- containerPort: 26379
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { memory: 128Mi }
volumeMounts:
- name: config
mountPath: /config
- name: data
mountPath: /data
volumes:
- name: config
configMap:
name: redis-config
- name: data
emptyDir: {}
5) HAProxy
Dois detalhes que fazem toda a diferença: o bloco resolvers faz o HAProxy re-resolver o DNS dos pods, já que o IP muda a cada restart, e a senha vem de variável de ambiente, porque o HAProxy expande ${VAR} dentro de aspas duplas.
apiVersion: v1
kind: ConfigMap
metadata:
name: haproxy-config
data:
haproxy.cfg: |
resolvers k8s
parse-resolv-conf
hold valid 5s
defaults
mode tcp
timeout connect 3s
timeout client 1h
timeout server 1h
frontend redis-write
bind *:6379
default_backend redis-primary
backend redis-primary
option tcp-check
tcp-check connect
tcp-check send "AUTH ${REDIS_PASSWORD}\r\n"
tcp-check expect string +OK
tcp-check send PING\r\n
tcp-check expect string +PONG
tcp-check send info\ replication\r\n
tcp-check expect string role:master
tcp-check send QUIT\r\n
tcp-check expect string +OK
default-server inter 1s fall 3 rise 2 on-marked-down shutdown-sessions resolvers k8s init-addr none
server redis-0 redis-0.redis.default.svc.cluster.local:6379 check
server redis-1 redis-1.redis.default.svc.cluster.local:6379 check
server redis-2 redis-2.redis.default.svc.cluster.local:6379 check
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-haproxy
spec:
replicas: 2
selector:
matchLabels:
app: redis-haproxy
template:
metadata:
labels:
app: redis-haproxy
spec:
containers:
- name: haproxy
image: haproxy:lts
env:
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-auth
key: password
ports:
- containerPort: 6379
volumeMounts:
- name: config
mountPath: /usr/local/etc/haproxy
volumes:
- name: config
configMap:
name: haproxy-config
---
apiVersion: v1
kind: Service
metadata:
name: redis-haproxy # é este endereço que as aplicações usam
spec:
selector:
app: redis-haproxy
ports:
- port: 6379
targetPort: 6379
Salve tudo num arquivo e aplique:
kubectl apply -f redis-ha.yaml
kubectl get pods -l 'app in (redis,redis-sentinel,redis-haproxy)' -w
As aplicações passam a usar redis-haproxy.default.svc.cluster.local:6379, com a senha do Secret.
Dica: estes manifests são um esqueleto didático, bom para entender cada peça. Em produção vale considerar um operator ou um Helm chart mantido pela comunidade, que já tratam casos de borda como um Sentinel reiniciando durante um failover.
Testando o failover
Se isso falhar no teste, falharia às 3h da manhã. Melhor descobrir agora.
- Descubra quem é o master atual:
kubectl exec redis-sentinel-0 -- redis-cli -p 26379 sentinel get-master-addr-by-name mymaster - Derrube o pod master (troque
redis-0pelo master do passo anterior):kubectl delete pod redis-0 - Acompanhe a eleição nos logs do Sentinel. Procure por
+switch-master:kubectl logs -f redis-sentinel-0 | grep -E "sdown|odown|switch-master" - Confirme que o HAProxy já aponta para o novo master:
kubectl run -it --rm redis-cli --image=redis:7 --restart=Never -- \ redis-cli -h redis-haproxy -a 'troque-esta-senha' info replication | grep role - Veja o impacto real na aplicação: quantos timeouts, retries e erros ela registrou.
- Espere o pod derrubado voltar e confira que ele subiu como réplica, não como um segundo master.
Checklist de produção
- ☐ PVC por pod Redis e
appendonly yes - ☐ Anti-affinity para Redis e Sentinel, de preferência
requiredse houver nós suficientes - ☐ Requests e limits de CPU e memória definidos
- ☐ PodDisruptionBudget (
maxUnavailable: 1) para Redis, Sentinel e HAProxy - ☐ NetworkPolicy limitando o acesso às portas 6379 e 26379
- ☐ Autenticação no Redis (
requirepassemasterauth) com a senha num Secret - ☐ Monitoramento: latência, memória, número de failovers e
rolede cada pod (redis_exporter + Prometheus) - ☐ Teste de failover com procedimento documentado
Erros comuns que quase todo mundo comete
- Rodar tudo no mesmo nó e chamar isso de HA.
- Esquecer o headless service e depender de IP de pod.
- Deixar o Redis sem autenticação “porque é rede interna”.
- Deixar o pod reiniciado voltar como master e criar dois masters (split-brain).
- HAProxy sem
resolvers, preso no IP antigo do pod. - Um único HAProxy virando o novo ponto único de falha.
- Não testar o failover e confiar na fé.
Conclusão
Se você quer Redis estável no Kubernetes, este combo funciona muito bem: StatefulSet para dar identidade e guardar os dados, Sentinel para o failover automático e HAProxy para um endpoint simples e estável.
Não é uma arquitetura “zero complexidade”, mas é o tipo de complexidade que compra tranquilidade operacional.
Referências
- Redis Docs: High availability with Redis Sentinel
- Kubernetes Docs: StatefulSets
- HAProxy Blog: Advanced Redis Health Check
- ArysTech: Setting Up HAProxy with Redis Sentinel for High Availability on MicroK8s
Créditos das imagens: ArysTech (post sobre Redis Sentinel + HAProxy em MicroK8s) e Karl Stoney (Redis Sentinel behind HAProxy).
Deixe um comentário