Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Deixar todo release pronto para rollback, com imagens versionadas e um desfazer de um comando
- Escolher entre deploy rolling, blue-green e canary
- Evitar requisições perdidas e rollbacks quebrados por causa de desligamento abrupto e migrations de banco
Nesta página
- O que torna um rollback possível?
- Como fazer rollback, passo a passo?
- Qual estratégia de deploy sem downtime usar?
- Como recarregar sem perder requisições?
- Por que o deploy ainda perde requisições?
- Como as migrations de banco quebram o rollback?
- O que conferir antes e depois de cada release?
- Perguntas frequentes
Todo deploy é uma pequena aposta de que a versão nova funciona. O rollback é como você cancela a aposta em segundos, e o deploy sem downtime é como você aposta sem o usuário perceber. Os dois resolvem problemas diferentes, mas dependem do mesmo hábito: nunca sobrescrever o que está rodando e sempre manter a versão anterior pronta.
Três estratégias de release cobrem quase todos os casos: rolling, blue-green e canary. E duas falhas derrubam qualquer uma delas: o desligamento abrupto do processo e as migrations de banco.
O que torna um rollback possível?#
O rollback só é rápido quando a versão anterior ainda existe como um artefato completo, pronto para rodar. Isso pede três regras:
- Builds imutáveis e versionados. Marque as imagens Docker com uma versão ou o SHA do commit, nunca só com
latest. Uma tag que muda de lugar não serve de ponto de volta. - Configuração separada do código. As variáveis de ambiente ficam fora do build, assim reverter o código não reverte (nem mantém) a configuração errada sem você perceber. Veja variáveis de ambiente.
- Release antigo guardado. Mantenha pelo menos as últimas imagens ou pastas de release. O Kubernetes, por exemplo, guarda ReplicaSets antigos para rollback, controlados por
revisionHistoryLimit(se você colocar 0, perde a capacidade de voltar).
Com essas regras valendo, o rollback é subir uma versão mais antiga, não fazer um build novo.
Como fazer rollback, passo a passo?#
Docker Compose. Fixe a imagem pela tag com uma variável:
services:
app:
image: registry.example.com/myapp:${APP_VERSION}
restart: unless-stoppedO deploy e o rollback mudam um único valor:
APP_VERSION=1.4.2 docker compose up -d # deploy
APP_VERSION=1.4.1 docker compose up -d # rollbackKubernetes. Os Deployments registram as revisões para você:
kubectl rollout history deployment/myapp
kubectl rollout undo deployment/myapp # revisão anterior
kubectl rollout undo deployment/myapp --to-revision=2 # uma revisão específicaUma revisão nova só é criada quando o template do pod muda (imagem, labels, env), então escalar réplicas não polui o histórico.
Servidor comum. Faça o deploy de cada release numa pasta própria e aponte um symlink current para ela. Voltar é mover o link e recarregar o processo:
ln -sfn /srv/myapp/releases/1.4.1 /srv/myapp/currentDepois de qualquer rollback, confira se funcionou: chame o endpoint de health e leia os logs da aplicação para confirmar que é a versão antiga que está respondendo.
Qual estratégia de deploy sem downtime usar?#
| Estratégia | Como funciona | Custo | Velocidade do rollback |
|---|---|---|---|
| Rolling | Substitui as instâncias aos poucos | Sem ambiente extra | Outro rolling update |
| Blue-green | Roda a antiga e a nova lado a lado e troca todo o tráfego | Capacidade em dobro durante a troca | Imediata: troca de volta |
| Canary | Manda uma fatia pequena do tráfego para a versão nova primeiro | Exige divisão de tráfego e métricas | Imediata: manda 0% |
Rolling é o padrão no Kubernetes (maxSurge e maxUnavailable vêm com 25%). Não precisa de ambiente extra, mas por um tempo as duas versões atendem tráfego ao mesmo tempo, então elas precisam ser compatíveis entre si.
Blue-green deixa o ambiente antigo (blue) intocado enquanto o novo (green) sobe e é testado. Quando você vira o roteador, todos os usuários mudam de uma vez, e virar de volta é o seu rollback. O preço é rodar duas cópias.
Canary limita o estrago: 1% do tráfego, depois 10%, depois todo mundo, olhando taxa de erro e latência entre um passo e outro. Compensa quando você tem tráfego suficiente para os números dizerem alguma coisa.
As três precisam dos mesmos dois ingredientes: pelo menos duas instâncias atrás de um roteador (veja load balancer) e um health check que avise o roteador quando a instância nova está pronta.
Como recarregar sem perder requisições?#
Você não precisa de um orquestrador para uma versão básica disso.
Nginx. O nginx -s reload valida a configuração nova primeiro. Se ela estiver inválida, o Nginx continua rodando com a antiga. Se estiver válida, workers novos sobem e os antigos terminam as requisições em andamento antes de sair. Sempre teste antes de recarregar:
nginx -t && nginx -s reloadPM2 (Node.js). Em modo cluster, o pm2 reload recarrega os processos sem downtime, ao contrário do pm2 restart, que mata e reinicia o processo:
pm2 start app.js -i 2 # duas instâncias em modo cluster
pm2 reload app # reload sem downtimeIsso só ajuda com duas instâncias ou mais, e a aplicação precisa ser stateless: cada processo tem a própria memória, então guarde sessões no Redis ou num banco.
Health checks. O roteador só deve mandar tráfego para uma instância que prove que está pronta. No Compose:
services:
app:
image: registry.example.com/myapp:${APP_VERSION}
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/healthz"]
interval: 5s
timeout: 3s
retries: 3
start_period: 10sFaça o /healthz verificar o que a aplicação realmente precisa (por exemplo, um ping no banco), e não só devolver 200 assim que o processo sobe. O exemplo supõe que a sua imagem tem wget; troque por curl ou por um script pequeno se não tiver. O Compose só registra o resultado (o status aparece no docker compose ps); o seu roteador ainda precisa agir com base nele, lendo esse status ou rodando a própria checagem contra o /healthz.
Por que o deploy ainda perde requisições?#
Quando um orquestrador ou gerenciador de processos para uma instância antiga, ele manda primeiro um sinal de término (SIGTERM, ou SIGINT no PM2). Se a aplicação ignora o sinal, as requisições em andamento são cortadas. Trate o sinal parando de aceitar conexões novas e terminando as que estão em curso:
// server.js (Node.js, ES modules)
import http from "node:http";
const server = http.createServer((req, res) => {
if (req.url === "/healthz") return res.end("ok");
res.end("hello");
});
server.listen(3000);
let shuttingDown = false;
function shutdown() {
if (shuttingDown) return;
shuttingDown = true;
// Para de aceitar conexões novas; o callback roda quando as abertas terminam.
server.close(() => process.exit(0));
// Rede de segurança: não fica preso para sempre numa conexão travada.
setTimeout(() => process.exit(1), 10_000).unref();
}
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);Outras causas comuns:
- O roteador continua mandando tráfego para uma instância que está desligando. Resolva com health checks e um pequeno atraso para drenar as conexões.
- A instância nova recebe tráfego antes de estar aquecida. Resolva com uma checagem de prontidão e com
start_period, para que um início lento não seja marcado como unhealthy. - Conexões longas, como WebSockets. Um restart sempre as interrompe, então os clientes precisam reconectar sozinhos.
Como as migrations de banco quebram o rollback?#
Código volta fácil. Dado não. Se a versão 2 apaga uma coluna que a versão 1 lê, voltar para a versão 1 faz ela quebrar. Num release rolling ou canary o problema é pior, porque as duas versões rodam ao mesmo tempo contra o mesmo banco.
A saída usual é o padrão expand and contract (expandir e contrair), dividido em releases separados:
- Expandir. Crie a coluna ou tabela nova. Mantenha a antiga. As duas versões funcionam.
- Migrar. Publique um código que escreve nas duas e lê da nova. Preencha as linhas antigas.
- Contrair. Quando nenhuma versão rodando usar mais a coluna antiga, e você não precisar mais voltar para antes desse ponto, apague a coluna.
Regras práticas: deixe as migrations compatíveis com o release anterior, nunca renomeie uma coluna num passo só (crie o nome novo, copie, remova o antigo depois) e faça um dump do banco antes de qualquer migration destrutiva. Veja como fazer backup de um banco de dados.
O que conferir antes e depois de cada release?#
Antes: os testes passam, a versão anterior está marcada e ainda disponível, e a migration é compatível com a versão anterior. Faça o deploy por um pipeline para que os passos se repitam sempre iguais, como em deploy automático com GitHub Actions.
Depois: o endpoint de health responde a partir da versão nova, a taxa de erro e a latência batem com a referência da versão antiga, e os logs não mostram exceções novas. Defina o gatilho de rollback com antecedência (por exemplo, taxa de erro acima de um limite fixo por cinco minutos) para ninguém ficar discutindo isso no meio de um incidente.
Perguntas frequentes
Qual a diferença entre rollback e roll-forward?
Rollback sobe de novo a versão anterior que funcionava. Roll-forward publica uma correção nova por cima do release quebrado. O rollback costuma ser mais rápido, mas não é possível quando uma migration de banco não tem volta.
Preciso de Kubernetes para ter deploy sem downtime?
Não. Qualquer arranjo com duas cópias da aplicação e algo que distribua o tráfego entre elas resolve, por exemplo Nginx na frente de dois processos, PM2 em modo cluster ou Docker Compose com um proxy reverso.
Qual a diferença entre blue-green e canary?
Blue-green mantém dois ambientes completos e troca todo o tráfego de uma vez. Canary manda uma fatia pequena do tráfego para a versão nova primeiro e só aumenta se as métricas continuarem saudáveis.
Por que o deploy perde requisições mesmo com duas instâncias?
Normalmente o processo antigo é encerrado sem drenar as conexões. As requisições em andamento são cortadas, ou o load balancer continua mandando tráfego para uma instância que já está desligando.
Mais em Deploy
Ver todos os 33 artigos- O que é deploy (e o que acontece quando você faz um)Deploy é o processo de levar o código de uma aplicação para um servidor, prepará-lo e colocá-lo no ar. Veja as etapas, os métodos e como voltar atrás.
- Quanta RAM e CPU minha aplicação precisa?Dimensione RAM e CPU medindo o pico real da aplicação e somando uma folga de 30%. Veja como medir em Node.js, Python e Java e o que fazer quando faltar.
- Deploy automático com GitHub ActionsDeploy automático com GitHub Actions envia seu código para o servidor a cada push. Veja o workflow passo a passo, secrets, testes antes do deploy e erros comuns.
- Quanto custa hospedar uma aplicação Node.jsHospedar Node.js custa de zero, com limites, a poucas dezenas de reais por mês. Veja o que define o preço, quanta RAM o app usa e como cada opção cobra.