From 60e69c9ff6b8f02c9288434159cedd46c1d9d0af Mon Sep 17 00:00:00 2001 From: ArgoCD Setup Date: Fri, 10 Apr 2026 08:43:01 -0300 Subject: [PATCH] feat: adicionar aula-16 (Flagger), remover Jaeger da aula-14 e integrar tracing Istio na aula-15 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CLAUDE.md | 2 + aula-03/configmap.yaml | 2 +- aula-14/README.md | 34 +- aula-14/cleanup.sh | 14 +- aula-14/istio/jaeger-values.yaml | 29 -- aula-14/istio/kiali-values.yaml | 6 +- aula-14/k8s/ingress-jaeger.yaml | 25 - aula-14/k8s/ingress-kiali.yaml | 3 + aula-14/setup.sh | 55 +-- aula-15/otel-collector-values.yaml | 9 +- aula-15/setup.sh | 59 ++- aula-16/README.md | 427 +++++++++++++++++ aula-16/cleanup.sh | 117 +++++ aula-16/flagger-values.yaml | 5 + aula-16/setup.sh | 739 +++++++++++++++++++++++++++++ 15 files changed, 1397 insertions(+), 129 deletions(-) delete mode 100644 aula-14/istio/jaeger-values.yaml delete mode 100644 aula-14/k8s/ingress-jaeger.yaml create mode 100644 aula-16/README.md create mode 100755 aula-16/cleanup.sh create mode 100644 aula-16/flagger-values.yaml create mode 100755 aula-16/setup.sh diff --git a/CLAUDE.md b/CLAUDE.md index db1b4c2..ed6ab29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,6 +26,7 @@ App de demonstração: `node-bugado` - trava após N requests para demonstrar he | 13 | Container Factory (eStargz) | Hetzner | | 14 | Istio Traffic Splitting | Hetzner | | 15 | APM: Grafana Tempo + OpenTelemetry | Hetzner | +| 16 | Canary Automatizado com Flagger | Hetzner | ## Comandos Rápidos @@ -42,6 +43,7 @@ cd aula-12 && ./setup.sh # Victoria Metrics + Grafana cd aula-13 && ./setup.sh # Container Factory cd aula-14 && ./setup.sh # Istio Traffic Splitting cd aula-15 && ./setup.sh # APM: Tempo + OpenTelemetry +cd aula-16 && ./setup.sh # Canary Automatizado com Flagger ``` ## App node-bugado diff --git a/aula-03/configmap.yaml b/aula-03/configmap.yaml index 4946d2f..1f600c8 100644 --- a/aula-03/configmap.yaml +++ b/aula-03/configmap.yaml @@ -3,7 +3,7 @@ kind: ConfigMap metadata: name: app-config data: - MAX_REQUESTS: "10" + MAX_REQUESTS: "3" app.js: | const http = require("http"); diff --git a/aula-14/README.md b/aula-14/README.md index 98d0eb8..6777b94 100644 --- a/aula-14/README.md +++ b/aula-14/README.md @@ -1,6 +1,6 @@ # Aula 14 - Istio Traffic Splitting -Demonstração de **app-backend deployment** usando Istio para dividir tráfego entre duas versões da aplicação. +Demonstração de **canary deployment manual** usando Istio para dividir tráfego entre duas versões da aplicação. ## Motivação @@ -29,9 +29,9 @@ Em produção, lançar uma nova versão diretamente para 100% dos usuários é a │ │ bug │ │ ok │ │ │ └──────┘ └──────┘ │ │ │ - │ ┌─────────┐ ┌────────┐ ┌─────────────────┐│ - │ │ Kiali │ │ Jaeger │ │ Victoria Metrics││ - │ └─────────┘ └────────┘ └─────────────────┘│ + │ ┌─────────┐ ┌─────────────────┐ │ + │ │ Kiali │ │ Victoria Metrics │ │ + │ └─────────┘ └─────────────────┘ │ └─────────────────────────────────────────────┘ ``` @@ -72,11 +72,10 @@ aula-14/ │ ├── deployment-v2.yaml │ ├── service.yaml │ ├── destination-rule.yaml # Define subsets v1, v2 -│ └── virtual-service.yaml # Traffic splitting (90/10) +│ ├── virtual-service.yaml # Traffic splitting (90/10) +│ └── ingress-kiali.yaml └── istio/ - ├── kiali-values.yaml - ├── jaeger-values.yaml - └── gateway.yaml + └── kiali-values.yaml ``` ## Instalação @@ -89,9 +88,9 @@ cd aula-14 O script irá: 1. Verificar pré-requisitos (incluindo Victoria Metrics da aula-12) 2. Coletar configuração (domínio, registry, TLS) -3. Instalar Istio (istio-base + istiod) -4. Instalar Kiali e Jaeger (métricas via Victoria Metrics) -5. Configurar Ingress para dashboards +3. Instalar Istio (istio-base + istiod + ingressgateway) +4. Instalar Kiali (dashboard do service mesh, métricas via Victoria Metrics) +5. Configurar Ingress com Basic Auth e rate limiting 6. Build e push das imagens v1 e v2 7. Deploy da aplicação com traffic splitting 90/10 @@ -152,9 +151,9 @@ kubectl patch virtualservice app-backend -n istio --type='json' \ {"op":"replace","path":"/spec/http/0/route/1/weight","value":50}]' ``` -### 4. Verificar traces no Jaeger +### 4. Observar no Kiali -Abra o Jaeger e observe os traces das requisições passando pelo mesh. +Volte ao Kiali e veja o tráfego redistribuído igualmente entre v1 e v2. ### 5. Rollout completo para v2 @@ -185,9 +184,6 @@ kubectl logs -n istio -l app=app-backend,version=v2 -f # Port-forward para Kiali (alternativa ao Ingress) kubectl port-forward svc/kiali -n istio-system 20001:20001 - -# Port-forward para Jaeger -kubectl port-forward svc/tracing -n istio-system 16686:80 ``` ## Cleanup @@ -196,7 +192,7 @@ kubectl port-forward svc/tracing -n istio-system 16686:80 ./cleanup.sh ``` -Remove Istio, addons e namespace da aplicação. +Remove Istio, Kiali e namespace da aplicação. ## Troubleshooting @@ -228,3 +224,7 @@ Verificar se DestinationRule existe: ```bash kubectl get destinationrule app-backend -n istio -o yaml ``` + +### E o tracing? + +O tracing distribuído (ver o caminho de um request individual) é configurado na **aula-15** com Grafana Tempo + OpenTelemetry. O Istio envia traces automaticamente para o Tempo após a aula-15 ser instalada. diff --git a/aula-14/cleanup.sh b/aula-14/cleanup.sh index 4833d95..e407a7a 100755 --- a/aula-14/cleanup.sh +++ b/aula-14/cleanup.sh @@ -4,7 +4,7 @@ # ============================================================================ # Remove todos os componentes instalados pelo setup.sh: # - Namespace istio (aplicação) -# - Kiali, Jaeger (addons) +# - Kiali (addon) # - Istio (istiod + base) # - Ingress resources # @@ -37,9 +37,9 @@ echo -e "${CYAN}╚════════════════════ echo "" echo -e "${YELLOW}Este script irá remover:${NC}" echo " - Namespace 'istio' com aplicação" -echo " - Addons: Kiali, Jaeger" +echo " - Addon: Kiali" echo " - Istio: istiod e istio-base" -echo " - Ingress do Kiali e Jaeger" +echo " - Ingress do Kiali" echo "" read -p "Continuar? (digite 'sim' para confirmar): " confirm if [[ "$confirm" != "sim" ]]; then @@ -64,16 +64,14 @@ fi # Remover Ingress log_info "Removendo Ingress..." -kubectl delete ingress kiali jaeger -n istio-system --ignore-not-found=true 2>/dev/null || true +kubectl delete ingress kiali -n istio-system --ignore-not-found=true 2>/dev/null || true log_success "Ingress removidos" -# Remover addons (Kiali, Jaeger) +# Remover addon (Kiali) log_info "Removendo Kiali..." kubectl delete -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml 2>/dev/null || true -log_info "Removendo Jaeger..." -kubectl delete -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml 2>/dev/null || true -log_success "Addons removidos" +log_success "Addon removido" # Remover istiod if helm status istiod -n istio-system &> /dev/null; then diff --git a/aula-14/istio/jaeger-values.yaml b/aula-14/istio/jaeger-values.yaml deleted file mode 100644 index 76484e3..0000000 --- a/aula-14/istio/jaeger-values.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Jaeger - Distributed Tracing -# Tracing para visualizar requisições através do mesh - -provisionDataStore: - cassandra: false - -allInOne: - enabled: true - image: jaegertracing/all-in-one - tag: "1.62" - resources: - requests: - memory: "256Mi" - cpu: "100m" - limits: - memory: "512Mi" - cpu: "500m" - -storage: - type: memory - -collector: - enabled: false - -query: - enabled: false - -agent: - enabled: false diff --git a/aula-14/istio/kiali-values.yaml b/aula-14/istio/kiali-values.yaml index 8a143b3..f9d1229 100644 --- a/aula-14/istio/kiali-values.yaml +++ b/aula-14/istio/kiali-values.yaml @@ -15,10 +15,8 @@ deployment: external_services: prometheus: - url: http://vmsingle-victoria-metrics-k8s-stack.monitoring:8429 + url: http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429 tracing: - enabled: true - in_cluster_url: http://tracing.istio-system:16685/jaeger - use_grpc: true + enabled: false grafana: enabled: false diff --git a/aula-14/k8s/ingress-jaeger.yaml b/aula-14/k8s/ingress-jaeger.yaml deleted file mode 100644 index b85d08a..0000000 --- a/aula-14/k8s/ingress-jaeger.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: jaeger - namespace: istio-system - annotations: - nginx.ingress.kubernetes.io/backend-protocol: HTTP - nginx.ingress.kubernetes.io/auth-type: basic - nginx.ingress.kubernetes.io/auth-secret: basic-auth - nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" - ${TLS_ANNOTATION} -spec: - ingressClassName: nginx - ${TLS_CONFIG} - rules: - - host: ${JAEGER_HOST} - http: - paths: - - path: / - pathType: Prefix - backend: - service: - name: tracing - port: - number: 80 diff --git a/aula-14/k8s/ingress-kiali.yaml b/aula-14/k8s/ingress-kiali.yaml index 7d62814..045cca9 100644 --- a/aula-14/k8s/ingress-kiali.yaml +++ b/aula-14/k8s/ingress-kiali.yaml @@ -8,6 +8,9 @@ metadata: nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" + nginx.ingress.kubernetes.io/limit-rps: "20" + nginx.ingress.kubernetes.io/limit-connections: "10" + nginx.ingress.kubernetes.io/limit-burst-multiplier: "3" ${TLS_ANNOTATION} spec: ingressClassName: nginx diff --git a/aula-14/setup.sh b/aula-14/setup.sh index 1f6ffcc..cb9eae7 100755 --- a/aula-14/setup.sh +++ b/aula-14/setup.sh @@ -2,18 +2,16 @@ # ============================================================================ # Aula 14 - Istio Traffic Splitting # ============================================================================ -# Instala Istio com Kiali e Jaeger para demonstrar canary deployment +# Instala Istio com Kiali para demonstrar canary deployment # usando traffic splitting entre duas versões da aplicação. # # Componentes: # - Istio (istio-base + istiod) # - Kiali (visualização do service mesh) -# - Jaeger (tracing distribuído) # - Aplicação app-backend v1 e v2 # # Observabilidade: # - Usa Victoria Metrics da aula-12 para métricas -# - Jaeger para tracing distribuído # # Pré-requisitos: # - Cluster Kubernetes da aula-08 @@ -61,7 +59,6 @@ REGISTRY_HOST=${REGISTRY_HOST} REGISTRY_PROJECT=${REGISTRY_PROJECT} APP_HOST=${APP_HOST} KIALI_HOST=${KIALI_HOST} -JAEGER_HOST=${JAEGER_HOST} USE_LETSENCRYPT=${USE_LETSENCRYPT} LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} BASIC_AUTH_USER=${BASIC_AUTH_USER} @@ -96,7 +93,6 @@ collect_user_input() { echo -e " Registry: ${GREEN}${REGISTRY_HOST}${NC}" echo -e " App: ${GREEN}${APP_HOST}${NC}" echo -e " Kiali: ${GREEN}${KIALI_HOST}${NC}" - echo -e " Jaeger: ${GREEN}${JAEGER_HOST}${NC}" echo "" echo -e "[1] Usar configuração existente" echo -e "[2] Inserir nova configuração" @@ -119,7 +115,6 @@ collect_user_input() { echo "" APP_HOST=$(ask_hostname "App" "$APP_HOST" "app.${INHERITED_DOMAIN}") KIALI_HOST=$(ask_hostname "Kiali" "$KIALI_HOST" "kiali.${INHERITED_DOMAIN}") - JAEGER_HOST=$(ask_hostname "Jaeger" "$JAEGER_HOST" "jaeger.${INHERITED_DOMAIN}") # Owner/repo no Gitea para o registry (ex: root, demo, factory) if [[ -z "$REGISTRY_PROJECT" ]]; then @@ -149,9 +144,9 @@ collect_user_input() { USE_LETSENCRYPT=false fi - # Basic Auth para Kiali e Jaeger + # Basic Auth para Kiali echo "" - echo -e "${CYAN}Autenticação para Kiali e Jaeger:${NC}" + echo -e "${CYAN}Autenticação para Kiali:${NC}" if [[ -z "$BASIC_AUTH_USER" ]]; then BASIC_AUTH_USER="admin" fi @@ -204,9 +199,9 @@ check_prerequisites() { log_success "Cluster Kubernetes acessível" # Verificar Victoria Metrics (aula-12) - if kubectl get svc -n monitoring vmsingle-victoria-metrics-k8s-stack &> /dev/null; then + if kubectl get svc -n monitoring vmsingle-monitoring-victoria-metrics-k8s-stack &> /dev/null; then log_success "Victoria Metrics encontrado (aula-12)" - VICTORIA_METRICS_URL="http://vmsingle-victoria-metrics-k8s-stack.monitoring:8429" + VICTORIA_METRICS_URL="http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429" else log_warn "Victoria Metrics não encontrado. Instale a aula-12 primeiro." log_warn "Kiali funcionará sem métricas até Victoria Metrics estar disponível." @@ -274,14 +269,9 @@ install_istio() { install_observability() { echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" - echo -e "${CYAN} Instalando Kiali e Jaeger${NC}" + echo -e "${CYAN} Instalando Kiali${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" - log_info "Instalando Jaeger..." - kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/jaeger.yaml 2>/dev/null || true - log_success "Jaeger instalado" - - # Instalar Kiali com configuração para Victoria Metrics log_info "Instalando Kiali..." kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.24/samples/addons/kiali.yaml 2>/dev/null || true @@ -290,18 +280,16 @@ install_observability() { log_info "Configurando Kiali para usar Victoria Metrics..." kubectl patch configmap kiali -n istio-system --type merge -p "{ \"data\": { - \"config.yaml\": \"external_services:\\n prometheus:\\n url: ${VICTORIA_METRICS_URL}\\n tracing:\\n enabled: true\\n in_cluster_url: http://tracing.istio-system:16685/jaeger\\n use_grpc: true\\n\" + \"config.yaml\": \"auth:\\n strategy: anonymous\\nlogin_token:\\n signing_key: kiali-signing-key-1234\\nserver:\\n web_root: /kiali\\nexternalservices:\\n prometheus:\\n url: ${VICTORIA_METRICS_URL}\\n tracing:\\n enabled: false\\n grafana:\\n enabled: false\\n\" } }" 2>/dev/null || true - # Reiniciar Kiali para aplicar configuração kubectl rollout restart deployment/kiali -n istio-system 2>/dev/null || true fi log_success "Kiali instalado" - log_info "Aguardando pods de observabilidade..." + log_info "Aguardando Kiali..." kubectl wait --for=condition=available deployment/kiali -n istio-system --timeout=300s 2>/dev/null || true - kubectl wait --for=condition=available deployment/jaeger -n istio-system --timeout=300s 2>/dev/null || true - log_success "Observabilidade pronta" + log_success "Kiali pronto" } # ============================================================================ @@ -311,7 +299,7 @@ install_observability() { setup_basic_auth() { echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" - echo -e "${CYAN} Configurando Basic Auth para Kiali e Jaeger${NC}" + echo -e "${CYAN} Configurando Basic Auth para Kiali${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" log_info "Criando secret basic-auth..." @@ -340,7 +328,7 @@ setup_basic_auth() { setup_ingress() { echo "" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" - echo -e "${CYAN} Configurando Ingress para Kiali e Jaeger${NC}" + echo -e "${CYAN} Configurando Ingress para Kiali${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" local TLS_ANNOTATION="" @@ -360,19 +348,6 @@ setup_ingress() { export KIALI_HOST TLS_ANNOTATION TLS_CONFIG envsubst '${KIALI_HOST} ${TLS_ANNOTATION} ${TLS_CONFIG}' < "${SCRIPT_DIR}/k8s/ingress-kiali.yaml" | kubectl apply -f - log_success "Ingress do Kiali criado" - - log_info "Criando Ingress para Jaeger..." - if [[ "$USE_LETSENCRYPT" == "true" ]]; then - TLS_CONFIG="tls: - - hosts: - - ${JAEGER_HOST} - secretName: jaeger-tls" - else - TLS_CONFIG="" - fi - export JAEGER_HOST TLS_ANNOTATION TLS_CONFIG - envsubst '${JAEGER_HOST} ${TLS_ANNOTATION} ${TLS_CONFIG}' < "${SCRIPT_DIR}/k8s/ingress-jaeger.yaml" | kubectl apply -f - - log_success "Ingress do Jaeger criado" } setup_app_ingress() { @@ -521,7 +496,6 @@ show_summary() { echo -e "${GREEN}Componentes instalados:${NC}" echo " - Istio (istiod + base)" echo " - Kiali (dashboard do service mesh)" - echo " - Jaeger (tracing distribuído)" echo " - app-backend v1 e v2" echo "" if [[ -n "$VICTORIA_METRICS_URL" ]]; then @@ -532,10 +506,9 @@ show_summary() { echo "" echo -e "${GREEN}URLs:${NC}" echo -e " App: ${CYAN}${protocol}://${APP_HOST}${NC}" - echo -e " Kiali: ${CYAN}${protocol}://${KIALI_HOST}${NC}" - echo -e " Jaeger: ${CYAN}${protocol}://${JAEGER_HOST}${NC}" + echo -e " Kiali: ${CYAN}${protocol}://${KIALI_HOST}/kiali/${NC}" echo "" - echo -e "${GREEN}Credenciais (Basic Auth para Kiali/Jaeger):${NC}" + echo -e "${GREEN}Credenciais (Basic Auth para Kiali):${NC}" echo -e " Usuário: ${CYAN}${BASIC_AUTH_USER}${NC}" echo -e " Senha: ${CYAN}${BASIC_AUTH_PASS}${NC}" echo "" @@ -562,7 +535,6 @@ show_summary() { echo -e "${YELLOW}Configure DNS para:${NC}" echo " ${APP_HOST} -> IP do Ingress" echo " ${KIALI_HOST} -> IP do Ingress" - echo " ${JAEGER_HOST} -> IP do Ingress" echo "" } @@ -596,7 +568,6 @@ main() { echo "" echo -e " ${YELLOW}${APP_HOST}${NC}" echo -e " ${YELLOW}${KIALI_HOST}${NC}" - echo -e " ${YELLOW}${JAEGER_HOST}${NC}" echo "" if [[ "$USE_LETSENCRYPT" == "true" ]]; then echo -e "${YELLOW}⚠ O Let's Encrypt precisa do DNS configurado para emitir o certificado.${NC}" diff --git a/aula-15/otel-collector-values.yaml b/aula-15/otel-collector-values.yaml index 357a031..997fabb 100644 --- a/aula-15/otel-collector-values.yaml +++ b/aula-15/otel-collector-values.yaml @@ -37,7 +37,10 @@ ports: jaeger-grpc: enabled: false zipkin: - enabled: false + enabled: true + containerPort: 9411 + servicePort: 9411 + protocol: TCP config: receivers: @@ -47,6 +50,8 @@ config: endpoint: "0.0.0.0:4317" http: endpoint: "0.0.0.0:4318" + zipkin: + endpoint: "0.0.0.0:9411" connectors: spanmetrics: @@ -79,7 +84,7 @@ config: service: pipelines: traces: - receivers: [otlp] + receivers: [otlp, zipkin] processors: [memory_limiter, batch] exporters: [otlp/tempo, spanmetrics] metrics/spanmetrics: diff --git a/aula-15/setup.sh b/aula-15/setup.sh index ad0a2c1..6acc88b 100755 --- a/aula-15/setup.sh +++ b/aula-15/setup.sh @@ -209,7 +209,7 @@ data: datasources: - name: Tempo type: tempo - url: http://tempo.monitoring:3100 + url: http://tempo.monitoring:3200 access: proxy isDefault: false jsonData: @@ -413,6 +413,63 @@ log_success "Ingress demo-app criado" log_info "Aguardando demo-app ficar pronta..." kubectl wait --for=condition=available deployment/demo-app -n demo --timeout=180s 2>/dev/null || true +# ============================================================================= +# CONFIGURAR ISTIO TRACING (se Istio estiver instalado) +# ============================================================================= + +if kubectl get deployment istiod -n istio-system &> /dev/null; then + echo "" + log_info "=== Istio detectado — configurando tracing via OTel Collector ===" + + OTEL_ZIPKIN_URL="otel-collector-opentelemetry-collector.monitoring" + + # Configurar istiod para enviar traces ao OTel Collector via Zipkin + log_info "Configurando istiod para enviar traces ao OTel Collector..." + helm upgrade istiod istio/istiod -n istio-system \ + --set "meshConfig.defaultConfig.tracing.zipkin.address=${OTEL_ZIPKIN_URL}:9411" \ + --set 'meshConfig.defaultConfig.tracing.sampling=100' \ + --set 'meshConfig.enablePrometheusMerge=true' \ + --set 'meshConfig.extensionProviders[0].name=zipkin' \ + --set "meshConfig.extensionProviders[0].zipkin.service=${OTEL_ZIPKIN_URL}.svc.cluster.local" \ + --set 'meshConfig.extensionProviders[0].zipkin.port=9411' \ + --reuse-values \ + --wait --timeout=5m 2>/dev/null || log_warn "Falha ao configurar istiod (helm upgrade)" + log_success "istiod configurado" + + # Criar Telemetry API resource + log_info "Criando Telemetry resource..." + kubectl apply -f - <<'TELEMETRY_EOF' +apiVersion: telemetry.istio.io/v1 +kind: Telemetry +metadata: + name: mesh-default + namespace: istio-system +spec: + tracing: + - providers: + - name: zipkin + randomSamplingPercentage: 100 +TELEMETRY_EOF + log_success "Telemetry resource criado" + + # Atualizar Kiali para apontar tracing ao Grafana/Tempo + log_info "Configurando Kiali para usar Tempo via Grafana..." + VICTORIA_METRICS_URL="http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429" + GRAFANA_HOST_INTERNAL="${GRAFANA_HOST:-grafana.${DOMAIN}}" + kubectl patch configmap kiali -n istio-system --type merge -p "{ + \"data\": { + \"config.yaml\": \"auth:\\n strategy: anonymous\\nlogin_token:\\n signing_key: kiali-signing-key-1234\\nserver:\\n web_root: /kiali\\nexternal_services:\\n prometheus:\\n url: ${VICTORIA_METRICS_URL}\\n tracing:\\n enabled: true\\n provider: tempo\\n internal_url: http://tempo.monitoring:3200\\n use_grpc: true\\n grafana:\\n enabled: false\\n\" + } + }" 2>/dev/null || true + kubectl rollout restart deployment/kiali -n istio-system 2>/dev/null || true + log_success "Kiali configurado com Tempo" + + log_info "Reinicie os pods com sidecar para ativar tracing:" + log_info " kubectl rollout restart deployment -n " +else + log_info "Istio nao detectado — tracing apenas via OTel SDK (demo app)" +fi + # ============================================================================= # APLICAR ALERTAS # ============================================================================= diff --git a/aula-16/README.md b/aula-16/README.md new file mode 100644 index 0000000..c71f7ad --- /dev/null +++ b/aula-16/README.md @@ -0,0 +1,427 @@ +# 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) diff --git a/aula-16/cleanup.sh b/aula-16/cleanup.sh new file mode 100755 index 0000000..b28b11d --- /dev/null +++ b/aula-16/cleanup.sh @@ -0,0 +1,117 @@ +#!/bin/bash +# ============================================================================ +# Aula 16 - Cleanup do Flagger +# ============================================================================ +# Remove o Flagger e reverte as configurações de canary. +# NÃO remove o Istio (aula-14) nem o Victoria Metrics (aula-12). +# ============================================================================ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[OK]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERRO]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "" +echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ Cleanup - Aula 16 (Flagger) ║${NC}" +echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}" +echo "" +echo -e "${YELLOW}Isso vai remover:${NC}" +echo " - Flagger Loadtester (streamify-production)" +echo " - Flagger Controller (istio-system)" +echo " - Flagger CRDs" +echo " - VMPodScrapes do Istio" +echo " - PeerAuthentication" +echo " - Label istio-injection do namespace" +echo "" +echo -e "${YELLOW}NÃO vai remover:${NC}" +echo " - Istio (aula-14)" +echo " - Victoria Metrics (aula-12)" +echo " - Mudanças no Helm chart (reverter manualmente no repo streamify-deploy)" +echo "" +read -p "Confirmar remoção? (digite 'sim'): " confirm +if [[ "$confirm" != "sim" ]]; then + echo "Cancelado." + exit 0 +fi + +echo "" + +# 1. Remover Loadtester +log_info "Removendo Flagger Loadtester..." +helm uninstall flagger-loadtester -n streamify-production --wait 2>/dev/null || true +log_success "Loadtester removido" + +# 2. Remover Canary CRD resource (para Flagger devolver controle do deployment) +log_info "Removendo Canary resources..." +kubectl delete canary --all -n streamify-production --ignore-not-found=true 2>/dev/null || true +log_success "Canary resources removidos" + +# 3. Aguardar Flagger restaurar o deployment original +log_info "Aguardando restauração do deployment..." +sleep 10 + +# 4. Remover Flagger +log_info "Removendo Flagger..." +helm uninstall flagger -n istio-system --wait 2>/dev/null || true +log_success "Flagger removido" + +# 5. Remover CRDs +log_info "Removendo Flagger CRDs..." +kubectl delete crd canaries.flagger.app metrictemplates.flagger.app alertproviders.flagger.app 2>/dev/null || true +log_success "CRDs removidos" + +# 6. Remover VMPodScrapes +log_info "Removendo VMPodScrapes do Istio..." +kubectl delete vmpodscrape envoy-stats istiod -n monitoring --ignore-not-found=true 2>/dev/null || true +log_success "VMPodScrapes removidos" + +# 7. Remover PeerAuthentication +log_info "Removendo PeerAuthentication..." +kubectl delete peerauthentication default -n streamify-production --ignore-not-found=true 2>/dev/null || true +log_success "PeerAuthentication removida" + +# 8. Remover label de sidecar injection +log_info "Removendo label istio-injection..." +kubectl label namespace streamify-production istio-injection- 2>/dev/null || true +log_success "Label removida" + +# 9. Reiniciar pods para remover sidecars +log_info "Reiniciando deployments para remover sidecars..." +kubectl rollout restart deployment -n streamify-production 2>/dev/null || true +kubectl rollout status deployment/streamify-production-web -n streamify-production --timeout=300s 2>/dev/null || true +log_success "Sidecars removidos" + +# 10. Limpar VirtualService e DestinationRule órfãos +log_info "Removendo VirtualServices e DestinationRules órfãos..." +kubectl delete virtualservice --all -n streamify-production --ignore-not-found=true 2>/dev/null || true +kubectl delete destinationrule --all -n streamify-production --ignore-not-found=true 2>/dev/null || true +log_success "Recursos Istio limpos" + +# 11. Remover .env +read -p "Remover arquivo .env? [s/N]: " remove_env +if [[ "$remove_env" == "s" || "$remove_env" == "S" ]]; then + rm -f "${SCRIPT_DIR}/.env" + log_success ".env removido" +fi + +echo "" +echo -e "${GREEN}Cleanup concluído!${NC}" +echo "" +echo -e "${YELLOW}Lembrete:${NC} Para reverter as mudanças no Helm chart:" +echo " 1. Remova canary.enabled do values-production.yaml" +echo " 2. Remova o template canary.yaml" +echo " 3. Reverta a linha do ingress.yaml (remova o sufixo -primary)" +echo " 4. Commit e push no repo streamify-deploy" +echo "" diff --git a/aula-16/flagger-values.yaml b/aula-16/flagger-values.yaml new file mode 100644 index 0000000..6f7d60e --- /dev/null +++ b/aula-16/flagger-values.yaml @@ -0,0 +1,5 @@ +# Flagger - Progressive Delivery Operator +# Integrado com Istio + Victoria Metrics + +meshProvider: istio +metricsServer: http://vmsingle-monitoring-victoria-metrics-k8s-stack.monitoring:8429 diff --git a/aula-16/setup.sh b/aula-16/setup.sh new file mode 100755 index 0000000..e9620e0 --- /dev/null +++ b/aula-16/setup.sh @@ -0,0 +1,739 @@ +#!/bin/bash +# ============================================================================ +# Aula 16 - Canary Automatizado com Flagger +# ============================================================================ +# Instala o Flagger para canary deployment automatizado do Streamify +# usando Istio para traffic splitting e Victoria Metrics para métricas. +# +# Componentes: +# - Flagger (operador de progressive delivery) +# - Flagger Loadtester (gerador de tráfego sintético) +# - VMPodScrapes (coleta de métricas Istio) +# - Canary CRD (configuração no Helm chart do Streamify) +# +# Pré-requisitos: +# - Cluster Kubernetes (aula-08) +# - Istio instalado (aula-14) +# - Victoria Metrics (aula-12) +# - Tempo + OTel Collector (aula-15) +# - ArgoCD (aula-11) +# - Streamify em produção (streamify-production) +# ============================================================================ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[OK]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERRO]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="${SCRIPT_DIR}/.env" + +# ============================================================================ +# Gerenciamento de Configuração +# ============================================================================ + +load_config() { + if [[ -f "$ENV_FILE" ]]; then + source "$ENV_FILE" + return 0 + fi + return 1 +} + +save_config() { + cat > "$ENV_FILE" << EOF +# Configuração da Aula 16 - Flagger Canary +# Gerado em: $(date) + +GITEA_HOST=${GITEA_HOST} +DEPLOY_REPO=${DEPLOY_REPO} +EOF + log_success "Configuração salva em .env" +} + +# ============================================================================ +# Verificação de Pré-requisitos +# ============================================================================ + +check_prerequisites() { + echo "" + log_info "Verificando pré-requisitos..." + + local failed=false + + for cmd in kubectl helm git; do + if command -v "$cmd" &> /dev/null; then + log_success "$cmd encontrado" + else + log_error "$cmd não encontrado" + failed=true + fi + done + + if ! kubectl cluster-info &> /dev/null; then + log_error "Cluster Kubernetes não acessível" + exit 1 + fi + log_success "Cluster Kubernetes acessível" + + # Verificar Istio + if kubectl get deployment istiod -n istio-system &> /dev/null; then + log_success "Istio encontrado" + else + log_error "Istio não instalado. Execute a aula-14 primeiro." + exit 1 + fi + + # Verificar Victoria Metrics + if kubectl get svc -n monitoring vmsingle-monitoring-victoria-metrics-k8s-stack &> /dev/null; then + log_success "Victoria Metrics encontrado" + else + log_error "Victoria Metrics não encontrado. Execute a aula-12 primeiro." + exit 1 + fi + + # Verificar ArgoCD + if kubectl get deployment argocd-server -n argocd &> /dev/null; then + log_success "ArgoCD encontrado" + else + log_error "ArgoCD não encontrado. Execute a aula-11 primeiro." + exit 1 + fi + + # Verificar Streamify em produção + if kubectl get deployment streamify-production-web -n streamify-production &> /dev/null; then + log_success "Streamify production encontrado" + else + log_error "Streamify não está deployado em streamify-production." + exit 1 + fi + + if [[ "$failed" == "true" ]]; then + exit 1 + fi +} + +# ============================================================================ +# Coleta de Configuração +# ============================================================================ + +collect_config() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configuração do Flagger${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo "" + + if load_config; then + echo -e "Configuração existente encontrada:" + echo -e " Gitea: ${GREEN}${GITEA_HOST}${NC}" + echo -e " Repo: ${GREEN}${DEPLOY_REPO}${NC}" + echo "" + echo -e "[1] Usar configuração existente" + echo -e "[2] Inserir nova configuração" + read -p "Escolha [1/2]: " choice + if [[ "$choice" == "1" ]]; then + return 0 + fi + fi + + echo "" + if [[ -z "$GITEA_HOST" ]]; then + GITEA_HOST="gitea.kube.quest" + fi + echo -e "Host do Gitea: ${GREEN}${GITEA_HOST}${NC}" + read -p "Enter para confirmar ou digite novo valor: " new_host + [[ -n "$new_host" ]] && GITEA_HOST="$new_host" + + if [[ -z "$DEPLOY_REPO" ]]; then + DEPLOY_REPO="depaula/streamify-deploy" + fi + echo -e "Repositório de deploy: ${GREEN}${DEPLOY_REPO}${NC}" + read -p "Enter para confirmar ou digite novo valor: " new_repo + [[ -n "$new_repo" ]] && DEPLOY_REPO="$new_repo" + + save_config +} + +# ============================================================================ +# Instalação do Flagger +# ============================================================================ + +install_flagger() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalando Flagger${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + helm repo add flagger https://flagger.app 2>/dev/null || true + helm repo update flagger + + log_info "Instalando Flagger CRDs..." + kubectl apply -f https://raw.githubusercontent.com/fluxcd/flagger/main/artifacts/flagger/crd.yaml 2>/dev/null + log_success "CRDs instalados" + + log_info "Instalando Flagger no namespace istio-system..." + if helm status flagger -n istio-system &> /dev/null; then + helm upgrade flagger flagger/flagger \ + -n istio-system \ + -f "${SCRIPT_DIR}/flagger-values.yaml" \ + --wait + else + helm install flagger flagger/flagger \ + -n istio-system \ + -f "${SCRIPT_DIR}/flagger-values.yaml" \ + --wait + fi + log_success "Flagger instalado" + + log_info "Aguardando Flagger..." + kubectl wait --for=condition=available deployment/flagger -n istio-system --timeout=120s + log_success "Flagger pronto" +} + +# ============================================================================ +# Instalação do Loadtester +# ============================================================================ + +install_loadtester() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalando Loadtester${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "O loadtester gera tráfego sintético para que o Flagger" + log_info "tenha métricas suficientes para analisar o canary." + echo "" + + if helm status flagger-loadtester -n streamify-production &> /dev/null; then + helm upgrade flagger-loadtester flagger/loadtester \ + -n streamify-production \ + --wait + else + helm install flagger-loadtester flagger/loadtester \ + -n streamify-production \ + --wait + fi + log_success "Loadtester instalado em streamify-production" +} + +# ============================================================================ +# Configuração de Métricas Istio no Victoria Metrics +# ============================================================================ + +setup_istio_metrics() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configurando coleta de métricas Istio${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Criando VMPodScrape para sidecars Envoy..." + cat <<'EOF' | kubectl apply -f - +apiVersion: operator.victoriametrics.com/v1beta1 +kind: VMPodScrape +metadata: + name: envoy-stats + namespace: monitoring + labels: + app.kubernetes.io/name: istio + app.kubernetes.io/component: envoy +spec: + podMetricsEndpoints: + - port: http-envoy-prom + path: /stats/prometheus + relabelConfigs: + - action: keep + sourceLabels: [__meta_kubernetes_pod_container_name] + regex: "istio-proxy" + namespaceSelector: + any: true +EOF + log_success "VMPodScrape envoy-stats criado" + + log_info "Criando VMPodScrape para istiod..." + cat <<'EOF' | kubectl apply -f - +apiVersion: operator.victoriametrics.com/v1beta1 +kind: VMPodScrape +metadata: + name: istiod + namespace: monitoring + labels: + app.kubernetes.io/name: istio + app.kubernetes.io/component: istiod +spec: + podMetricsEndpoints: + - port: http-monitoring + path: /metrics + selector: + matchLabels: + app: istiod + namespaceSelector: + matchNames: + - istio-system +EOF + log_success "VMPodScrape istiod criado" + + log_info "As métricas do Istio levarão ~60s para aparecer no Victoria Metrics." + + # Aumentar memória do Victoria Metrics (512Mi é insuficiente com métricas Istio) + local VM_MEM + VM_MEM=$(kubectl get vmsingle monitoring-victoria-metrics-k8s-stack -n monitoring \ + -o jsonpath='{.spec.resources.limits.memory}' 2>/dev/null || echo "512Mi") + if [[ "$VM_MEM" == "512Mi" ]]; then + log_warn "Victoria Metrics com apenas 512Mi — insuficiente para métricas Istio." + log_info "Aumentando memória para 1536Mi..." + kubectl patch vmsingle monitoring-victoria-metrics-k8s-stack -n monitoring \ + --type merge -p '{"spec":{"resources":{"limits":{"memory":"1536Mi"},"requests":{"memory":"512Mi"}}}}' 2>/dev/null || true + log_success "Victoria Metrics memory atualizado para 1536Mi" + else + log_success "Victoria Metrics memory OK ($VM_MEM)" + fi +} + +# ============================================================================ +# Habilitar Istio Sidecar Injection +# ============================================================================ + +enable_sidecar_injection() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Habilitando Istio sidecar injection${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Adicionando labels no namespace streamify-production..." + kubectl label namespace streamify-production \ + istio-injection=enabled \ + pod-security.kubernetes.io/enforce=privileged \ + --overwrite + + log_success "Labels adicionadas" + + # Verificar se os pods já têm sidecar + local PROXY_COUNT + PROXY_COUNT=$(kubectl get pods -n streamify-production -o jsonpath='{range .items[*]}{.spec.containers[*].name}{"\n"}{end}' | grep -c "istio-proxy" || echo "0") + + if [[ "$PROXY_COUNT" -eq 0 ]]; then + log_info "Reiniciando deployments para injetar sidecars..." + kubectl rollout restart deployment -n streamify-production + log_info "Aguardando pods ficarem prontos..." + kubectl rollout status deployment/streamify-production-web -n streamify-production --timeout=300s + kubectl rollout status deployment/streamify-production-queue -n streamify-production --timeout=120s + kubectl rollout status deployment/streamify-production-schedule -n streamify-production --timeout=120s + log_success "Sidecars injetados em todos os pods" + else + log_success "Sidecars já estão injetados" + fi + + # Configurar PeerAuthentication para permitir tráfego do NGINX Ingress + log_info "Configurando PeerAuthentication (PERMISSIVE) para NGINX Ingress..." + cat <<'EOF' | kubectl apply -f - +apiVersion: security.istio.io/v1 +kind: PeerAuthentication +metadata: + name: default + namespace: streamify-production +spec: + mtls: + mode: PERMISSIVE +EOF + log_success "PeerAuthentication configurada" +} + +# ============================================================================ +# Modificar Helm Chart do Streamify +# ============================================================================ + +update_helm_chart() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Atualizando Helm chart do Streamify${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + local DEPLOY_DIR + DEPLOY_DIR=$(mktemp -d) + + log_info "Clonando repositório de deploy..." + git clone "https://${GITEA_HOST}/${DEPLOY_REPO}.git" "$DEPLOY_DIR" 2>&1 | tail -1 + log_success "Repositório clonado" + + # Verificar se canary já está configurado + if grep -q "canary:" "$DEPLOY_DIR/values.yaml" 2>/dev/null; then + log_warn "Configuração canary já existe no values.yaml" + log_info "Verificando se precisa atualizar..." + fi + + # 1. Adicionar bloco canary no values.yaml (defaults) + if ! grep -q "canary:" "$DEPLOY_DIR/values.yaml"; then + log_info "Adicionando configuração canary ao values.yaml..." + cat >> "$DEPLOY_DIR/values.yaml" << 'EOF' + +canary: + enabled: false + analysis: + interval: "1m" + threshold: 5 + maxWeight: 50 + stepWeight: 10 + metrics: + requestSuccessRate: 99 + requestDuration: 500 +EOF + log_success "values.yaml atualizado" + fi + + # 2. Adicionar canary.enabled: true no values-production.yaml + if ! grep -q "canary:" "$DEPLOY_DIR/values-production.yaml"; then + log_info "Habilitando canary no values-production.yaml..." + cat >> "$DEPLOY_DIR/values-production.yaml" << 'EOF' + +canary: + enabled: true +EOF + log_success "values-production.yaml atualizado" + fi + + # 3. Criar template canary.yaml + log_info "Criando template canary.yaml..." + cat > "$DEPLOY_DIR/templates/canary.yaml" << 'TMPL' +{{- if .Values.canary.enabled }} +apiVersion: flagger.app/v1beta1 +kind: Canary +metadata: + name: {{ include "streamify.fullname" . }}-web + labels: + {{- include "streamify.web.labels" . | nindent 4 }} +spec: + targetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "streamify.fullname" . }}-web + service: + port: {{ .Values.web.service.port }} + targetPort: http + gateways: + - mesh + hosts: + - {{ include "streamify.fullname" . }}-web + analysis: + interval: {{ .Values.canary.analysis.interval }} + threshold: {{ .Values.canary.analysis.threshold }} + maxWeight: {{ .Values.canary.analysis.maxWeight }} + stepWeight: {{ .Values.canary.analysis.stepWeight }} + metrics: + - name: request-success-rate + thresholdRange: + min: {{ .Values.canary.analysis.metrics.requestSuccessRate }} + interval: 30s + - name: request-duration + thresholdRange: + max: {{ .Values.canary.analysis.metrics.requestDuration }} + interval: 30s + webhooks: + - name: load-test + url: http://flagger-loadtester.streamify-production/ + type: rollout + metadata: + cmd: "hey -z 1m -q 5 -c 2 http://{{ include "streamify.fullname" . }}-web-canary.streamify-production/up" +{{- end }} +TMPL + log_success "canary.yaml criado" + + # 4. Modificar ingress.yaml para apontar para -primary + log_info "Ajustando ingress.yaml para service -primary..." + if ! grep -q "canary.enabled" "$DEPLOY_DIR/templates/ingress.yaml"; then + sed -i.bak 's/name: {{ include "streamify.fullname" \$ }}-web$/name: {{ include "streamify.fullname" $ }}-web{{- if $.Values.canary.enabled }}-primary{{- end }}/' \ + "$DEPLOY_DIR/templates/ingress.yaml" + rm -f "$DEPLOY_DIR/templates/ingress.yaml.bak" + log_success "ingress.yaml atualizado" + else + log_warn "ingress.yaml já contém configuração canary" + fi + + # 5. Adicionar annotation para sidecars aguardarem antes dos init containers + log_info "Ajustando web.yaml para holdApplicationUntilProxyStarts..." + if ! grep -q "holdApplicationUntilProxyStarts" "$DEPLOY_DIR/templates/deployments/web.yaml"; then + sed -i.bak '/checksum\/secret/a\ + {{- if .Values.canary.enabled }}\ + proxy.istio.io/config: '"'"'{"holdApplicationUntilProxyStarts": true}'"'"'\ + {{- end }}' \ + "$DEPLOY_DIR/templates/deployments/web.yaml" + rm -f "$DEPLOY_DIR/templates/deployments/web.yaml.bak" + log_success "web.yaml atualizado com holdApplicationUntilProxyStarts" + else + log_warn "web.yaml já contém holdApplicationUntilProxyStarts" + fi + + # 6. Modificar hpa.yaml para desabilitar quando canary ativo + log_info "Ajustando hpa.yaml..." + if ! grep -q "canary.enabled" "$DEPLOY_DIR/templates/hpa.yaml"; then + sed -i.bak 's/{{- if .Values.web.autoscaling.enabled }}/{{- if and .Values.web.autoscaling.enabled (not .Values.canary.enabled) }}/' \ + "$DEPLOY_DIR/templates/hpa.yaml" + rm -f "$DEPLOY_DIR/templates/hpa.yaml.bak" + log_success "hpa.yaml atualizado" + else + log_warn "hpa.yaml já contém configuração canary" + fi + + # 6. Commit e push + log_info "Commitando mudanças..." + cd "$DEPLOY_DIR" + git add -A + if git diff --cached --quiet; then + log_warn "Nenhuma mudança para commitar" + else + git commit -m "feat: adicionar Flagger canary deployment para web + +- Novo template canary.yaml (Canary CRD) +- Ingress aponta para service -primary quando canary ativo +- HPA desabilitado quando canary ativo +- Canary habilitado apenas em production" + log_info "Fazendo push para o Gitea..." + git push origin main + log_success "Mudanças enviadas para o repositório de deploy" + fi + cd "$SCRIPT_DIR" + rm -rf "$DEPLOY_DIR" +} + +# ============================================================================ +# Configurar ArgoCD +# ============================================================================ + +configure_argocd() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configurando ArgoCD${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Adicionando ignoreDifferences para VirtualService no ArgoCD..." + + # Patch do Application para ignorar mudanças no VirtualService feitas pelo Flagger + kubectl patch app streamify-production -n argocd --type=json -p='[ + { + "op": "add", + "path": "/spec/ignoreDifferences", + "value": [ + { + "group": "networking.istio.io", + "kind": "VirtualService", + "jsonPointers": ["/spec/http/0/route"] + } + ] + } + ]' 2>/dev/null || log_warn "Não foi possível configurar ignoreDifferences automaticamente" + + log_success "ArgoCD configurado" + + log_info "Sincronizando ArgoCD..." + kubectl patch app streamify-production -n argocd --type=merge -p='{"operation":{"sync":{"prune":false}}}' 2>/dev/null || true + + # Aguardar sync + log_info "Aguardando ArgoCD sincronizar (pode levar até 3 minutos)..." + for i in $(seq 1 36); do + local STATUS + STATUS=$(kubectl get app streamify-production -n argocd -o jsonpath='{.status.sync.status}' 2>/dev/null || echo "Unknown") + if [[ "$STATUS" == "Synced" ]]; then + log_success "ArgoCD sincronizado" + return 0 + fi + sleep 5 + done + log_warn "ArgoCD ainda não sincronizou. Verifique manualmente: kubectl get app streamify-production -n argocd" +} + +# ============================================================================ +# Aguardar Inicialização do Flagger +# ============================================================================ + +wait_for_canary() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Aguardando Flagger inicializar o Canary${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "O Flagger vai criar os services -primary e -canary..." + log_info "Isso pode levar até 2 minutos." + + for i in $(seq 1 24); do + local STATUS + STATUS=$(kubectl get canary streamify-production-web -n streamify-production -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [[ "$STATUS" == "Initialized" || "$STATUS" == "Succeeded" ]]; then + log_success "Canary inicializado com sucesso!" + echo "" + kubectl get canary -n streamify-production + return 0 + elif [[ -n "$STATUS" ]]; then + log_info "Status: $STATUS (aguardando Initialized...)" + else + log_info "Canary ainda não foi criado pelo ArgoCD..." + fi + sleep 5 + done + + log_warn "Canary ainda não inicializou. Verifique:" + echo " kubectl get canary -n streamify-production" + echo " kubectl logs deployment/flagger -n istio-system --tail=20" +} + +# ============================================================================ +# Dashboard Grafana +# ============================================================================ + +create_dashboard() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Criando Dashboard no Grafana${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + local GRAFANA_SVC="monitoring-grafana.monitoring" + local GRAFANA_PASS + GRAFANA_PASS=$(kubectl get secret monitoring-grafana -n monitoring -o jsonpath='{.data.admin-password}' 2>/dev/null | base64 -d) + + if [[ -z "$GRAFANA_PASS" ]]; then + log_warn "Não foi possível obter a senha do Grafana. Dashboard não criado." + return 0 + fi + + log_info "Criando dashboard 'Streamify - Observabilidade Istio'..." + + local RESULT + RESULT=$(kubectl run grafana-dash --rm -i --restart=Never --image=curlimages/curl:latest --command -- \ + curl -s -X POST \ + -H "Content-Type: application/json" \ + -u "admin:${GRAFANA_PASS}" \ + -d '{ + "dashboard": { + "title": "Streamify - Observabilidade Istio", + "tags": ["istio", "streamify", "canary"], + "timezone": "browser", + "refresh": "30s", + "time": {"from": "now-1h", "to": "now"}, + "panels": [ + { + "title": "Request Rate por Serviço", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}, + "datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"}, + "fieldConfig": {"defaults": {"unit": "reqps", "custom": {"lineWidth": 2, "fillOpacity": 10}}}, + "targets": [{"expr": "sum by(destination_service_name) (rate(istio_requests_total{destination_workload_namespace=\"streamify-production\"}[5m]))", "legendFormat": "{{destination_service_name}}"}] + }, + { + "title": "Error Rate (5xx)", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}, + "datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"}, + "fieldConfig": {"defaults": {"unit": "reqps", "custom": {"lineWidth": 2, "fillOpacity": 10}, "color": {"mode": "fixed", "fixedColor": "red"}}}, + "targets": [{"expr": "sum by(destination_service_name, response_code) (rate(istio_requests_total{destination_workload_namespace=\"streamify-production\", response_code=~\"5.*\"}[5m]))", "legendFormat": "{{destination_service_name}} [{{response_code}}]"}] + }, + { + "title": "Latência p99 por Serviço", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}, + "datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"}, + "fieldConfig": {"defaults": {"unit": "ms", "custom": {"lineWidth": 2, "fillOpacity": 10}}}, + "targets": [{"expr": "histogram_quantile(0.99, sum by(le, destination_service_name) (rate(istio_request_duration_milliseconds_bucket{destination_workload_namespace=\"streamify-production\"}[5m])))", "legendFormat": "p99 {{destination_service_name}}"}, {"expr": "histogram_quantile(0.50, sum by(le, destination_service_name) (rate(istio_request_duration_milliseconds_bucket{destination_workload_namespace=\"streamify-production\"}[5m])))", "legendFormat": "p50 {{destination_service_name}}"}] + }, + { + "title": "Saturação de Memória (% do limit)", + "type": "timeseries", + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}, + "datasource": {"type": "prometheus", "uid": "P4169E866C3094E38"}, + "fieldConfig": {"defaults": {"unit": "percentunit", "custom": {"lineWidth": 2, "fillOpacity": 10}, "thresholds": {"mode": "absolute", "steps": [{"color": "green", "value": null}, {"color": "yellow", "value": 0.7}, {"color": "red", "value": 0.85}]}}}, + "targets": [{"expr": "max by(container, pod) (container_memory_working_set_bytes{namespace=\"streamify-production\", container!=\"\", container!=\"istio-proxy\", container!=\"istio-init\"} / container_spec_memory_limit_bytes{namespace=\"streamify-production\", container!=\"\", container!=\"istio-proxy\", container!=\"istio-init\"} > 0)", "legendFormat": "{{pod}} / {{container}}"}] + } + ] + }, + "overwrite": true + }' \ + "http://${GRAFANA_SVC}/api/dashboards/db" 2>/dev/null) + + if echo "$RESULT" | grep -q '"status":"success"'; then + log_success "Dashboard criado no Grafana" + else + log_warn "Não foi possível criar o dashboard automaticamente" + log_info "Crie manualmente: Grafana > Dashboards > Import" + fi +} + +# ============================================================================ +# Resumo +# ============================================================================ + +show_summary() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalação Concluída${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo "" + echo -e "${GREEN}Componentes instalados:${NC}" + echo " - Flagger (operador canary, namespace: istio-system)" + echo " - Flagger Loadtester (tráfego sintético, namespace: streamify-production)" + echo " - VMPodScrapes (coleta métricas Istio)" + echo " - Canary CRD (no Helm chart do Streamify)" + echo "" + echo -e "${GREEN}Configuração do Canary:${NC}" + echo " - Interval: 1m (análise a cada 1 minuto)" + echo " - Step Weight: 10% (incremento de tráfego por step)" + echo " - Max Weight: 50% (promove ao atingir 50%)" + echo " - Threshold: 5 (rollback após 5 falhas)" + echo " - Métricas: success rate > 99%, latência < 500ms" + echo "" + echo -e "${GREEN}Como funciona a partir de agora:${NC}" + echo "" + echo " 1. CI builda nova imagem e atualiza a tag no streamify-deploy" + echo " 2. ArgoCD sincroniza, Flagger detecta a mudança" + echo " 3. Flagger cria canary: 10% → 20% → 30% → 40% → 50% → promote" + echo " 4. Se métricas falharem: rollback automático" + echo "" + echo -e "${GREEN}Observar:${NC}" + echo "" + echo " # Acompanhar canary em tempo real" + echo " kubectl get canary -n streamify-production -w" + echo "" + echo " # Logs do Flagger" + echo " kubectl logs deployment/flagger -n istio-system -f" + echo "" + echo " # Kiali (tráfego visual)" + echo " https://kiali.kube.quest/kiali/" + echo "" + echo " # Dashboard Istio (métricas do canary)" + echo " Grafana > Dashboards > Streamify - Observabilidade Istio" + echo "" + echo " # Traces (distributed tracing)" + echo " Grafana > Explore > Tempo" + echo "" +} + +# ============================================================================ +# Execução +# ============================================================================ + +main() { + echo "" + echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ Aula 16 - Canary Automatizado com Flagger ║${NC}" + echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}" + + check_prerequisites + collect_config + install_flagger + install_loadtester + setup_istio_metrics + enable_sidecar_injection + update_helm_chart + configure_argocd + wait_for_canary + create_dashboard + show_summary +} + +main "$@"