Redis Sentinel no Docker Compose: por que não é alta disponibilidade e como testar
Monte Redis com Sentinel no Compose como laboratório, entenda quorum e maioria, derrube o primário de propósito e confirme que a aplicação reconecta antes de levar o desenho para produção.
O docker compose up sobe um Redis primário, duas réplicas e três Sentinels, todos verdes. O card no quadro da equipe diz "Redis em alta disponibilidade". Dois meses depois, a VPS reinicia para uma atualização de kernel e os seis containers caem juntos. O carrinho de compras fica fora do ar até a máquina voltar.
A resposta curta: Sentinel protege contra a queda de um processo Redis, não contra a queda da máquina onde estão todos. Seis containers num host só é um laboratório excelente para aprender e testar failover. Para alta disponibilidade de verdade, cada Redis e cada Sentinel precisam estar em máquinas diferentes, e você só sabe que funciona depois de derrubar o primário e ver a aplicação continuar gravando.
Por que três containers no mesmo host não sobrevivem
Alta disponibilidade é sobre domínios de falha. Se o disco, a energia, o kernel ou o provedor daquela máquina falham, tudo que está nela falha junto. Sentinel elege uma réplica como novo primário quando o atual some, mas precisa de uma réplica viva em outro lugar e de Sentinels vivos para votar.
A votação tem duas etapas, e elas confundem. O quorum configurado é quantos Sentinels precisam concordar que o primário está fora para marcá-lo como objetivamente caído. Para executar o failover, um Sentinel precisa ser autorizado pela maioria dos Sentinels conhecidos. Com três Sentinels e quorum 2, você aguenta perder um. Com dois Sentinels, perder um deixa o outro sem maioria e o failover não acontece. Por isso o número mínimo recomendado é três, em máquinas independentes.
Se a dúvida é entre Sentinel e Cluster, o guia Redis Sentinel ou Cluster compara os dois com testes de hash slots e comandos multi-chave. Aqui o foco é o laboratório em Compose e os testes que mostram se o desenho aguenta.
Como montar o laboratório, passo a passo
O exemplo: o carrinho da Loja Exemplo, uma API Node.js que guarda sessões de carrinho no Redis. O primário de exemplo se chama loja.
- Crie um arquivo de configuração por nó Redis com senha, anúncio do próprio nome e persistência.
- Crie um arquivo por Sentinel, em diretório gravável: o Sentinel reescreve o próprio arquivo com o estado que descobre.
- Suba tudo no Compose e confira quem é o primário.
- Conecte a aplicação pelos Sentinels, nunca direto no endereço do primário.
- Derrube o primário com a aplicação gravando e meça o tempo sem escrita.
- Traga o antigo primário de volta e confirme que ele voltou como réplica.
Configuração dos nós
# redis/redis-1.conf (redis-2 e redis-3 iguais, com o próprio nome # em replica-announce-ip e a linha "replicaof redis-1 6379") port 6379 requirepass SENHA_FORTE_DO_LAB masterauth SENHA_FORTE_DO_LAB replica-announce-ip redis-1 appendonly yes min-replicas-to-write 1 min-replicas-max-lag 10
As duas senhas são iguais de propósito: qualquer nó pode virar primário (e precisa de requirepass) ou réplica (e precisa de masterauth). O min-replicas-to-write faz o primário recusar escrita se não tiver nenhuma réplica conectada com atraso aceitável, o que reduz o volume de dados perdidos num failover. Esses arquivos têm senha e ficam fora do Git.
# sentinel/s1/sentinel.conf (copie para s2 e s3, um arquivo por Sentinel) port 26379 sentinel resolve-hostnames yes sentinel announce-hostnames yes sentinel monitor loja redis-1 6379 2 sentinel auth-pass loja SENHA_FORTE_DO_LAB sentinel down-after-milliseconds loja 5000 sentinel failover-timeout loja 60000 sentinel parallel-syncs loja 1
Não monte o mesmo arquivo nos três Sentinels: cada um grava nele o próprio identificador e o que descobriu, e compartilhar o arquivo faz um Sentinel se confundir com outro. O uso de nomes em vez de IPs depende de resolve-hostnames e announce-hostnames, disponíveis a partir do Redis 6.2. Já os redis.conf dos nós vão montados como somente leitura: depois de um failover, o papel novo de cada nó vive só na memória, e quem reinicia volta com o replicaof do arquivo até o Sentinel reconfigurá-lo, o que leva alguns segundos. No laboratório isso ajuda a ver o Sentinel trabalhando; em produção, deixe o arquivo gravável.
# compose.yaml
services:
redis-1:
image: redis:7.4
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes: ["./redis/redis-1.conf:/usr/local/etc/redis/redis.conf:ro", "r1:/data"]
redis-2:
image: redis:7.4
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes: ["./redis/redis-2.conf:/usr/local/etc/redis/redis.conf:ro", "r2:/data"]
redis-3:
image: redis:7.4
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes: ["./redis/redis-3.conf:/usr/local/etc/redis/redis.conf:ro", "r3:/data"]
sentinel-1:
image: redis:7.4
command: ["redis-sentinel", "/etc/sentinel/sentinel.conf"]
volumes: ["./sentinel/s1:/etc/sentinel"]
sentinel-2:
image: redis:7.4
command: ["redis-sentinel", "/etc/sentinel/sentinel.conf"]
volumes: ["./sentinel/s2:/etc/sentinel"]
sentinel-3:
image: redis:7.4
command: ["redis-sentinel", "/etc/sentinel/sentinel.conf"]
volumes: ["./sentinel/s3:/etc/sentinel"]
volumes:
r1:
r2:
r3:
A aplicação conecta pelos Sentinels
import Redis from 'ioredis';
const redis = new Redis({
sentinels: [
{ host: 'sentinel-1', port: 26379 },
{ host: 'sentinel-2', port: 26379 },
{ host: 'sentinel-3', port: 26379 }
],
name: 'loja',
password: process.env.REDIS_PASSWORD,
connectTimeout: 5000,
maxRetriesPerRequest: 3
});
redis.on('error', (err) => console.error('Redis:', err.message));
setInterval(async () => {
try {
await redis.set('carrinho:teste', new Date().toISOString(), 'EX', 300);
console.log('ok', new Date().toISOString());
} catch (err) {
console.error('falhou', new Date().toISOString(), err.message);
}
}, 1000);
O cliente pergunta aos Sentinels quem é o primário e, quando a conexão cai, pergunta de novo. Se a aplicação conectasse direto em redis-1, o failover aconteceria e ela continuaria batendo na porta do nó morto. Rode esse script como mais um serviço na mesma rede do Compose.
O teste que importa: derrubar o primário
docker compose exec sentinel-1 redis-cli -p 26379 SENTINEL get-master-addr-by-name loja # 1) "redis-1" 2) "6379" docker compose stop redis-1 # espere uns 10 segundos e pergunte de novo docker compose exec sentinel-1 redis-cli -p 26379 SENTINEL get-master-addr-by-name loja # 1) "redis-2" 2) "6379" docker compose start redis-1 docker compose exec redis-1 redis-cli --askpass INFO replication # role:slave
No log do script, você verá uma janela de "falhou" de alguns segundos: o tempo do down-after-milliseconds somado ao da eleição e da reconexão. Anote esse número. Ele é o que você informa ao negócio como tempo sem escrita num failover. Repita com docker compose pause redis-1, que simula um processo travado em vez de morto, um caso que engana healthchecks simples, como em healthcheck que mente.
Do laboratório para produção
Em produção, cada par Redis e Sentinel fica num host diferente, de preferência em zonas de falha diferentes. Os nomes anunciados precisam ser alcançáveis pelos clientes: dentro do Compose, redis-2 resolve; para uma aplicação fora daquela rede, não. Esse é o erro clássico de quem leva o laboratório para várias máquinas: o Sentinel anuncia um endereço que só existe dentro da rede do Docker. E lembre que a replicação é assíncrona: escritas confirmadas pelo primário logo antes da queda podem não ter chegado à réplica. Carrinho tolera isso; saldo de conta, não.
Erros comuns
- Chamar de alta disponibilidade seis containers num host. A primeira reinicialização da máquina derruba tudo, e a equipe descobre na hora do incidente.
- Rodar só dois Sentinels. Com um fora, o outro não tem maioria e o failover nunca acontece.
- Montar o arquivo do Sentinel como somente leitura ou compartilhado. O Sentinel não consegue gravar o estado, ou vários se confundem com o mesmo identificador.
- Esquecer o
masterauthnos nós. O antigo primário volta, não consegue se autenticar no novo e fica fora da replicação. - Aplicação conectada direto no primário. O failover acontece e a aplicação continua tentando o nó morto.
- Usar a senha do tutorial. Ela está publicada e é a primeira que um robô tenta.
Checklist
- Existem pelo menos três Sentinels, e você sabe quantos podem cair.
- Cada Sentinel tem o próprio arquivo de configuração, gravável.
- Todos os nós têm
requirepassemasterauthcom senha própria, fora do Git. - A aplicação conecta pelos Sentinels.
- Você derrubou e pausou o primário e mediu a janela sem escrita.
- O antigo primário voltou como réplica.
- Em produção, os nós estão em hosts diferentes, com endereços alcançáveis pelos clientes.
Se você desligar agora o host onde roda o seu Redis primário, ainda existe um Redis e uma maioria de Sentinels vivos em outra máquina?