60e69c9ff6
- 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
428 lines
20 KiB
Markdown
428 lines
20 KiB
Markdown
# 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: <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.
|
|
|
|
```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)
|