#!/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 "$@"