Files
workshop/aula-16
ArgoCD Setup 60e69c9ff6 feat: adicionar aula-16 (Flagger), remover Jaeger da aula-14 e integrar tracing Istio na aula-15
- aula-16: nova aula de canary deployment automatizado com Flagger
- aula-14: removido Jaeger (tracing movido para aula-15), corrigido
  nome do serviço Victoria Metrics, adicionado rate limiting no Kiali
- aula-15: adicionado receiver Zipkin no OTel Collector para receber
  traces do Istio, corrigida porta do Tempo (3100→3200), integração
  automática com Istio quando detectado
- aula-03: corrigido MAX_REQUESTS de 10 para 3 (valor padrão)
- CLAUDE.md: adicionada aula-16 na documentação
2026-04-10 08:43:01 -03:00
..

Aula 16 - Canary Automatizado com Flagger

Motivação

Na aula 14, fizemos canary deployment na mão: alteramos os pesos do VirtualService com kubectl patch, observamos o comportamento, e decidimos promover ou reverter. Funcionou — mas imagine fazer isso toda sexta-feira às 18h com o time te pressionando pra deployar logo.

O Flagger resolve esse problema. Ele é um operador Kubernetes que automatiza o ciclo completo de canary deployment:

  1. Detecta que uma nova versão foi deployada
  2. Cria pods canary com a nova versão
  3. Redireciona uma fração do tráfego (ex: 10%)
  4. Consulta métricas (taxa de sucesso, latência)
  5. Se tudo estiver saudável, aumenta o tráfego (20%, 30%, 50%...)
  6. Se algo estiver errado, reverte automaticamente para a versão anterior

Nesta aula, vamos aplicar o Flagger a um sistema real em produção: o Streamify.

O que o Flagger faz por baixo dos panos

Quando você cria um recurso Canary apontando para um Deployment, o Flagger assume o controle:

                          Você tem:
                          ─────────
                          Deployment "web"    (sua app)
                          Service "web"       (ClusterIP)

                          Flagger cria:
                          ─────────────
                          Deployment "web-primary"   (cópia estável, recebe 100% do tráfego)
                          Service "web-primary"      (aponta pros pods primary)
                          Service "web-canary"       (aponta pros pods canary)
                          VirtualService             (controla os pesos via Istio)

O Deployment original (web) vira o "template" — quando você muda a image tag dele, o Flagger interpreta como "nova versão" e inicia o ciclo canary:

  CI muda image tag ──► ArgoCD sync ──► Flagger detecta mudança
                                              │
                                              ▼
                                     Cria pods canary (nova versão)
                                              │
                                              ▼
                                     VirtualService: 10% canary, 90% primary
                                              │
                                              ▼
                                     Consulta Victoria Metrics:
                                       - Taxa de sucesso > 99%?
                                       - Latência p99 < 500ms?
                                              │
                                         ┌────┴────┐
                                         │         │
                                        OK?      Falhou?
                                         │         │
                                         ▼         ▼
                                  Aumenta peso   Rollback automático
                                  20% → 30%...   (0% canary, 100% primary)
                                         │
                                         ▼
                                  50% atingido?
                                         │
                                         ▼
                                  Promove: primary = nova versão
                                  VirtualService: 100% primary

Arquitetura

┌─────────────────────────────────────────────────────────────────────┐
│                         Cluster Kubernetes                          │
│                                                                     │
│  ┌─── istio-system ──────────────────────────────────────────────┐  │
│  │  istiod          Flagger Controller                           │  │
│  │                       │                                       │  │
│  │                       │ observa Canary CRD                    │  │
│  │                       │ gerencia VirtualService                │  │
│  │                       │ consulta métricas                     │  │
│  └───────────────────────┼───────────────────────────────────────┘  │
│                          │                                          │
│  ┌─── monitoring ────────┼───────────────────────────────────────┐  │
│  │                       │                                       │  │
│  │  Victoria Metrics ◄───┘ query: istio_requests_total           │  │
│  │       ▲                         istio_request_duration_*      │  │
│  │       │ scrape (:15020)                                       │  │
│  └───────┼───────────────────────────────────────────────────────┘  │
│          │                                                          │
│  ┌─── streamify-production ──────────────────────────────────────┐  │
│  │       │                                                       │  │
│  │  ┌────┴─────┐    ┌──────────────┐    ┌──────────────┐        │  │
│  │  │ Envoy    │    │  web-primary  │    │  web-canary   │       │  │
│  │  │ sidecar  │    │  (v1 estável) │    │  (v2 nova)    │       │  │
│  │  └──────────┘    └──────────────┘    └──────────────┘        │  │
│  │                         ▲                    ▲                │  │
│  │                    90% ─┘                    └─ 10%           │  │
│  │                         VirtualService (Istio)                │  │
│  │                              ▲                                │  │
│  │  Internet ──► NGINX ──► svc/web-primary                      │  │
│  │              Ingress                                          │  │
│  │                                                               │  │
│  │  ┌────────────┐  ┌────────────┐  ┌──────────┐               │  │
│  │  │   queue     │  │  schedule   │  │ postgres  │  (sem canary)│  │
│  │  └────────────┘  └────────────┘  └──────────┘               │  │
│  └───────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────┘

Conceitos

Conceito Descrição
Canary CRD Recurso customizado do Flagger que define a estratégia de deploy progressivo
Primary Deployment estável que recebe a maior parte do tráfego
Canary Pods temporários com a nova versão, recebendo tráfego incremental
Analysis Loop periódico onde Flagger consulta métricas e decide avançar ou reverter
Promotion Quando o canary atinge maxWeight com sucesso, a nova versão se torna primary
Rollback Quando métricas falham além do threshold, Flagger reverte para a versão anterior
stepWeight Incremento de tráfego a cada iteração (ex: 10% → 20% → 30%)
maxWeight Peso máximo do canary antes de promover (ex: 50%)
threshold Número de falhas toleradas antes de fazer rollback
Loadtester Componente que gera tráfego sintético para o canary ter métricas para análise

Por que Canary só no Web?

O Flagger analisa métricas HTTP (taxa de sucesso, latência). Os workers de queue e o scheduler não recebem tráfego HTTP externo — não há métricas HTTP para analisar. Por isso, apenas o Deployment web é gerenciado pelo Flagger. Queue e scheduler continuam com RollingUpdate normal.

Regra de ouro: Migrations

Durante um canary, duas versões da aplicação rodam simultaneamente contra o mesmo banco de dados. Isso significa que migrations devem ser sempre aditivas:

Seguro durante canary Perigoso durante canary
ADD COLUMN DROP COLUMN
ADD TABLE RENAME COLUMN
ADD INDEX DROP TABLE
ALTER COLUMN SET DEFAULT ALTER COLUMN TYPE

Se precisar remover uma coluna, faça em duas releases:

  1. Release A (canary): deploy do código que não usa mais a coluna
  2. Release B (depois de promoted): migration que remove a coluna

Pré-requisitos

  • Cluster Kubernetes (aula-08)
  • Istio instalado (aula-14)
  • Victoria Metrics (aula-12)
  • Tempo + OTel Collector (aula-15)
  • ArgoCD (aula-11)
  • Streamify deployado em produção (streamify-production)
  • kubectl, helm

Estrutura

aula-16/
├── README.md              # Esta documentação
├── setup.sh               # Instalação automatizada
├── cleanup.sh             # Remoção limpa
└── flagger-values.yaml    # Configuração do Flagger

Instalação

cd aula-16
./setup.sh

O script vai:

  1. Verificar pré-requisitos
  2. Instalar o Flagger no cluster
  3. Configurar coleta de métricas do Istio
  4. Habilitar sidecar injection no namespace de produção
  5. Aplicar as mudanças no Helm chart do Streamify
  6. Aguardar o Flagger inicializar

Verificação

Após a instalação, verifique se o Flagger inicializou corretamente:

# Status do Canary
kubectl get canary -n streamify-production

# Deve mostrar:
# NAME                        STATUS        WEIGHT
# streamify-production-web    Initialized   0

# Services criados pelo Flagger
kubectl get svc -n streamify-production | grep web

# Deve mostrar 3 services:
# streamify-production-web           (original, gerenciado pelo Flagger)
# streamify-production-web-primary   (versão estável)
# streamify-production-web-canary    (versão nova durante canary)

# VirtualService criado pelo Flagger
kubectl get virtualservice -n streamify-production

Exercício 1: Deploy com sucesso

Simule um deploy normal. O Flagger vai criar o canary, analisar as métricas, e promover automaticamente.

# 1. Observe o canary em tempo real (deixe rodando num terminal)
kubectl get canary -n streamify-production -w

# 2. Em outro terminal, faça um deploy (mude a image tag)
#    No repo streamify-deploy, edite values-production.yaml:
#    web.image.tag: <novo-sha>
#    Commit e push. ArgoCD vai sincronizar.

# 3. Acompanhe o progresso
kubectl describe canary streamify-production-web -n streamify-production

# 4. Observe no Kiali o tráfego sendo dividido
#    https://kiali.kube.quest/kiali/

Saída esperada:

NAME                        STATUS        WEIGHT   LASTTRANSITION
streamify-production-web    Progressing   0        ...
streamify-production-web    Progressing   10       ...
streamify-production-web    Progressing   20       ...
streamify-production-web    Progressing   30       ...
streamify-production-web    Progressing   40       ...
streamify-production-web    Progressing   50       ...
streamify-production-web    Promoting     0        ...
streamify-production-web    Succeeded     0        ...

Exercício 2: Deploy com falha (rollback automático)

Simule um deploy defeituoso. O Flagger vai detectar a falha nas métricas e reverter automaticamente.

# 1. Observe o canary em tempo real
kubectl get canary -n streamify-production -w

# 2. Faça um deploy com uma imagem que retorna erros
#    Use uma tag inválida ou uma versão com bug

# 3. O Flagger vai detectar:
#    - Taxa de sucesso < 99%
#    - Latência > 500ms
#    E vai reverter automaticamente

# 4. Verifique os eventos
kubectl get events -n streamify-production --sort-by='.lastTimestamp' | grep -i canary

Saída esperada:

NAME                        STATUS    WEIGHT   LASTTRANSITION
streamify-production-web    Failed    0        ...

Exercício 3: Observabilidade

Durante um canary ativo, observe:

  1. Kiali (https://kiali.kube.quest/kiali/) — Graph mostra tráfego dividido entre primary e canary
  2. Grafana/Tempo — Explore > Tempo > traces mostram requests para ambas as versões
  3. Victoria Metrics — Query: istio_requests_total{destination_workload=~"streamify.*"}

Comandos úteis

# Status do canary
kubectl get canary -n streamify-production

# Detalhes e eventos
kubectl describe canary streamify-production-web -n streamify-production

# Logs do Flagger
kubectl logs deployment/flagger -n istio-system -f

# Pesos atuais do VirtualService
kubectl get virtualservice streamify-production-web -n streamify-production -o jsonpath='{.spec.http[0].route}' | python3 -m json.tool

# Forçar promoção manual (emergência)
kubectl annotate canary streamify-production-web -n streamify-production \
  flagger.app/promote="true" --overwrite

# Forçar rollback manual (emergência)
kubectl annotate canary streamify-production-web -n streamify-production \
  flagger.app/rollback="true" --overwrite

Cleanup

./cleanup.sh

Troubleshooting

Canary fica em "Initializing" por muito tempo

Verifique se o Flagger está rodando:

kubectl get pods -n istio-system -l app.kubernetes.io/name=flagger
kubectl logs deployment/flagger -n istio-system --tail=20

Métricas Istio não aparecem no Victoria Metrics

Verifique se os VMPodScrapes foram criados:

kubectl get vmpodscrape -n monitoring

E se os sidecars estão injetados:

kubectl get pods -n streamify-production -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].name}{"\n"}{end}'

Cada pod deve ter 2 containers: web e istio-proxy.

Canary falha imediatamente (threshold reached)

Geralmente significa que não há métricas suficientes. Verifique:

  1. O loadtester está gerando tráfego?
  2. Victoria Metrics tem dados? istio_requests_total{destination_workload="streamify-production-web"}

Ingress retorna 503 após habilitar canary

O Ingress precisa apontar para o service -primary. Verifique:

kubectl get ingress -n streamify-production -o yaml | grep "name:"

ArgoCD mostra "OutOfSync" constantemente

O Flagger modifica o VirtualService dinamicamente. Configure ignoreDifferences no ArgoCD Application:

ignoreDifferences:
  - group: networking.istio.io
    kind: VirtualService
    jsonPointers:
      - /spec/http/0/route

Dashboard: Streamify - Observabilidade Istio

O setup instala um dashboard no Grafana com 4 painéis que funcionam out-of-the-box com as métricas do Istio:

Painel O que mostra Query
Request Rate Requests/s por serviço sum by(destination_service_name) (rate(istio_requests_total{...}[5m]))
Error Rate (5xx) Erros por serviço e código rate(istio_requests_total{response_code=~"5.*"}[5m])
Latência p99 Percentil 99 de latência histogram_quantile(0.99, rate(istio_request_duration_milliseconds_bucket{...}[5m]))
Saturação de Memória % do memory limit usado container_memory_working_set_bytes / container_spec_memory_limit_bytes

Acesse: Grafana > Dashboards > Streamify - Observabilidade Istio

O que esses painéis revelam durante um canary

  • Request Rate: pico do loadtester visível (~15 req/s), tráfego dividido entre primary e canary
  • Error Rate: se a nova versão gera 500s, aparece aqui antes do Flagger reverter
  • Latência p99: degradação de performance da nova versão é visível por spike de latência
  • Saturação: se a nova versão tem memory leak, o painel mostra a curva subindo

Além do Workshop: Observabilidade em Produção

Este workshop foca no pipeline deploy → métricas → decisão automática. Em produção real, o Grafana pode ir muito além. Abaixo está o que é viável em cada nível, com as limitações intencionais do workshop documentadas.

Nível 1: Já funciona com o que temos (só falta dashboard)

Tudo abaixo usa métricas que o Istio + kubelet já exportam:

CPU Throttling — Mais útil que "% de CPU". Mostra quando o kernel está limitando o container:

rate(container_cpu_cfs_throttled_seconds_total{namespace="streamify-production"}[5m])

Deploy Impact — Correlacionar timestamps de deploy com error rate. O Flagger gera eventos Kubernetes que podem virar annotations no Grafana.

Request rate por response code — Ver a distribuição 200/404/500 ao longo do tempo. Detecta regressões antes do usuário reclamar.

Nível 2: Viável com instrumentação na app (~dias de trabalho)

Requer mudanças no código do Streamify (OTel SDK para Rails):

Latência por camada (app vs banco vs fila) — O Istio só dá o trace da camada de rede. Para ver "quanto tempo gastou no Postgres" ou "quanto tempo na fila do Sidekiq", precisa instrumentar o Rails com OpenTelemetry SDK. O OTel Collector e Tempo já estão prontos para receber esses traces.

Métricas de negócio — Leads processados/min, mensagens enviadas, conversões por hora. Requer que a app exporte métricas custom via /metrics (Prometheus client) ou OTel SDK. Combinar infra + negócio no mesmo dashboard responde: "a infra impactou receita?"

Custo por namespace/cliente — Para multi-tenant: custo por pod, por feature, por cliente. Requer dados de billing (Hetzner API) + alocação de recursos por label.

Nível 3: Possível mas desproporcional para o contexto atual

Documentado para referência — faz sentido quando o sistema crescer:

Detecção de anomalia sem threshold fixo — Baseline histórico em vez de "alerta se CPU > 80%". Na prática, precisa de volume de dados significativo para o modelo estatístico funcionar. Com o tráfego atual do Streamify, thresholds fixos são mais confiáveis.

Fingerprint de falhas — Padrões como "CPU normal + latência alta + fila crescendo = problema de I/O externo". Faz sentido com 50+ microserviços onde os padrões se repetem. Com 1 app, o troubleshooting manual é mais rápido.

Auto-healing contextual — Escalar baseado em tipo de erro, trocar rotas, fallback automático. O Flagger + HPA já cobrem 90% disso. O restante é overengineering para o tamanho atual.

ROI por recurso — Cruzar custo de infra com valor gerado. Poderoso para SaaS em escala, mas o esforço de implementação não se paga com poucos serviços.

Por que o workshop para aqui

O objetivo das aulas 14-16 é demonstrar o ciclo completo:

código → CI → imagem → ArgoCD → Flagger → métricas → decisão → promote/rollback
                                                ↕
                                          traces (Tempo)
                                          métricas (Victoria Metrics)
                                          visualização (Grafana + Kiali)

Esse pipeline já resolve o problema real: deploy seguro com rollback automático baseado em dados. Os níveis 2 e 3 são extensões naturais conforme o sistema cresce — a infraestrutura de observabilidade (Tempo, OTel Collector, Victoria Metrics) já está pronta para recebê-los.

Referências