DevOps & CI/CD • 5 min de leitura

Deploy zero-downtime em produção

Todo sistema vai a produção com a promessa implícita de estar disponível. Quando uma atualização quebra isso — mesmo por dois minutos — usuários perdem transações, pipelines de pagamento falham no meio, e sessões ativas caem sem aviso. Em sistemas com SLA de 99,9%, dois minutos de downtime por semana já violam o contrato. Em sistemas financeiros ou de saúde, o custo é mais concreto que isso.

A ideia por trás do zero-downtime é simples: nunca há um momento em que nenhuma instância saudável está servindo tráfego. A complexidade está em como garantir isso enquanto o código muda, o banco de dados migra, e a configuração evolui — às vezes tudo ao mesmo tempo.

As três estratégias que importam

Rolling deployment é o padrão mais comum e o ponto de partida natural. As instâncias são atualizadas em grupos — digamos, 25% de cada vez — enquanto as restantes continuam servindo. O load balancer direciona tráfego apenas para instâncias saudáveis; se uma falhar nos health checks após a atualização, o rollout para. É o comportamento padrão do Kubernetes com um Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0    # nunca reduz capacidade
      maxSurge: 1          # sobe uma instância nova antes de derrubar uma velha
  template:
    spec:
      containers:
        - name: api
          image: minha-api:v2
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 3

O readinessProbe é o detalhe que faz isso funcionar de verdade: o Kubernetes só manda tráfego para um pod quando ele responde como saudável. Sem isso, requisições chegam em instâncias que ainda estão inicializando.

Blue-green deployment mantém dois ambientes idênticos — blue rodando a versão atual, green recebendo a nova. Quando o green está pronto e validado, o load balancer muda o tráfego de uma vez. Rollback é instantâneo: basta apontar de volta para o blue. O custo é dobrar a infraestrutura durante o deploy, o que em clouds modernas é temporário e aceitável.

// Exemplo simplificado de troca via AWS SDK
import { ElasticLoadBalancingV2Client, ModifyListenerCommand } from '@aws-sdk/client-elastic-load-balancing-v2';

const client = new ElasticLoadBalancingV2Client({ region: 'us-east-1' });

async function switchTraffego(listenerArn, greenTargetGroupArn) {
  await client.send(new ModifyListenerCommand({
    ListenerArn: listenerArn,
    DefaultActions: [{
      Type: 'forward',
      TargetGroupArn: greenTargetGroupArn,
    }],
  }));
  console.log('Tráfego redirecionado para o ambiente green');
}

Canary release é blue-green com granularidade: em vez de migrar 100% do tráfego de uma vez, você começa com 5%, observa métricas por alguns minutos ou horas, e vai aumentando gradualmente. Se as taxas de erro subirem ou a latência piorar, você reverte antes de afetar a maioria dos usuários. É a estratégia que melhor equilibra velocidade de deploy com controle de risco.

# Ajuste de peso via boto3 — 10% para canary, 90% para stable
import boto3

elbv2 = boto3.client('elbv2')

elbv2.modify_listener(
    ListenerArn='arn:aws:elasticloadbalancing:...',
    DefaultActions=[{
        'Type': 'forward',
        'ForwardConfig': {
            'TargetGroups': [
                {'TargetGroupArn': 'arn:...stable', 'Weight': 90},
                {'TargetGroupArn': 'arn:...canary', 'Weight': 10},
            ],
            'TargetGroupStickinessConfig': {'Enabled': True, 'DurationSeconds': 300}
        }
    }]
)

O que o código não resolve sozinho

Estratégias de deploy cuidam do tráfego e das instâncias. Mas zero-downtime tem uma dimensão que vive fora do código da aplicação: migrações de banco de dados.

Adicionar uma coluna com NOT NULL sem valor padrão em uma tabela com milhões de linhas trava o banco inteiro por minutos em PostgreSQL — independente de quantos pods Kubernetes você tenha. O padrão para evitar isso é expand/contract: primeiro você adiciona a coluna como nullable (expand), deploya a aplicação que escreve nos dois formatos, depois preenche os valores existentes em background, adiciona a constraint, e por fim remove o código de compatibilidade (contract). São três deploys onde antes havia um, mas nenhum deles causa downtime.

O mesmo princípio vale para renomear colunas, trocar tipos, e remover campos. A versão nova do código não pode assumir que a migração já rodou — especialmente em rolling deployments, onde v1 e v2 da aplicação coexistem por alguns minutos.

Outro ponto que escapa nos planejamentos: conexões longas. Quando um pod é marcado para terminar, o Kubernetes envia SIGTERM e aguarda o terminationGracePeriodSeconds antes de forçar a morte. Se a aplicação não trata esse sinal, requisições em andamento são cortadas no meio. O tratamento correto é parar de aceitar novas conexões ao receber SIGTERM, deixar as existentes terminarem, e só então sair.

// Graceful shutdown em Node.js
const server = app.listen(8080);

process.on('SIGTERM', () => {
  server.close(() => {
    // conexões existentes drenaram, pode sair
    process.exit(0);
  });
});

Monitoramento não é opcional nesse contexto — é parte da estratégia. Um canary release sem observabilidade é só um deploy com nome diferente. O que você precisa acompanhar durante qualquer rollout: taxa de erro por versão, latência de p95 e p99, e health checks de readiness. Se qualquer uma dessas métricas piorar durante o rollout, o processo deve parar automaticamente — não esperar alguém perceber no Slack.

Kubernetes tem maxUnavailable: 0 e maxSurge para controlar isso no nível de pods. Para canary com controle mais fino, ferramentas como Argo Rollouts ou Flagger automatizam a progressão baseada em métricas do Prometheus: se o error rate do canary ficar abaixo de 1% por 10 minutos, sobe para 25%; se passar de 2% em qualquer janela, reverte sozinho.

O objetivo final não é deploy sem downtime por sorte — é deploy sem downtime por design, onde o sistema detecta e reage a problemas antes que afetem usuários o suficiente para importar.

Referências

  • Kubernetes. Deployments. https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
  • Argo Rollouts. Canary Deployments. https://argoproj.github.io/argo-rollouts/
  • Flagger. Automated canary deployments. https://flagger.app/
  • Fowler, M. BlueGreenDeployment. https://martinfowler.com/bliki/BlueGreenDeployment.html
  • Netflix. Simian Army. https://github.com/Netflix/simianarmy/wiki