Estratégias de Deploy: Blue-Green, Canary e Rolling Updates na Prática

Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.
Neste artigo
Um deploy que derruba 100% dos usuários por 30 segundos custa diferente de um que derruba 1% por 5 minutos. A diferença entre essas duas situações não é sorte: é estratégia de deploy.
Blue-Green, Canary e Rolling Updates resolvem o mesmo problema (colocar código novo em produção sem destruir a experiência do usuário), mas cada uma faz trade-offs diferentes entre complexidade de infra, velocidade de rollback e custo de recursos. Escolher errado não significa que o deploy vai falhar. Significa que você vai gastar infra demais, ou descobrir bugs tarde demais, ou levar minutos para reverter o que poderia levar segundos.
Como cada estratégia funciona por baixo
Antes de comparar, a mecânica precisa estar clara.
Blue-Green mantém dois ambientes idênticos (blue e green). Apenas um recebe tráfego de produção. O deploy acontece no ambiente ocioso. Quando os health checks passam, o roteador (load balancer, DNS, ingress) troca o ponteiro de tráfego de blue para green. Rollback é trocar o ponteiro de volta.
Canary direciona uma fração pequena do tráfego (1%, 5%, 10%) para a versão nova. Métricas de erro, latência e saturação são observadas. Se estiverem saudáveis, o percentual sobe progressivamente até 100%. Se degradarem, o tráfego volta para a versão anterior.
Rolling Update substitui instâncias da versão antiga pela nova em lotes. Se você tem 10 pods, atualiza 2 por vez. Durante o processo, versões antiga e nova coexistem. O Kubernetes usa essa estratégia como padrão.
Blue-Green:
[LB] ──► [Blue v1] ✓ tráfego ativo
[Green v2] ocioso, recebendo deploy
(troca)
[LB] ──► [Green v2] ✓ tráfego ativo
[Blue v1] ocioso, pronto para rollback
Canary:
[LB] ──► 95% [v1]
──► 5% [v2] ← observando métricas
(progressão)
[LB] ──► 50% [v1]
──► 50% [v2]
(fim)
[LB] ──► 100% [v2]
Rolling Update:
[pod1-v1] [pod2-v1] [pod3-v1] [pod4-v1]
[pod1-v2] [pod2-v2] [pod3-v1] [pod4-v1] ← 2 atualizados
[pod1-v2] [pod2-v2] [pod3-v2] [pod4-v2] ← todos atualizadosTabela comparativa: critérios que importam na decisão
| Critério | Blue-Green | Canary | Rolling Update |
|---|---|---|---|
| Velocidade de rollback | Segundos (troca de ponteiro) | Segundos (redireciona tráfego) | Minutos (precisa recriar pods antigos) |
| Custo de infra | Alto (ambiente duplicado 100%) | Médio (poucos pods extras) | Baixo (reutiliza os mesmos recursos) |
| Risco de exposição a bugs | Tudo ou nada: 0% ou 100% | Gradual: 1% → 5% → 25% → 100% | Parcial: depende do tamanho do lote |
| Complexidade de configuração | Média (dois ambientes + roteamento) | Alta (observabilidade + regras de peso) | Baixa (padrão do Kubernetes) |
| Coexistência de versões | Não (uma versão recebe tráfego) | Sim (duas versões simultâneas) | Sim (duas versões simultâneas) |
| Compatibilidade de banco | Precisa de migrations compatíveis com ambas | Precisa de migrations compatíveis com ambas | Precisa de migrations compatíveis com ambas |
| Melhor para | Aplicações com rollback crítico (fintech, saúde) | Validação gradual com métricas (SaaS, e-commerce) | Workloads stateless com baixo risco (APIs internas) |
Rolling Update com Kubernetes: a configuração padrão que você já usa
O Kubernetes aplica Rolling Update por padrão. Mas o padrão sem ajuste é perigoso: maxUnavailable: 25% e maxSurge: 25% significam que, em um deployment com 4 réplicas, 1 pod fica indisponível e 1 pod extra é criado simultaneamente. Em serviços com poucos pods, isso pode derrubar 25% da capacidade durante o deploy.
# deployment-rolling.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-pedidos
namespace: production
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
# maxUnavailable 0 garante que nenhum pod sai antes do novo estar Ready
maxUnavailable: 0
# maxSurge 2 cria no máximo 2 pods extras durante o rollout
maxSurge: 2
selector:
matchLabels:
app: api-pedidos
template:
metadata:
labels:
app: api-pedidos
version: v2.3.1
spec:
containers:
- name: api-pedidos
image: registry.example.com/api-pedidos:2.3.1
ports:
- containerPort: 3000
# readinessProbe evita que tráfego chegue antes do app estar pronto
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
# livenessProbe reinicia pods travados, mas com threshold alto
# para não matar pods em GC pause
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 5
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"O maxUnavailable: 0 é a configuração que faz diferença real. Sem ele, o Kubernetes derruba pods antigos antes dos novos estarem prontos, e o serviço perde capacidade durante o deploy.
Para monitorar o progresso:
# Acompanha o rollout em tempo real
kubectl rollout status deployment/api-pedidos -n production --timeout=300s
# Se algo der errado, reverte para a revisão anterior
kubectl rollout undo deployment/api-pedidos -n production
# Verifica o histórico de revisões (útil para saber qual versão reverter)
kubectl rollout history deployment/api-pedidos -n productionBlue-Green com Kubernetes Services
Blue-Green no Kubernetes não exige ferramentas extras. Dois Deployments (blue e green) e um Service que alterna o selector entre eles.
# deployment-blue.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-pedidos-blue
namespace: production
spec:
replicas: 4
selector:
matchLabels:
app: api-pedidos
slot: blue
template:
metadata:
labels:
app: api-pedidos
slot: blue
version: v2.3.0
spec:
containers:
- name: api-pedidos
image: registry.example.com/api-pedidos:2.3.0
ports:
- containerPort: 3000# deployment-green.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-pedidos-green
namespace: production
spec:
replicas: 4
selector:
matchLabels:
app: api-pedidos
slot: green
template:
metadata:
labels:
app: api-pedidos
slot: green
version: v2.3.1
spec:
containers:
- name: api-pedidos
image: registry.example.com/api-pedidos:2.3.1
ports:
- containerPort: 3000# service-production.yaml
apiVersion: v1
kind: Service
metadata:
name: api-pedidos
namespace: production
spec:
selector:
app: api-pedidos
# Troca entre blue e green para alternar o tráfego
slot: blue
ports:
- port: 80
targetPort: 3000A troca acontece com um único comando:
# Alterna tráfego de blue para green editando o selector do Service
kubectl patch service api-pedidos -n production \
-p '{"spec":{"selector":{"slot":"green"}}}'
# Rollback: volta para blue
kubectl patch service api-pedidos -n production \
-p '{"spec":{"selector":{"slot":"blue"}}}'O rollback leva o tempo de propagação do kube-proxy: normalmente menos de 2 segundos. Essa é a maior vantagem do Blue-Green. O custo é manter o dobro de pods rodando durante (e entre) os deploys. Se sua aplicação usa 8 pods com 512Mi cada, são 4Gi extras de memória alocados permanentemente.
Canary com Nginx Ingress e pesos de tráfego
Canary requer um mecanismo de split de tráfego. O Nginx Ingress Controller suporta isso nativamente via annotations, sem precisar de service mesh.
# ingress-stable.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-pedidos-stable
namespace: production
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-pedidos-stable
port:
number: 80# ingress-canary.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-pedidos-canary
namespace: production
annotations:
kubernetes.io/ingress.class: nginx
# nginx.ingress.kubernetes.io/canary ativa o modo canary
nginx.ingress.kubernetes.io/canary: "true"
# Peso define o percentual de tráfego para o canary (5 = 5%)
nginx.ingress.kubernetes.io/canary-weight: "5"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-pedidos-canary
port:
number: 80A progressão do canary acontece alterando o canary-weight:
# Sobe para 25% após validar métricas
kubectl annotate ingress api-pedidos-canary -n production \
nginx.ingress.kubernetes.io/canary-weight="25" --overwrite
# Sobe para 50%
kubectl annotate ingress api-pedidos-canary -n production \
nginx.ingress.kubernetes.io/canary-weight="50" --overwrite
# Promoção completa: remove o canary e atualiza o stable
kubectl annotate ingress api-pedidos-canary -n production \
nginx.ingress.kubernetes.io/canary-weight="100" --overwriteSem observabilidade, Canary é Rolling Update com passos extras. A progressão precisa ser guiada por métricas reais. Se você não tem dashboards de taxa de erro e latência p99 por versão, Canary não vai te dar mais segurança que Rolling Update. Ferramentas como Argo Rollouts automatizam essa progressão com análise de métricas do Prometheus, mas a configuração manual com Nginx Ingress funciona para times que preferem controle explícito.
O que NÃO fazer
Anti-pattern 1: Blue-Green sem health check antes da troca
# ERRADO: troca o tráfego imediatamente após o deploy
kubectl apply -f deployment-green.yaml
kubectl patch service api-pedidos -n production \
-p '{"spec":{"selector":{"slot":"green"}}}'O deploy do green pode levar 30 segundos para os pods ficarem Ready. Se você troca o Service antes disso, o tráfego vai para pods que ainda estão inicializando. A versão correta:
# CORRETO: espera todos os pods do green ficarem Ready antes de trocar
kubectl apply -f deployment-green.yaml
# rollout status bloqueia até que todos os pods passem no readinessProbe
kubectl rollout status deployment/api-pedidos-green -n production --timeout=120s
# Só troca o tráfego depois da confirmação
kubectl patch service api-pedidos -n production \
-p '{"spec":{"selector":{"slot":"green"}}}'Anti-pattern 2: Rolling Update com maxUnavailable alto e sem readinessProbe
Sem readinessProbe, o Kubernetes considera o pod Ready assim que o container inicia. Se a aplicação leva 10 segundos para carregar configurações, conectar ao banco e aquecer caches, o tráfego chega antes do app estar pronto. Combinado com maxUnavailable: 25%, o resultado é uma janela onde 25% das requisições falham.
Anti-pattern 3: Canary sem métricas por versão
Se o seu sistema de monitoramento agrega métricas de todas as versões juntas, você não consegue distinguir se o aumento de erros vem do canary (5% do tráfego) ou do stable (95%). Labels de versão nos seus logs e métricas são pré-requisito, não luxo.
Quando usar cada uma: matriz de decisão
Use Rolling Update quando:
- O serviço é stateless e tolera coexistência de versões
- O time não tem infraestrutura de observabilidade por versão
- O custo de manter pods extras é relevante (clusters pequenos)
- O risco do deploy é baixo (mudanças incrementais, boa cobertura de testes)
Use Blue-Green quando:
- Rollback em segundos é requisito de negócio (pagamentos, saúde)
- O orçamento de infra comporta o dobro de recursos
- As mudanças são grandes e você quer testar o ambiente completo antes de expor tráfego
- A aplicação não tolera coexistência de versões (breaking changes no protocolo)
Use Canary quando:
- Você tem observabilidade com métricas segmentadas por versão
- O volume de tráfego é suficiente para gerar dados estatísticos no canary (5% de 100 req/min são 5 req/min, pouco para detectar regressão de latência)
- A mudança tem risco médio-alto e você quer validar em produção com blast radius controlado
- O time tem maturidade para definir critérios de promoção e rollback automatizados
Se o seu serviço recebe menos de 1000 requisições por minuto, Canary com 5% significa 50 requisições por minuto na versão nova. Dependendo da variância natural da sua latência, você precisa de 10 a 15 minutos nesse percentual para ter dados confiáveis. Considere isso no tempo total de deploy.
Migrations de banco: o problema que todas compartilham
As três estratégias sofrem do mesmo problema: durante o deploy, duas versões da aplicação acessam o mesmo banco. Se a versão nova espera uma coluna que não existe, ou a versão antiga não sabe lidar com uma coluna removida, o deploy quebra independente da estratégia.
A solução é a mesma para as três: migrations compatíveis com ambas as versões. Adicione colunas antes do deploy, remova colunas depois. Renomeie em dois passos (cria nova, copia dados, remove antiga). Se você usa Prisma, o post sobre migrations seguras em produção detalha esse fluxo.
Essa restrição de compatibilidade é mais severa no Blue-Green, porque as duas versões podem coexistir por horas (enquanto o ambiente antigo fica de standby). No Rolling Update, a coexistência dura minutos. No Canary, pode durar horas se a progressão for lenta.
Integração com o pipeline de CI/CD
Nenhuma dessas estratégias funciona isolada. Elas são o último estágio de um pipeline que começa com testes, passa por build de imagem e termina no deploy. Se você está containerizando com Docker, a imagem precisa ser imutável e tagueada com a versão exata (nunca latest em produção). Se o projeto é um monorepo com Turborepo, o pipeline precisa identificar quais serviços mudaram para fazer deploy seletivo.
Feature flags complementam Canary de forma poderosa: o deploy sobe o código novo para 100% dos pods, mas a funcionalidade nova fica desligada até ser ativada para um percentual de usuários. Isso separa deploy de release, que é uma distinção que reduz risco drasticamente. Se você quer implementar isso sem depender de SaaS, o post sobre feature flags com TypeScript cobre o tema.
Para APIs que precisam manter compatibilidade durante deploys graduais, padrões como versionamento de API e validação de payload ajudam a evitar quebras. O post sobre API Gateway patterns discute roteamento por versão, que se conecta diretamente com Canary baseado em header.
FAQ
Posso combinar Blue-Green com Canary?
Sim, e isso é comum em organizações maiores. O fluxo: deploy no ambiente green, direciona 5% do tráfego via Canary para o green, progride até 100%, e o blue vira o ambiente de standby. Argo Rollouts suporta esse modelo nativamente com a estratégia blueGreen combinada com analysis.
Rolling Update funciona com aplicações stateful?
Funciona, mas com cuidados extras. StatefulSets no Kubernetes fazem rolling update respeitando a ordem dos pods (do último para o primeiro). Se a aplicação depende de eleição de líder ou replicação, o podManagementPolicy e o updateStrategy.partition precisam ser configurados para evitar split-brain.
Qual o mínimo de infraestrutura para começar com Canary? Nginx Ingress Controller com annotations de canary. Não precisa de service mesh. Mas precisa de métricas por versão: no mínimo, taxa de erro HTTP 5xx e latência p95 segmentadas por label de versão no Prometheus ou equivalente. Sem isso, você está fazendo Canary no escuro.
Blue-Green dobra meu custo de infra permanentemente? Depende de como você opera. Se o ambiente ocioso fica ligado 24/7 esperando o próximo deploy, sim. Se você escala o ambiente ocioso para zero réplicas entre deploys e sobe sob demanda antes do deploy, o custo extra é proporcional ao tempo de deploy. Em clusters com autoscaler, essa abordagem é viável.
Qual estratégia é melhor para deploy de frontend (SPA, Next.js)? Blue-Green funciona bem para frontends porque a troca é atômica: o CDN ou o deploy na edge aponta para o novo bundle de uma vez. Rolling Update em frontend não faz sentido porque não existem "pods" de frontend rodando. Canary em frontend é possível via split de tráfego no CDN, mas a granularidade é limitada e o cache do navegador complica a segmentação.
Posição técnica
Rolling Update é o ponto de partida correto para a maioria dos serviços. Não porque é o melhor, mas porque é o que o Kubernetes já faz, exige zero infraestrutura extra e, com maxUnavailable: 0 e readinessProbe bem configurado, oferece deploys sem downtime. Migrar para Canary só faz sentido quando você já tem observabilidade por versão funcionando. Migrar para Blue-Green só faz sentido quando o custo de um rollback lento é maior que o custo de manter infraestrutura duplicada. Escolha a estratégia pelo problema que você precisa resolver, não pela que parece mais sofisticada no diagrama de arquitetura.

Escrito por
Marcos Soares
Fullstack Developer · CEO da Agência Poti
Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.
Comentários
Participe da discussão
Seja o primeiro a comentar!
Continue Aprofundando
Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.


