Canary releases: liberação gradual com análise de métricas

1. Fundamentos das Canary Releases no Ecossistema DevOps

Canary releases são uma estratégia de deploy onde uma nova versão de software é liberada gradualmente para um subconjunto de usuários antes de ser disponibilizada para todos. O nome vem da prática histórica de usar canários em minas de carvão como sensores de gás — assim como o pássaro indicava perigo, a versão canary sinaliza problemas antes que afetem toda a base de usuários.

Diferentemente de blue-green deployments (que trocam todo o tráfego entre ambientes idênticos) ou rolling updates (que substituem pods sequencialmente), as canary releases permitem expor apenas uma fração do tráfego à nova versão, possibilitando análise de métricas em tempo real antes de decidir pela promoção ou rollback.

Os princípios fundamentais são: redução de risco, validação em produção com audiência limitada e tomada de decisão baseada em dados observáveis.

2. Arquitetura de Canary no Kubernetes

No Kubernetes, uma canary release típica utiliza dois Deployments — um para a versão estável (stable) e outro para a canary. Ambos compartilham o mesmo Service, mas com labels e selectors que permitem roteamento diferenciado.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-stable
  labels:
    app: myapp
    version: stable
spec:
  replicas: 10
  selector:
    matchLabels:
      app: myapp
      version: stable
  template:
    metadata:
      labels:
        app: myapp
        version: stable
    spec:
      containers:
      - name: app
        image: myapp:1.0.0
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-canary
  labels:
    app: myapp
    version: canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
      version: canary
  template:
    metadata:
      labels:
        app: myapp
        version: canary
    spec:
      containers:
      - name: app
        image: myapp:1.1.0

O Service único precisa selecionar ambos os conjuntos de pods:

apiVersion: v1
kind: Service
metadata:
  name: app-service
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 3000

Para roteamento mais granular, utilizamos Service Mesh como Istio ou Linkerd. Com Istio, criamos VirtualServices e DestinationRules para controlar pesos de tráfego.

3. Estratégias de Roteamento de Tráfego

Roteamento por peso

Com Istio, definimos um VirtualService que distribui o tráfego entre as versões:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app-vs
spec:
  hosts:
  - app-service
  http:
  - route:
    - destination:
        host: app-service
        subset: stable
      weight: 95
    - destination:
        host: app-service
        subset: canary
      weight: 5

E as DestinationRules correspondentes:

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: app-dr
spec:
  host: app-service
  subsets:
  - name: stable
    labels:
      version: stable
  - name: canary
    labels:
      version: canary

Roteamento baseado em cabeçalhos

Para testes A/B, podemos rotear usuários específicos via cookies ou headers:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app-vs-header
spec:
  hosts:
  - app-service
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: app-service
        subset: canary
  - route:
    - destination:
        host: app-service
        subset: stable

4. Métricas e Health Checks para Decisão de Rollout

As métricas críticas para avaliar uma canary release incluem:

  • Latência: p50, p95, p99 — aumentos indicam degradação de performance
  • Taxa de erro: HTTP 5xx, exceções não tratadas
  • Throughput: requisições por segundo (RPS)
  • Consumo de recursos: CPU, memória, I/O

Com Prometheus e Grafana, monitoramos em tempo real:

# Exemplo de query Prometheus para taxa de erro
sum(rate(http_requests_total{version="canary", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{version="canary"}[5m]))

Health probes do Kubernetes garantem que pods problemáticos sejam removidos:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-canary
spec:
  template:
    spec:
      containers:
      - name: app
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 5
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 3

5. Automação do Rollout com Análise de Métricas

Ferramentas como Flagger e Argo Rollouts automatizam o progressive delivery. Com Flagger, definimos um recurso Canary que orquestra todo o processo:

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: app-canary
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app
  service:
    port: 80
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 1m
    webhooks:
    - name: load-test
      url: http://loadtester.flagger:8080/
      timeout: 5s

O Flagger monitora as métricas a cada intervalo. Se os thresholds forem violados, realiza rollback automático. Caso contrário, incrementa o peso da canary até 100%.

6. Exemplo Prático de Canary Release com Docker e Kubernetes

Dockerfile da aplicação (microsserviço Node.js)

FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]

Aplicação simples com endpoint de métricas

const express = require('express');
const app = express();
const version = process.env.VERSION || '1.0.0';

app.get('/', (req, res) => {
  res.json({ version, message: 'Hello from canary!' });
});

app.get('/health', (req, res) => res.status(200).send('OK'));
app.get('/ready', (req, res) => res.status(200).send('Ready'));

app.listen(3000);

Manifesto completo com Flagger Canary

Após instalar o Flagger no cluster, aplicamos:

kubectl apply -f deployment-stable.yaml
kubectl apply -f deployment-canary.yaml
kubectl apply -f service.yaml
kubectl apply -f canary.yaml

O Flagger gerencia automaticamente a progressão do tráfego, monitora métricas do Prometheus e decide por promoção ou rollback.

7. Desafios e Boas Práticas Operacionais

Gerenciamento de estado de sessão: Sessões HTTP ou WebSocket podem quebrar se o tráfego for roteado entre versões. Use sticky sessions (session affinity) no Service ou cookies consistentes.

Cache distribuído: Versões simultâneas podem ter formatos de cache incompatíveis. Utilize versionamento de chave ou cache-sidecar com isolamento.

Migrações de banco de dados: Migrações devem ser backward-compatible por pelo menos duas versões. Estratégias como expand-migrate-contract (expandir schema, migrar dados, contrair schema antigo) são essenciais.

Observabilidade: Combine logs estruturados (JSON), tracing distribuído com Jaeger e dashboards unificados no Grafana para correlação rápida de problemas.

8. Integração com Ferramentas Vizinhas da Série

Canary + Chaos Engineering: Injete falhas controladas (ex: latência, falha de pod) durante o rollout para testar a resiliência da canary. Ferramentas como Chaos Mesh integram-se com Flagger.

Canary + Feature Flags: Use feature flags (LaunchDarkly, Unleash) para ativar/desativar funcionalidades dentro da versão canary, permitindo testes mais granulares sem novo deploy.

Canary + Database Migrations: Ferramentas como Flyway ou Liquibase devem executar migrações em modo "migrate-only" durante a fase canary, garantindo que o schema seja compatível com ambas as versões.


Referências