From 3860809e5c39a48ea9045effdfda583fa8122473 Mon Sep 17 00:00:00 2001 From: ArgoCD Setup Date: Sat, 24 Jan 2026 07:40:51 -0300 Subject: [PATCH] feat(aula-14): adicionar Istio Traffic Splitting com canary deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Instala Istio (base + istiod + ingressgateway) - Configura Kiali e Jaeger para observabilidade - Deploy de app-backend v1 e v2 com traffic splitting 90/10 - Integra com Victoria Metrics da aula-12 - Inclui teste-stress.sh para validar distribuição de tráfego - Tráfego externo passa pelo Istio Gateway via NGINX Ingress --- CLAUDE.md | 2 + aula-14/README.md | 230 ++++++++++++ aula-14/app-backend/v1/Dockerfile | 6 + aula-14/app-backend/v1/app.js | 32 ++ aula-14/app-backend/v2/Dockerfile | 6 + aula-14/app-backend/v2/app.js | 21 ++ aula-14/cleanup.sh | 116 ++++++ aula-14/istio/gateway.yaml | 36 ++ aula-14/istio/jaeger-values.yaml | 29 ++ aula-14/istio/kiali-values.yaml | 24 ++ aula-14/k8s/deployment-v1.yaml | 53 +++ aula-14/k8s/deployment-v2.yaml | 53 +++ aula-14/k8s/destination-rule.yaml | 14 + aula-14/k8s/gateway.yaml | 15 + aula-14/k8s/ingress-app.yaml | 23 ++ aula-14/k8s/ingress-jaeger.yaml | 25 ++ aula-14/k8s/ingress-kiali.yaml | 25 ++ aula-14/k8s/namespace.yaml | 9 + aula-14/k8s/service.yaml | 14 + aula-14/k8s/virtual-service.yaml | 22 ++ aula-14/setup.sh | 592 ++++++++++++++++++++++++++++++ aula-14/teste-stress.sh | 198 ++++++++++ 22 files changed, 1545 insertions(+) create mode 100644 aula-14/README.md create mode 100644 aula-14/app-backend/v1/Dockerfile create mode 100644 aula-14/app-backend/v1/app.js create mode 100644 aula-14/app-backend/v2/Dockerfile create mode 100644 aula-14/app-backend/v2/app.js create mode 100755 aula-14/cleanup.sh create mode 100644 aula-14/istio/gateway.yaml create mode 100644 aula-14/istio/jaeger-values.yaml create mode 100644 aula-14/istio/kiali-values.yaml create mode 100644 aula-14/k8s/deployment-v1.yaml create mode 100644 aula-14/k8s/deployment-v2.yaml create mode 100644 aula-14/k8s/destination-rule.yaml create mode 100644 aula-14/k8s/gateway.yaml create mode 100644 aula-14/k8s/ingress-app.yaml create mode 100644 aula-14/k8s/ingress-jaeger.yaml create mode 100644 aula-14/k8s/ingress-kiali.yaml create mode 100644 aula-14/k8s/namespace.yaml create mode 100644 aula-14/k8s/service.yaml create mode 100644 aula-14/k8s/virtual-service.yaml create mode 100755 aula-14/setup.sh create mode 100755 aula-14/teste-stress.sh diff --git a/CLAUDE.md b/CLAUDE.md index 5929cc3..0ca035b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,7 @@ App de demonstração: `node-bugado` - trava após N requests para demonstrar he | 11 | ArgoCD + GitLab Runner | Hetzner | | 12 | Victoria Metrics (Observabilidade) | Hetzner | | 13 | Container Factory (eStargz) | Hetzner | +| 14 | Istio Traffic Splitting | Hetzner | ## Comandos Rápidos @@ -38,6 +39,7 @@ cd aula-10 && ./setup.sh # GitLab cd aula-11 && ./setup.sh # ArgoCD cd aula-12 && ./setup.sh # Victoria Metrics cd aula-13 && ./setup.sh # Container Factory +cd aula-14 && ./setup.sh # Istio Traffic Splitting ``` ## App node-bugado diff --git a/aula-14/README.md b/aula-14/README.md new file mode 100644 index 0000000..c2de27c --- /dev/null +++ b/aula-14/README.md @@ -0,0 +1,230 @@ +# Aula 14 - Istio Traffic Splitting + +Demonstração de **app-backend deployment** usando Istio para dividir tráfego entre duas versões da aplicação. + +## Motivação + +Em produção, lançar uma nova versão diretamente para 100% dos usuários é arriscado. Com traffic splitting podemos: + +- Enviar apenas 10% do tráfego para a nova versão inicialmente +- Monitorar erros e latência antes de aumentar o percentual +- Fazer rollback instantâneo sem redeploy +- Visualizar o fluxo de tráfego em tempo real com Kiali + +## Arquitetura + +``` + ┌─────────────────────────────────────────────┐ + │ Istio Service Mesh │ + │ │ + Requisições │ ┌─────────────────┐ │ + ──────────────► │ │ VirtualService │ │ + │ │ (90/10 split) │ │ + │ └────────┬────────┘ │ + │ │ │ + │ ┌─────┴─────┐ │ + │ ▼ ▼ │ + │ ┌──────┐ ┌──────┐ │ + │ │ v1 │ │ v2 │ │ + │ │ bug │ │ ok │ │ + │ └──────┘ └──────┘ │ + │ │ + │ ┌─────────┐ ┌────────┐ ┌─────────────────┐│ + │ │ Kiali │ │ Jaeger │ │ Victoria Metrics││ + │ └─────────┘ └────────┘ └─────────────────┘│ + └─────────────────────────────────────────────┘ +``` + +## Conceitos + +| Conceito | Descrição | +|----------|-----------| +| Service Mesh | Camada de infraestrutura que gerencia comunicação entre serviços | +| Sidecar Injection | Proxy Envoy injetado automaticamente em cada pod | +| VirtualService | Define regras de roteamento de tráfego | +| DestinationRule | Define subsets (versões) do serviço | +| Traffic Splitting | Divisão percentual do tráfego entre versões | + +## Pré-requisitos + +- Cluster Kubernetes na Hetzner (aula-08) +- GitLab com Registry (aula-10) +- Victoria Metrics (aula-12) para métricas +- kubectl, helm e docker instalados + +## Estrutura + +``` +aula-14/ +├── README.md +├── setup.sh +├── cleanup.sh +├── app-backend/ +│ ├── v1/ +│ │ ├── app.js # Versão com bug (trava após N requests) +│ │ └── Dockerfile +│ └── v2/ +│ ├── app.js # Versão corrigida (estável) +│ └── Dockerfile +├── k8s/ +│ ├── namespace.yaml # Namespace com istio-injection +│ ├── deployment-v1.yaml +│ ├── deployment-v2.yaml +│ ├── service.yaml +│ ├── destination-rule.yaml # Define subsets v1, v2 +│ └── virtual-service.yaml # Traffic splitting (90/10) +└── istio/ + ├── kiali-values.yaml + ├── jaeger-values.yaml + └── gateway.yaml +``` + +## Instalação + +```bash +cd aula-14 +./setup.sh +``` + +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 +6. Build e push das imagens v1 e v2 +7. Deploy da aplicação com traffic splitting 90/10 + +## Verificação + +### Verificar pods + +```bash +kubectl get pods -n istio +kubectl get pods -n istio-system +``` + +### Verificar sidecar injection + +Cada pod deve ter 2 containers (app + istio-proxy): + +```bash +kubectl get pods -n istio -o jsonpath='{range .items[*]}{.metadata.name}{": "}{range .spec.containers[*]}{.name}{" "}{end}{"\n"}{end}' +``` + +### Testar distribuição de tráfego + +```bash +kubectl exec -n istio curl-test -- sh -c \ + 'for i in $(seq 1 100); do curl -s http://app-backend/; done' | sort | uniq -c +``` + +Resultado esperado (aproximado): +``` + 90 v1 - Request X + 10 v2 - Request Y +``` + +## Exercício Prático + +### 1. Observar distribuição inicial (90/10) + +```bash +kubectl exec -n istio curl-test -- sh -c \ + 'for i in $(seq 1 100); do curl -s http://app-backend/; done' | sort | uniq -c +``` + +### 2. Acessar Kiali e visualizar tráfego + +Abra o Kiali no navegador e observe o gráfico de tráfego em tempo real. +Gere tráfego contínuo em outro terminal: + +```bash +kubectl exec -n istio curl-test -- sh -c \ + 'while true; do curl -s http://app-backend/ > /dev/null; sleep 0.1; done' +``` + +### 3. Alterar para 50/50 + +```bash +kubectl patch virtualservice app-backend -n istio --type='json' \ + -p='[{"op":"replace","path":"/spec/http/0/route/0/weight","value":50}, + {"op":"replace","path":"/spec/http/0/route/1/weight","value":50}]' +``` + +### 4. Verificar traces no Jaeger + +Abra o Jaeger e observe os traces das requisições passando pelo mesh. + +### 5. Rollout completo para v2 + +```bash +kubectl patch virtualservice app-backend -n istio --type='json' \ + -p='[{"op":"replace","path":"/spec/http/0/route/0/weight","value":0}, + {"op":"replace","path":"/spec/http/0/route/1/weight","value":100}]' +``` + +### 6. Confirmar que app não trava mais + +Com 100% do tráfego na v2, a aplicação permanece estável independente da quantidade de requests. + +## Comandos Úteis + +```bash +# Ver distribuição atual +kubectl get virtualservice app-backend -n istio -o yaml + +# Ver pods com labels de versão +kubectl get pods -n istio --show-labels + +# Logs da v1 +kubectl logs -n istio -l app=app-backend,version=v1 -f + +# Logs da v2 +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 + +```bash +./cleanup.sh +``` + +Remove Istio, addons e namespace da aplicação. + +## Troubleshooting + +### Pod não tem sidecar + +Verificar se o namespace tem o label correto: + +```bash +kubectl get ns istio --show-labels +``` + +Deve ter `istio-injection=enabled`. Se não tiver: + +```bash +kubectl label ns istio istio-injection=enabled +kubectl rollout restart deployment -n istio +``` + +### Kiali não mostra tráfego + +1. Verificar se Victoria Metrics está coletando métricas (aula-12) +2. Gerar tráfego suficiente (pelo menos 10 requests) +3. Aguardar 30 segundos para métricas aparecerem + +### VirtualService não aplicando weights + +Verificar se DestinationRule existe: + +```bash +kubectl get destinationrule app-backend -n istio -o yaml +``` diff --git a/aula-14/app-backend/v1/Dockerfile b/aula-14/app-backend/v1/Dockerfile new file mode 100644 index 0000000..10b694b --- /dev/null +++ b/aula-14/app-backend/v1/Dockerfile @@ -0,0 +1,6 @@ +FROM node:24-alpine +WORKDIR /app +COPY app.js . +EXPOSE 3000 +USER node +CMD ["node", "app.js"] diff --git a/aula-14/app-backend/v1/app.js b/aula-14/app-backend/v1/app.js new file mode 100644 index 0000000..48687b2 --- /dev/null +++ b/aula-14/app-backend/v1/app.js @@ -0,0 +1,32 @@ +const http = require("http"); + +const MAX_REQUESTS = parseInt(process.env.MAX_REQUESTS) || 5; +let requestCount = 0; +let frozen = false; + +const server = http.createServer((req, res) => { + if (frozen) { + return; + } + + requestCount++; + console.log(`[v1] request ${requestCount}/${MAX_REQUESTS}`); + + if (requestCount >= MAX_REQUESTS) { + frozen = true; + console.log("[v1] Aplicação travou"); + } + + if (req.url === "/health") { + res.writeHead(200); + res.end("ok"); + return; + } + + res.writeHead(200); + res.end(`v1 - Request ${requestCount}\n`); +}); + +server.listen(3000, () => { + console.log(`[v1] Porta 3000 (trava após ${MAX_REQUESTS} requests)`); +}); diff --git a/aula-14/app-backend/v2/Dockerfile b/aula-14/app-backend/v2/Dockerfile new file mode 100644 index 0000000..10b694b --- /dev/null +++ b/aula-14/app-backend/v2/Dockerfile @@ -0,0 +1,6 @@ +FROM node:24-alpine +WORKDIR /app +COPY app.js . +EXPOSE 3000 +USER node +CMD ["node", "app.js"] diff --git a/aula-14/app-backend/v2/app.js b/aula-14/app-backend/v2/app.js new file mode 100644 index 0000000..b86f3d3 --- /dev/null +++ b/aula-14/app-backend/v2/app.js @@ -0,0 +1,21 @@ +const http = require("http"); + +let requestCount = 0; + +const server = http.createServer((req, res) => { + if (req.url === "/health") { + res.writeHead(200); + res.end("ok"); + return; + } + + requestCount++; + console.log(`[v2] request ${requestCount}`); + + res.writeHead(200); + res.end(`v2 - Request ${requestCount}\n`); +}); + +server.listen(3000, () => { + console.log("[v2] Porta 3000 (estável)"); +}); diff --git a/aula-14/cleanup.sh b/aula-14/cleanup.sh new file mode 100755 index 0000000..f19c415 --- /dev/null +++ b/aula-14/cleanup.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# ============================================================================ +# Aula 14 - Cleanup do Istio Traffic Splitting +# ============================================================================ +# Remove todos os componentes instalados pelo setup.sh: +# - Namespace istio (aplicação) +# - Kiali, Jaeger (addons) +# - Istio (istiod + base) +# - Ingress resources +# +# Mantém: +# - Cluster Kubernetes +# - GitLab e Registry (aula-10) +# - Outros namespaces +# ============================================================================ + +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 14 Istio Traffic Splitting ║${NC}" +echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}" +echo "" +echo -e "${YELLOW}Este script irá remover:${NC}" +echo " - Namespace 'istio' com aplicação" +echo " - Addons: Kiali, Jaeger" +echo " - Istio: istiod e istio-base" +echo " - Ingress do Kiali e Jaeger" +echo "" +read -p "Continuar? (digite 'sim' para confirmar): " confirm +if [[ "$confirm" != "sim" ]]; then + log_info "Operação cancelada" + exit 0 +fi + +echo "" + +# Remover pod de teste +log_info "Removendo pod de teste..." +kubectl delete pod curl-test -n istio --ignore-not-found=true 2>/dev/null || true + +# Remover namespace istio (aplicação demo) +if kubectl get namespace istio &> /dev/null; then + log_info "Removendo namespace istio..." + kubectl delete namespace istio --wait=false 2>/dev/null || true + log_success "Namespace istio marcado para remoção" +else + log_info "Namespace istio não encontrado" +fi + +# Remover Ingress +log_info "Removendo Ingress..." +kubectl delete ingress kiali jaeger -n istio-system --ignore-not-found=true 2>/dev/null || true +log_success "Ingress removidos" + +# Remover addons (Kiali, Jaeger) +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" + +# Remover istiod +if helm status istiod -n istio-system &> /dev/null; then + log_info "Removendo istiod..." + helm uninstall istiod -n istio-system --wait + log_success "istiod removido" +else + log_info "istiod não encontrado" +fi + +# Remover istio-base +if helm status istio-base -n istio-system &> /dev/null; then + log_info "Removendo istio-base..." + helm uninstall istio-base -n istio-system --wait + log_success "istio-base removido" +else + log_info "istio-base não encontrado" +fi + +# Remover namespace istio-system +if kubectl get namespace istio-system &> /dev/null; then + log_info "Removendo namespace istio-system..." + kubectl delete namespace istio-system --wait=false 2>/dev/null || true + log_success "Namespace istio-system marcado para remoção" +fi + +# Remover arquivo .env +echo "" +read -p "Remover arquivo .env? [s/N]: " remove_env +if [[ "$remove_env" == "s" || "$remove_env" == "S" ]]; then + rm -f "${SCRIPT_DIR}/.env" + log_success "Arquivo .env removido" +fi + +echo "" +echo -e "${GREEN}Cleanup concluído.${NC}" +echo "" +echo "Aguarde alguns minutos para os namespaces serem removidos completamente." +echo "Verificar: kubectl get ns" +echo "" diff --git a/aula-14/istio/gateway.yaml b/aula-14/istio/gateway.yaml new file mode 100644 index 0000000..6e29066 --- /dev/null +++ b/aula-14/istio/gateway.yaml @@ -0,0 +1,36 @@ +apiVersion: networking.istio.io/v1beta1 +kind: Gateway +metadata: + name: app-backend-gateway + namespace: istio +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "${APP_HOST}" +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: app-backend-external + namespace: istio +spec: + hosts: + - "${APP_HOST}" + gateways: + - app-backend-gateway + http: + - route: + - destination: + host: app-backend + subset: v1 + weight: 90 + - destination: + host: app-backend + subset: v2 + weight: 10 diff --git a/aula-14/istio/jaeger-values.yaml b/aula-14/istio/jaeger-values.yaml new file mode 100644 index 0000000..76484e3 --- /dev/null +++ b/aula-14/istio/jaeger-values.yaml @@ -0,0 +1,29 @@ +# 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 new file mode 100644 index 0000000..8a143b3 --- /dev/null +++ b/aula-14/istio/kiali-values.yaml @@ -0,0 +1,24 @@ +# Kiali - Service Mesh Observability +# Dashboard para visualizar tráfego do Istio + +server: + web_root: /kiali + +auth: + strategy: anonymous + +deployment: + accessible_namespaces: + - "**" + ingress: + enabled: false + +external_services: + prometheus: + url: http://vmsingle-victoria-metrics-k8s-stack.monitoring:8429 + tracing: + enabled: true + in_cluster_url: http://tracing.istio-system:16685/jaeger + use_grpc: true + grafana: + enabled: false diff --git a/aula-14/k8s/deployment-v1.yaml b/aula-14/k8s/deployment-v1.yaml new file mode 100644 index 0000000..3a62678 --- /dev/null +++ b/aula-14/k8s/deployment-v1.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app-backend-v1 + namespace: istio + labels: + app: app-backend + version: v1 +spec: + replicas: 1 + selector: + matchLabels: + app: app-backend + version: v1 + template: + metadata: + labels: + app: app-backend + version: v1 + spec: + imagePullSecrets: + - name: regcred + containers: + - name: app-backend + image: ${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend:v1 + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: MAX_REQUESTS + value: "100" + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 3 + periodSeconds: 3 + failureThreshold: 2 + terminationGracePeriodSeconds: 10 diff --git a/aula-14/k8s/deployment-v2.yaml b/aula-14/k8s/deployment-v2.yaml new file mode 100644 index 0000000..19cfa09 --- /dev/null +++ b/aula-14/k8s/deployment-v2.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: app-backend-v2 + namespace: istio + labels: + app: app-backend + version: v2 +spec: + replicas: 1 + selector: + matchLabels: + app: app-backend + version: v2 + template: + metadata: + labels: + app: app-backend + version: v2 + spec: + imagePullSecrets: + - name: regcred + containers: + - name: app-backend + image: ${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend:v2 + imagePullPolicy: Always + ports: + - containerPort: 3000 + env: + - name: MAX_REQUESTS + value: "100" + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 3 + periodSeconds: 3 + failureThreshold: 2 + terminationGracePeriodSeconds: 10 diff --git a/aula-14/k8s/destination-rule.yaml b/aula-14/k8s/destination-rule.yaml new file mode 100644 index 0000000..0a784de --- /dev/null +++ b/aula-14/k8s/destination-rule.yaml @@ -0,0 +1,14 @@ +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: app-backend + namespace: istio +spec: + host: app-backend + subsets: + - name: v1 + labels: + version: v1 + - name: v2 + labels: + version: v2 diff --git a/aula-14/k8s/gateway.yaml b/aula-14/k8s/gateway.yaml new file mode 100644 index 0000000..4ac74b9 --- /dev/null +++ b/aula-14/k8s/gateway.yaml @@ -0,0 +1,15 @@ +apiVersion: networking.istio.io/v1beta1 +kind: Gateway +metadata: + name: app-gateway + namespace: istio +spec: + selector: + istio: ingressgateway + servers: + - port: + number: 80 + name: http + protocol: HTTP + hosts: + - "${APP_HOST}" diff --git a/aula-14/k8s/ingress-app.yaml b/aula-14/k8s/ingress-app.yaml new file mode 100644 index 0000000..14fbf68 --- /dev/null +++ b/aula-14/k8s/ingress-app.yaml @@ -0,0 +1,23 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: app-backend + namespace: istio-system + annotations: + nginx.ingress.kubernetes.io/backend-protocol: HTTP + nginx.ingress.kubernetes.io/upstream-vhost: "${APP_HOST}" + ${TLS_ANNOTATION} +spec: + ingressClassName: nginx + ${TLS_CONFIG} + rules: + - host: ${APP_HOST} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: istio-ingressgateway + port: + number: 80 diff --git a/aula-14/k8s/ingress-jaeger.yaml b/aula-14/k8s/ingress-jaeger.yaml new file mode 100644 index 0000000..b85d08a --- /dev/null +++ b/aula-14/k8s/ingress-jaeger.yaml @@ -0,0 +1,25 @@ +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 new file mode 100644 index 0000000..7d62814 --- /dev/null +++ b/aula-14/k8s/ingress-kiali.yaml @@ -0,0 +1,25 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kiali + 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: ${KIALI_HOST} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kiali + port: + number: 20001 diff --git a/aula-14/k8s/namespace.yaml b/aula-14/k8s/namespace.yaml new file mode 100644 index 0000000..a54ac46 --- /dev/null +++ b/aula-14/k8s/namespace.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: istio + labels: + istio-injection: enabled + # Istio sidecar requer NET_ADMIN e NET_RAW capabilities + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/aula-14/k8s/service.yaml b/aula-14/k8s/service.yaml new file mode 100644 index 0000000..1513dd2 --- /dev/null +++ b/aula-14/k8s/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: app-backend + namespace: istio + labels: + app: app-backend +spec: + ports: + - port: 80 + targetPort: 3000 + name: http + selector: + app: app-backend diff --git a/aula-14/k8s/virtual-service.yaml b/aula-14/k8s/virtual-service.yaml new file mode 100644 index 0000000..f097c69 --- /dev/null +++ b/aula-14/k8s/virtual-service.yaml @@ -0,0 +1,22 @@ +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: app-backend + namespace: istio +spec: + hosts: + - app-backend + - "${APP_HOST}" + gateways: + - app-gateway + - mesh + http: + - route: + - destination: + host: app-backend + subset: v1 + weight: 90 + - destination: + host: app-backend + subset: v2 + weight: 10 diff --git a/aula-14/setup.sh b/aula-14/setup.sh new file mode 100755 index 0000000..bab6c2b --- /dev/null +++ b/aula-14/setup.sh @@ -0,0 +1,592 @@ +#!/bin/bash +# ============================================================================ +# Aula 14 - Istio Traffic Splitting +# ============================================================================ +# Instala Istio com Kiali e Jaeger 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 +# - Victoria Metrics da aula-12 +# - GitLab com Registry da aula-10 +# - kubectl, helm e docker instalados +# ============================================================================ + +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" +AULA10_ENV="${SCRIPT_DIR}/../aula-10/.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 14 - Istio Traffic Splitting +# Gerado em: $(date) + +REGISTRY_HOST=${REGISTRY_HOST} +REGISTRY_PROJECT=${REGISTRY_PROJECT} +DOMAIN=${DOMAIN} +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} +BASIC_AUTH_PASS=${BASIC_AUTH_PASS} +EOF + log_success "Configuração salva em .env" +} + +collect_user_input() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configuração do Istio Traffic Splitting${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo "" + + if load_config; then + echo -e "Configuração existente encontrada:" + 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" + read -p "Escolha [1/2]: " choice + if [[ "$choice" == "1" ]]; then + return 0 + fi + fi + + if [[ -f "$AULA10_ENV" ]]; then + source "$AULA10_ENV" + log_info "Configuração herdada da aula-10" + fi + + if [[ -z "$DOMAIN" ]]; then + read -p "Domínio base (ex: kube.quest): " DOMAIN + else + echo -e "Domínio: ${GREEN}${DOMAIN}${NC}" + read -p "Enter para confirmar ou digite novo valor: " new_domain + [[ -n "$new_domain" ]] && DOMAIN="$new_domain" + fi + + if [[ -z "$REGISTRY_HOST" ]]; then + REGISTRY_HOST="reg.${DOMAIN}" + fi + echo -e "Registry: ${GREEN}${REGISTRY_HOST}${NC}" + read -p "Enter para confirmar ou digite novo valor: " new_reg + [[ -n "$new_reg" ]] && REGISTRY_HOST="$new_reg" + + # Grupo/projeto no GitLab para o registry (ex: root, demo, factory) + if [[ -z "$REGISTRY_PROJECT" ]]; then + REGISTRY_PROJECT="root" + fi + echo "" + echo -e "Grupo/projeto no GitLab: ${GREEN}${REGISTRY_PROJECT}${NC}" + echo -e " Imagens: ${CYAN}${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend:v1${NC}" + echo -e " ${CYAN}${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend:v2${NC}" + read -p "Grupo/projeto [${REGISTRY_PROJECT}]: " new_project + [[ -n "$new_project" ]] && REGISTRY_PROJECT="$new_project" + + APP_HOST="${APP_HOST:-app.${DOMAIN}}" + KIALI_HOST="${KIALI_HOST:-kiali.${DOMAIN}}" + JAEGER_HOST="${JAEGER_HOST:-jaeger.${DOMAIN}}" + + echo "" + echo -e "Hosts para serviços:" + echo -e " App: ${GREEN}${APP_HOST}${NC}" + echo -e " Kiali: ${GREEN}${KIALI_HOST}${NC}" + echo -e " Jaeger: ${GREEN}${JAEGER_HOST}${NC}" + read -p "Enter para confirmar ou 'n' para personalizar: " confirm + if [[ "$confirm" == "n" ]]; then + read -p "Host do App: " APP_HOST + read -p "Host do Kiali: " KIALI_HOST + read -p "Host do Jaeger: " JAEGER_HOST + fi + + echo "" + echo -e "[1] Usar Let's Encrypt (HTTPS)" + echo -e "[2] Sem TLS (HTTP)" + read -p "Escolha [1/2]: " tls_choice + if [[ "$tls_choice" == "1" ]]; then + USE_LETSENCRYPT=true + if [[ -z "$LETSENCRYPT_EMAIL" ]]; then + read -p "Email para Let's Encrypt: " LETSENCRYPT_EMAIL + fi + else + USE_LETSENCRYPT=false + fi + + # Basic Auth para Kiali e Jaeger + echo "" + echo -e "${CYAN}Autenticação para Kiali e Jaeger:${NC}" + if [[ -z "$BASIC_AUTH_USER" ]]; then + BASIC_AUTH_USER="admin" + fi + echo -e "Usuário: ${GREEN}${BASIC_AUTH_USER}${NC}" + read -p "Enter para confirmar ou digite novo valor: " new_user + [[ -n "$new_user" ]] && BASIC_AUTH_USER="$new_user" + + if [[ -z "$BASIC_AUTH_PASS" ]]; then + BASIC_AUTH_PASS=$(openssl rand -base64 12 | tr -d '/+=' | head -c 16) + echo -e "Senha gerada: ${GREEN}${BASIC_AUTH_PASS}${NC}" + else + echo -e "Senha: ${GREEN}${BASIC_AUTH_PASS}${NC}" + fi + read -p "Enter para confirmar ou digite nova senha: " new_pass + [[ -n "$new_pass" ]] && BASIC_AUTH_PASS="$new_pass" + + save_config +} + +# ============================================================================ +# Verificação de Pré-requisitos +# ============================================================================ + +check_prerequisites() { + echo "" + log_info "Verificando pré-requisitos..." + + if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado" + exit 1 + fi + log_success "kubectl encontrado" + + if ! command -v helm &> /dev/null; then + log_error "helm não encontrado" + exit 1 + fi + log_success "helm encontrado" + + if ! command -v docker &> /dev/null; then + log_error "docker não encontrado" + exit 1 + fi + log_success "docker encontrado" + + 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 Victoria Metrics (aula-12) + if kubectl get svc -n monitoring vmsingle-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" + 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." + VICTORIA_METRICS_URL="" + fi +} + +# ============================================================================ +# Instalação do Istio +# ============================================================================ + +install_istio() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalando Istio${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + helm repo add istio https://istio-release.storage.googleapis.com/charts 2>/dev/null || true + helm repo update istio + + kubectl create namespace istio-system 2>/dev/null || true + + log_info "Instalando istio-base..." + if helm status istio-base -n istio-system &> /dev/null; then + helm upgrade istio-base istio/base -n istio-system --wait + else + helm install istio-base istio/base -n istio-system --wait + fi + log_success "istio-base instalado" + + log_info "Instalando istiod..." + if helm status istiod -n istio-system &> /dev/null; then + helm upgrade istiod istio/istiod -n istio-system --wait + else + helm install istiod istio/istiod -n istio-system --wait + fi + log_success "istiod instalado" + + log_info "Aguardando istiod..." + kubectl wait --for=condition=available deployment/istiod -n istio-system --timeout=300s + log_success "istiod pronto" + + log_info "Instalando istio-ingressgateway..." + if helm status istio-ingressgateway -n istio-system &> /dev/null; then + helm upgrade istio-ingressgateway istio/gateway -n istio-system --wait + else + helm install istio-ingressgateway istio/gateway -n istio-system --wait + fi + log_success "istio-ingressgateway instalado" + + log_info "Aguardando ingressgateway..." + kubectl wait --for=condition=available deployment/istio-ingressgateway -n istio-system --timeout=300s + log_success "ingressgateway pronto" +} + +# ============================================================================ +# Instalação de Observabilidade +# ============================================================================ + +install_observability() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalando Kiali e Jaeger${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 + + # Configurar Kiali para usar Victoria Metrics + if [[ -n "$VICTORIA_METRICS_URL" ]]; then + 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\" + } + }" 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..." + 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" +} + +# ============================================================================ +# Configuração de Basic Auth +# ============================================================================ + +setup_basic_auth() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configurando Basic Auth para Kiali e Jaeger${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Criando secret basic-auth..." + + # Verificar se htpasswd está disponível + if ! command -v htpasswd &> /dev/null; then + log_info "htpasswd não encontrado, usando openssl..." + # Gerar hash com openssl (compatível com Apache htpasswd) + local HASH=$(openssl passwd -apr1 "${BASIC_AUTH_PASS}") + echo "${BASIC_AUTH_USER}:${HASH}" > /tmp/auth + else + htpasswd -cb /tmp/auth "${BASIC_AUTH_USER}" "${BASIC_AUTH_PASS}" + fi + + kubectl delete secret basic-auth -n istio-system 2>/dev/null || true + kubectl create secret generic basic-auth -n istio-system --from-file=auth=/tmp/auth + rm -f /tmp/auth + + log_success "Secret basic-auth criado" +} + +# ============================================================================ +# Configuração de Ingress +# ============================================================================ + +setup_ingress() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configurando Ingress para Kiali e Jaeger${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + local TLS_ANNOTATION="" + local TLS_CONFIG="" + + if [[ "$USE_LETSENCRYPT" == "true" ]]; then + TLS_ANNOTATION="cert-manager.io/cluster-issuer: letsencrypt-prod" + fi + + log_info "Criando Ingress para Kiali..." + if [[ "$USE_LETSENCRYPT" == "true" ]]; then + TLS_CONFIG="tls: + - hosts: + - ${KIALI_HOST} + secretName: kiali-tls" + fi + 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() { + echo "" + log_info "Criando Ingress para App..." + + local TLS_ANNOTATION="" + local TLS_CONFIG="" + + if [[ "$USE_LETSENCRYPT" == "true" ]]; then + TLS_ANNOTATION="cert-manager.io/cluster-issuer: letsencrypt-prod" + TLS_CONFIG="tls: + - hosts: + - ${APP_HOST} + secretName: app-tls" + fi + + export APP_HOST TLS_ANNOTATION TLS_CONFIG + envsubst '${APP_HOST} ${TLS_ANNOTATION} ${TLS_CONFIG}' < "${SCRIPT_DIR}/k8s/ingress-app.yaml" | kubectl apply -f - + log_success "Ingress do App criado" +} + +# ============================================================================ +# Build e Push das Imagens +# ============================================================================ + +build_and_push_images() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Build e Push das Imagens (ARM64)${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + local IMAGE_BASE="${REGISTRY_HOST}/${REGISTRY_PROJECT}/app-backend" + + log_info "Build app-backend:v1 (linux/arm64)..." + docker buildx build --platform linux/arm64 -t "${IMAGE_BASE}:v1" --push "${SCRIPT_DIR}/app-backend/v1" + log_success "app-backend:v1 construída e enviada" + + log_info "Build app-backend:v2 (linux/arm64)..." + docker buildx build --platform linux/arm64 -t "${IMAGE_BASE}:v2" --push "${SCRIPT_DIR}/app-backend/v2" + log_success "app-backend:v2 construída e enviada" +} + +# ============================================================================ +# Deploy da Aplicação +# ============================================================================ + +create_registry_secret() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Criando Secret do Registry${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Criando namespace istio..." + kubectl apply -f "${SCRIPT_DIR}/k8s/namespace.yaml" + log_success "Namespace criado" + + log_info "Obtendo credenciais do registry..." + local CREDS + if command -v docker-credential-osxkeychain &> /dev/null; then + CREDS=$(docker-credential-osxkeychain get <<< "${REGISTRY_HOST}" 2>/dev/null || true) + elif command -v docker-credential-secretservice &> /dev/null; then + CREDS=$(docker-credential-secretservice get <<< "${REGISTRY_HOST}" 2>/dev/null || true) + fi + + if [[ -n "$CREDS" ]]; then + local USERNAME=$(echo "$CREDS" | jq -r '.Username') + local PASSWORD=$(echo "$CREDS" | jq -r '.Secret') + + kubectl delete secret regcred -n istio 2>/dev/null || true + kubectl create secret docker-registry regcred -n istio \ + --docker-server="${REGISTRY_HOST}" \ + --docker-username="${USERNAME}" \ + --docker-password="${PASSWORD}" + log_success "Secret regcred criado" + else + log_warn "Credenciais não encontradas no credential helper" + log_info "Tentando criar secret a partir do Docker config..." + kubectl delete secret regcred -n istio 2>/dev/null || true + kubectl create secret generic regcred -n istio \ + --from-file=.dockerconfigjson="${HOME}/.docker/config.json" \ + --type=kubernetes.io/dockerconfigjson 2>/dev/null || \ + log_warn "Falha ao criar secret. Faça login no registry: docker login ${REGISTRY_HOST}" + fi +} + +deploy_application() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Deploy da Aplicação${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Deploy app-backend v1..." + export REGISTRY_HOST REGISTRY_PROJECT + envsubst < "${SCRIPT_DIR}/k8s/deployment-v1.yaml" | kubectl apply -f - + log_success "v1 implantada" + + log_info "Deploy app-backend v2..." + envsubst < "${SCRIPT_DIR}/k8s/deployment-v2.yaml" | kubectl apply -f - + log_success "v2 implantada" + + log_info "Criando Service..." + kubectl apply -f "${SCRIPT_DIR}/k8s/service.yaml" + log_success "Service criado" + + log_info "Aplicando DestinationRule..." + kubectl apply -f "${SCRIPT_DIR}/k8s/destination-rule.yaml" + log_success "DestinationRule aplicada" + + log_info "Aplicando Gateway..." + export APP_HOST + envsubst < "${SCRIPT_DIR}/k8s/gateway.yaml" | kubectl apply -f - + log_success "Gateway aplicado" + + log_info "Aplicando VirtualService (90% v1, 10% v2)..." + envsubst < "${SCRIPT_DIR}/k8s/virtual-service.yaml" | kubectl apply -f - + log_success "VirtualService aplicado" + + log_info "Aguardando pods..." + kubectl wait --for=condition=available deployment/app-backend-v1 -n istio --timeout=120s + kubectl wait --for=condition=available deployment/app-backend-v2 -n istio --timeout=120s + log_success "Pods prontos" +} + +create_test_pod() { + log_info "Criando pod de teste..." + kubectl run curl-test -n istio --image=curlimages/curl:latest \ + --restart=Never --command -- sleep infinity 2>/dev/null || true + kubectl wait --for=condition=ready pod/curl-test -n istio --timeout=60s 2>/dev/null || true + log_success "Pod de teste disponível" +} + +# ============================================================================ +# Resumo +# ============================================================================ + +show_summary() { + local protocol="http" + [[ "$USE_LETSENCRYPT" == "true" ]] && protocol="https" + + 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 " - 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 + echo -e "${GREEN}Métricas:${NC} Victoria Metrics (aula-12)" + else + echo -e "${YELLOW}Métricas:${NC} Victoria Metrics não disponível. Instale aula-12." + fi + 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 "" + echo -e "${GREEN}Credenciais (Basic Auth para Kiali/Jaeger):${NC}" + echo -e " Usuário: ${CYAN}${BASIC_AUTH_USER}${NC}" + echo -e " Senha: ${CYAN}${BASIC_AUTH_PASS}${NC}" + echo "" + echo -e "${GREEN}Fluxo de Uso:${NC}" + echo "" + echo " # 1. Setup" + echo " ./setup.sh # Deploy com 90% v1, 10% v2" + echo "" + echo " # 2. Testar distribuição" + echo " ./teste-stress.sh # Veja ~90 v1, ~10 v2" + echo "" + echo " # 3. Mudar para 50/50 e re-testar" + echo " kubectl patch virtualservice app-backend -n istio --type='json' \\" + echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":50}," + echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":50}]'" + echo " ./teste-stress.sh" + echo "" + echo " # 4. Rollout completo para v2" + echo " kubectl patch virtualservice app-backend -n istio --type='json' \\" + echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":0}," + echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":100}]'" + echo " ./teste-stress.sh" + echo "" + 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 "" +} + +# ============================================================================ +# Execução +# ============================================================================ + +main() { + echo "" + echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ Aula 14 - Istio Traffic Splitting ║${NC}" + echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}" + + check_prerequisites + collect_user_input + install_istio + install_observability + setup_basic_auth + setup_ingress + build_and_push_images + create_registry_secret + deploy_application + setup_app_ingress + create_test_pod + show_summary +} + +main "$@" diff --git a/aula-14/teste-stress.sh b/aula-14/teste-stress.sh new file mode 100755 index 0000000..18ca3d2 --- /dev/null +++ b/aula-14/teste-stress.sh @@ -0,0 +1,198 @@ +#!/bin/bash +# ============================================================================ +# Teste de Stress - Demonstração de Traffic Splitting do Istio +# ============================================================================ +# Script interativo para visualizar a distribuição de tráfego entre v1 e v2. +# +# Uso: +# ./teste-stress.sh [URL] [NUM_REQUESTS] +# +# Exemplos: +# ./teste-stress.sh # Usa APP_HOST do .env, 100 requests +# ./teste-stress.sh https://app.kube.quest # URL específica, 100 requests +# ./teste-stress.sh https://app.kube.quest 50 # 50 requests +# ============================================================================ + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +MAGENTA='\033[0;35m' +NC='\033[0m' +BOLD='\033[1m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENV_FILE="${SCRIPT_DIR}/.env" + +# ============================================================================ +# Configuração +# ============================================================================ + +# Carregar .env se existir +if [[ -f "$ENV_FILE" ]]; then + source "$ENV_FILE" +fi + +# Parâmetros +URL="${1:-}" +NUM_REQUESTS="${2:-100}" + +# Se URL não foi passada, tentar construir do .env +if [[ -z "$URL" ]]; then + if [[ -n "$APP_HOST" ]]; then + if [[ "$USE_LETSENCRYPT" == "true" ]]; then + URL="https://${APP_HOST}" + else + URL="http://${APP_HOST}" + fi + else + echo -e "${RED}[ERRO]${NC} URL não especificada e APP_HOST não encontrado no .env" + echo "" + echo "Uso: $0 [URL] [NUM_REQUESTS]" + echo "" + echo "Exemplos:" + echo " $0 https://app.kube.quest" + echo " $0 https://app.kube.quest 50" + echo "" + echo "Ou configure APP_HOST no .env executando setup.sh novamente." + exit 1 + fi +fi + +# ============================================================================ +# Funções +# ============================================================================ + +get_current_weights() { + local weights + weights=$(kubectl get virtualservice app-backend -n istio -o jsonpath='{.spec.http[0].route[*].weight}' 2>/dev/null || echo "90 10") + echo "$weights" +} + +draw_bar() { + local value=$1 + local max=$2 + local width=40 + local filled=$((value * width / max)) + local empty=$((width - filled)) + + printf "[" + for ((i=0; i/dev/null || echo "ERROR") + + # Identificar versão baseado na resposta + if echo "$response" | grep -q "v1\|node-bugado v1"; then + ((count_v1++)) + elif echo "$response" | grep -q "v2\|stable\|node-bugado v2"; then + ((count_v2++)) + else + ((count_other++)) + fi + + # Exibir progresso + printf "\r${NC}[%3d/%d] ${BLUE}v1:%-3d${NC} ${GREEN}v2:%-3d${NC}" "$i" "$NUM_REQUESTS" "$count_v1" "$count_v2" + + # Pequena pausa para não sobrecarregar + sleep 0.05 + done + + echo "" + echo "" + + # Exibir resultados + show_results "$count_v1" "$count_v2" "$count_other" +} + +show_results() { + local count_v1=$1 + local count_v2=$2 + local count_other=$3 + local total=$((count_v1 + count_v2 + count_other)) + + local pct_v1=0 + local pct_v2=0 + [[ $total -gt 0 ]] && pct_v1=$((count_v1 * 100 / total)) + [[ $total -gt 0 ]] && pct_v2=$((count_v2 * 100 / total)) + + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${BOLD}Resultados:${NC}" + echo "" + printf " ${BLUE}v1${NC}: %3d (%2d%%) " "$count_v1" "$pct_v1" + draw_bar "$count_v1" "$total" + echo "" + printf " ${GREEN}v2${NC}: %3d (%2d%%) " "$count_v2" "$pct_v2" + draw_bar "$count_v2" "$total" + echo "" + + if [[ $count_other -gt 0 ]]; then + echo -e " ${RED}Erros${NC}: $count_other" + fi + + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo "" + + # Comandos para alterar distribuição + show_commands +} + +show_commands() { + echo -e "${BOLD}Comandos para alterar distribuição:${NC}" + echo "" + echo -e "${YELLOW}# Aumentar tráfego para v2 (50/50)${NC}" + echo "kubectl patch virtualservice app-backend -n istio --type='json' \\" + echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":50}," + echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":50}]'" + echo "" + echo -e "${YELLOW}# Virada completa para v2 (0/100)${NC}" + echo "kubectl patch virtualservice app-backend -n istio --type='json' \\" + echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":0}," + echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":100}]'" + echo "" + echo -e "${YELLOW}# Rollback para v1 se necessário (100/0)${NC}" + echo "kubectl patch virtualservice app-backend -n istio --type='json' \\" + echo " -p='[{\"op\":\"replace\",\"path\":\"/spec/http/0/route/0/weight\",\"value\":100}," + echo " {\"op\":\"replace\",\"path\":\"/spec/http/0/route/1/weight\",\"value\":0}]'" + echo "" + echo -e "${CYAN}Após alterar, re-execute: ${NC}${BOLD}./teste-stress.sh${NC}" + echo "" +} + +# ============================================================================ +# Execução +# ============================================================================ + +show_header +run_test