# 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 ```bash 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: ```bash # 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. ```bash # 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: # 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. ```bash # 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 ```bash # 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 ```bash ./cleanup.sh ``` ## Troubleshooting ### Canary fica em "Initializing" por muito tempo Verifique se o Flagger está rodando: ```bash 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: ```bash kubectl get vmpodscrape -n monitoring ``` E se os sidecars estão injetados: ```bash 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: ```bash 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: ```yaml 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: ```promql 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 - [Flagger — Progressive Delivery](https://flagger.app/) - [Flagger + Istio Tutorial](https://docs.flagger.app/tutorials/istio-progressive-delivery) - [Istio Traffic Management](https://istio.io/latest/docs/concepts/traffic-management/) - [OpenTelemetry Ruby SDK](https://opentelemetry.io/docs/languages/ruby/) - [Grafana Tempo — TraceQL](https://grafana.com/docs/tempo/latest/traceql/) - [USE Method (Utilization, Saturation, Errors)](https://www.brendangregg.com/usemethod.html)