diff --git a/aula-05/configmap.yaml b/aula-05/configmap.yaml new file mode 100644 index 0000000..1f600c8 --- /dev/null +++ b/aula-05/configmap.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config +data: + MAX_REQUESTS: "3" + + app.js: | + const http = require("http"); + + const MAX_REQUESTS = Number(process.env.MAX_REQUESTS || 3); + + let requestCount = 0; + + console.log("MAX_REQUESTS =", MAX_REQUESTS); + + const server = http.createServer((req, res) => { + if (req.url === "/health") { + if (requestCount > MAX_REQUESTS) { + console.log('❌ O health check falhou'); + return; + } + + res.writeHead(200); + res.end(`ok`); + return; + } + + requestCount++; + console.log("request", requestCount); + + if (requestCount > MAX_REQUESTS) { + console.log(`🔥 App travado após ${MAX_REQUESTS} requests`); + return; + } + + res.writeHead(200); + res.end(`Req -> ${requestCount}/${MAX_REQUESTS}`); + }); + + server.listen(3000, () => { + console.log("App rodando na porta 3000"); + }); diff --git a/aula-05/deployment.yaml b/aula-05/deployment.yaml new file mode 100644 index 0000000..12cecad --- /dev/null +++ b/aula-05/deployment.yaml @@ -0,0 +1,56 @@ +# Deployment com 5 replicas iniciais - KEDA escala automaticamente até 30 +# Configurado para 300 requisições sem falhas +apiVersion: apps/v1 +kind: Deployment +metadata: + name: node-bugado +spec: + replicas: 5 # Mínimo 5 pods - KEDA escala ultra-agressivo quando detecta problemas + selector: + matchLabels: + app: node-bugado + template: + metadata: + labels: + app: node-bugado + spec: + terminationGracePeriodSeconds: 1 + containers: + - name: app + image: node:24-alpine + command: ["node", "/app/app.js"] + env: + - name: MAX_REQUESTS + valueFrom: + configMapKeyRef: + name: app-config + key: MAX_REQUESTS + ports: + - containerPort: 3000 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + readinessProbe: + httpGet: + path: /health + port: 3000 + periodSeconds: 1 # Verifica a cada 1 segundo + failureThreshold: 1 # 1 falha = remove do Service + livenessProbe: + httpGet: + path: /health + port: 3000 + initialDelaySeconds: 2 # Espera para começar a testar + periodSeconds: 1 # A cada 1 seg faz o teste novamente + failureThreshold: 2 # 2 falhas = reinicia o container + volumeMounts: + - name: app-volume + mountPath: /app + volumes: + - name: app-volume + configMap: + name: app-config diff --git a/aula-05/ingress-nginx.yaml b/aula-05/ingress-nginx.yaml new file mode 100644 index 0000000..00b640d --- /dev/null +++ b/aula-05/ingress-nginx.yaml @@ -0,0 +1,65 @@ +# NGINX Ingress com "Keep Request" + Retry automático +# Quando um pod falha, tenta automaticamente outro pod (até 5 tentativas) +# Quando todos os pods estão indisponíveis, aguarda até 99s antes de retornar 503 +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: node-bugado-nginx + annotations: + # Retry automático: tenta outro pod quando recebe erro + nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout http_502 http_503 http_504" + nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "5" + nginx.ingress.kubernetes.io/proxy-next-upstream-timeout: "30" + # Location interno que aguarda o backend ficar disponível + nginx.ingress.kubernetes.io/server-snippet: | + error_page 502 503 504 = @wait_for_backend; + + location @wait_for_backend { + internal; + content_by_lua_block { + local max_wait = 99 + local interval = 1 + local start = ngx.now() + local backend_host = "node-bugado.default.svc.cluster.local" + local backend_port = 3000 + + ngx.log(ngx.INFO, "[KeepRequest] Aguardando backend por ate ", max_wait, "s...") + + while (ngx.now() - start) < max_wait do + local sock = ngx.socket.tcp() + sock:settimeout(1000) + + local ok, err = sock:connect(backend_host, backend_port) + if ok then + sock:close() + local elapsed = math.floor(ngx.now() - start) + ngx.log(ngx.INFO, "[KeepRequest] Backend ready apos ", elapsed, "s") + -- Redirect interno (não retorna ao cliente) + return ngx.exec(ngx.var.request_uri) + end + + ngx.log(ngx.WARN, "[KeepRequest] Backend indisponivel: ", err) + ngx.sleep(interval) + end + + ngx.status = 503 + ngx.header["Content-Type"] = "text/plain" + ngx.say("Servico indisponivel apos ", max_wait, "s de espera") + ngx.exit(503) + } + } + # Timeouts curtos para falhar rápido e ir para o handler Lua + nginx.ingress.kubernetes.io/proxy-connect-timeout: "2" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3" +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: node-bugado + port: + number: 3000 diff --git a/aula-05/scaledobject.yaml b/aula-05/scaledobject.yaml new file mode 100644 index 0000000..d8dfc88 --- /dev/null +++ b/aula-05/scaledobject.yaml @@ -0,0 +1,135 @@ +# ============================================================================= +# KEDA ScaledObject - Auto-scaling baseado em métricas do Victoria Metrics +# ============================================================================= +# +# O que é KEDA? +# KEDA (Kubernetes Event-driven Autoscaling) permite escalar deployments +# baseado em métricas externas, como Prometheus, Victoria Metrics, RabbitMQ, etc. +# +# Como funciona? +# 1. KEDA consulta o Victoria Metrics a cada 'pollingInterval' segundos +# 2. Se a métrica ultrapassar o 'threshold', ele escala UP +# 3. Se a métrica ficar abaixo do 'threshold', ele escala DOWN +# 4. O KEDA cria um HPA (Horizontal Pod Autoscaler) automaticamente +# +# ============================================================================= + +apiVersion: keda.sh/v1alpha1 +kind: ScaledObject +metadata: + name: node-bugado-scaledobject +spec: + # --------------------------------------------------------------------------- + # CONFIGURAÇÃO BÁSICA + # --------------------------------------------------------------------------- + + # Deployment que será escalado + scaleTargetRef: + name: node-bugado + + # Limites de réplicas + minReplicaCount: 5 # Nunca menos que 5 pods (alta disponibilidade) + maxReplicaCount: 30 # Nunca mais que 30 pods (limite de recursos) + + # Frequência de verificação das métricas + pollingInterval: 1 # Verifica a cada 1 segundo (mínimo possível) + # Valores menores = reação mais rápida + # Valores maiores = menos carga no Victoria Metrics + + # Tempo de espera antes de iniciar scale down + cooldownPeriod: 5 # Aguarda 5s após métricas normalizarem + # Evita scale down prematuro + + # --------------------------------------------------------------------------- + # COMPORTAMENTO DO HPA (Horizontal Pod Autoscaler) + # --------------------------------------------------------------------------- + advanced: + horizontalPodAutoscalerConfig: + behavior: + # SCALE UP - Quando precisa de mais pods + scaleUp: + stabilizationWindowSeconds: 0 # Sem espera - escala imediatamente + policies: + - type: Pods + value: 5 # Adiciona 5 pods de cada vez + periodSeconds: 1 # A cada 1 segundo pode adicionar mais + # Exemplo: 5 -> 10 -> 15 -> 20 -> 25 -> 30 (em 5 segundos) + + # SCALE DOWN - Quando pode reduzir pods + scaleDown: + stabilizationWindowSeconds: 10 # Aguarda 10s de métricas estáveis + policies: + - type: Percent + value: 50 # Remove 50% dos pods por vez + periodSeconds: 5 # A cada 5 segundos + # Exemplo: 30 -> 15 -> 8 -> 5 (em 10 segundos) + + # --------------------------------------------------------------------------- + # TRIGGERS - Métricas que disparam o scaling + # --------------------------------------------------------------------------- + triggers: + # ------------------------------------------------------------------------- + # TRIGGER 1: Pods Indisponíveis + # ------------------------------------------------------------------------- + # Métrica: kube_deployment_status_replicas_unavailable + # Origem: kube-state-metrics (coletado pelo Victoria Metrics) + # Comportamento: Escala quando há pods que não estão prontos para receber tráfego + # + - type: prometheus + metadata: + # Endereço do Victoria Metrics (compatível com API do Prometheus) + serverAddress: http://vmsingle-vm-victoria-metrics-k8s-stack.monitoring:8428 + + # Query PromQL - conta pods indisponíveis do deployment node-bugado + query: | + kube_deployment_status_replicas_unavailable{deployment="node-bugado", namespace="default"} + + # threshold: valor da métrica que dispara o scaling + # Se unavailable >= 1, adiciona pods + threshold: '1' + + # activationThreshold: valor mínimo para ativar o scaler + # Só começa a escalar se unavailable >= 1 + activationThreshold: '1' + + # ------------------------------------------------------------------------- + # TRIGGER 2: Restarts Frequentes + # ------------------------------------------------------------------------- + # Métrica: kube_pod_container_status_restarts_total + # Origem: kube-state-metrics (coletado pelo Victoria Metrics) + # Comportamento: Escala quando pods estão reiniciando muito (indica problemas) + # + - type: prometheus + metadata: + serverAddress: http://vmsingle-vm-victoria-metrics-k8s-stack.monitoring:8428 + + # Query PromQL - soma de restarts nos últimos 2 minutos + # increase() calcula o incremento no período + # sum() agrega todos os pods do deployment + query: | + sum(increase(kube_pod_container_status_restarts_total{namespace="default", pod=~"node-bugado.*"}[2m])) + + # Se houve 1 ou mais restarts nos últimos 2 min, escala + threshold: '1' + activationThreshold: '1' + +# ============================================================================= +# COMANDOS ÚTEIS PARA DEBUG +# ============================================================================= +# +# Ver status do ScaledObject: +# kubectl get scaledobject +# kubectl describe scaledobject node-bugado-scaledobject +# +# Ver HPA criado pelo KEDA: +# kubectl get hpa +# kubectl describe hpa keda-hpa-node-bugado-scaledobject +# +# Testar queries no Victoria Metrics: +# kubectl run curl --rm -it --restart=Never --image=curlimages/curl -- \ +# -s "http://vmsingle-vm-victoria-metrics-k8s-stack.monitoring:8428/api/v1/query?query=kube_deployment_status_replicas_unavailable" +# +# Ver logs do KEDA operator: +# kubectl logs -n keda -l app=keda-operator +# +# ============================================================================= diff --git a/aula-05/service.yaml b/aula-05/service.yaml new file mode 100644 index 0000000..c47e55d --- /dev/null +++ b/aula-05/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: node-bugado +spec: + type: ClusterIP # Acesso via Ingress + selector: + app: node-bugado + ports: + - port: 3000 + targetPort: 3000 + protocol: TCP diff --git a/aula-05/setup.sh b/aula-05/setup.sh new file mode 100755 index 0000000..7eb6c1c --- /dev/null +++ b/aula-05/setup.sh @@ -0,0 +1,235 @@ +#!/bin/bash +# ============================================================================= +# Setup da Aula 05 - KEDA + Victoria Metrics +# ============================================================================= +# +# Este script instala e configura: +# 1. Victoria Metrics (coleta e armazena métricas) +# 2. KEDA (auto-scaling baseado em eventos) +# 3. Aplicação node-bugado com auto-scaling +# +# Pré-requisitos: +# - Kubernetes cluster rodando (k3s, minikube, kind, etc) +# - kubectl configurado +# - Helm 3.x instalado +# +# Uso: +# chmod +x setup.sh +# ./setup.sh +# +# ============================================================================= + +set -e # Para na primeira falha + +# Cores para output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Funções de log +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"; } + +# ============================================================================= +# VERIFICAÇÕES INICIAIS +# ============================================================================= + +log_info "Verificando pré-requisitos..." + +# Verificar kubectl +if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado. Instale o kubectl primeiro." + exit 1 +fi +log_success "kubectl encontrado" + +# Verificar conexão com cluster +if ! kubectl cluster-info &> /dev/null; then + log_error "Não foi possível conectar ao cluster Kubernetes." + exit 1 +fi +log_success "Conectado ao cluster Kubernetes" + +# Verificar helm +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado. Instale o Helm >= 3.14 primeiro." + log_info "Instalação: https://helm.sh/docs/intro/install/" + exit 1 +fi +log_success "Helm $(helm version --short) encontrado" + +echo "" + +# ============================================================================= +# 1. INSTALAR VICTORIA METRICS +# ============================================================================= + +log_info "=== Instalando Victoria Metrics ===" + +# Adicionar repo do Victoria Metrics +log_info "Adicionando repositório Helm do Victoria Metrics..." +helm repo add vm https://victoriametrics.github.io/helm-charts/ 2>/dev/null || true +helm repo update + +# Verificar se já está instalado +if helm status vm -n monitoring &> /dev/null; then + log_warn "Victoria Metrics já está instalado. Pulando..." +else + log_info "Instalando Victoria Metrics Stack..." + helm install vm vm/victoria-metrics-k8s-stack \ + --namespace monitoring \ + --create-namespace \ + --set vmsingle.enabled=true \ + --wait + log_success "Victoria Metrics instalado com sucesso!" +fi + +# Aguardar pods ficarem prontos +log_info "Aguardando pods do Victoria Metrics ficarem prontos..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/name=vmsingle \ + -n monitoring \ + --timeout=120s 2>/dev/null || true + +log_success "Victoria Metrics está rodando" +echo "" + +# ============================================================================= +# 2. INSTALAR KEDA +# ============================================================================= + +log_info "=== Instalando KEDA ===" + +# Adicionar repo do KEDA +log_info "Adicionando repositório Helm do KEDA..." +helm repo add kedacore https://kedacore.github.io/charts 2>/dev/null || true +helm repo update + +# Verificar se já está instalado +if helm status keda -n keda &> /dev/null; then + log_warn "KEDA já está instalado. Pulando..." +else + log_info "Instalando KEDA..." + helm install keda kedacore/keda \ + --namespace keda \ + --create-namespace \ + --wait + log_success "KEDA instalado com sucesso!" +fi + +# Aguardar pods ficarem prontos +log_info "Aguardando pods do KEDA ficarem prontos..." +kubectl wait --for=condition=ready pod \ + -l app=keda-operator \ + -n keda \ + --timeout=120s + +log_success "KEDA está rodando" +echo "" + +# ============================================================================= +# 3. INSTALAR NGINX INGRESS (se não existir) +# ============================================================================= + +log_info "=== Verificando NGINX Ingress ===" + +if kubectl get ingressclass nginx &> /dev/null; then + log_success "NGINX Ingress já está instalado" +else + log_info "Instalando NGINX Ingress Controller..." + helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 2>/dev/null || true + helm repo update + + helm install nginx-ingress ingress-nginx/ingress-nginx \ + --namespace ingress-nginx \ + --create-namespace \ + --wait + + log_success "NGINX Ingress instalado" +fi +echo "" + +# ============================================================================= +# 4. APLICAR MANIFESTOS DA APLICAÇÃO +# ============================================================================= + +log_info "=== Aplicando manifestos da aplicação ===" + +# Diretório do script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +log_info "Aplicando ConfigMap..." +kubectl apply -f "$SCRIPT_DIR/configmap.yaml" + +log_info "Aplicando Deployment..." +kubectl apply -f "$SCRIPT_DIR/deployment.yaml" + +log_info "Aplicando Service..." +kubectl apply -f "$SCRIPT_DIR/service.yaml" + +log_info "Aplicando Ingress..." +kubectl apply -f "$SCRIPT_DIR/ingress-nginx.yaml" + +log_info "Aplicando ScaledObject (KEDA)..." +kubectl apply -f "$SCRIPT_DIR/scaledobject.yaml" + +log_success "Todos os manifestos aplicados!" +echo "" + +# ============================================================================= +# 5. AGUARDAR TUDO FICAR PRONTO +# ============================================================================= + +log_info "=== Aguardando aplicação ficar pronta ===" + +log_info "Aguardando pods da aplicação..." +kubectl wait --for=condition=ready pod \ + -l app=node-bugado \ + --timeout=120s + +log_success "Aplicação está rodando!" +echo "" + +# ============================================================================= +# RESUMO FINAL +# ============================================================================= + +echo "==============================================" +echo -e "${GREEN} Setup Completo!${NC}" +echo "==============================================" +echo "" +echo "Componentes instalados:" +echo " - Victoria Metrics (namespace: monitoring)" +echo " - KEDA (namespace: keda)" +echo " - NGINX Ingress Controller" +echo " - Aplicação node-bugado (namespace: default)" +echo "" +echo "Comandos úteis:" +echo " # Ver pods da aplicação" +echo " kubectl get pods -l app=node-bugado" +echo "" +echo " # Ver ScaledObject e HPA" +echo " kubectl get scaledobject" +echo " kubectl get hpa" +echo "" +echo " # Teste de stress (300 requisições)" +echo " ./teste-stress.sh http://localhost 300" +echo "" +echo " # Monitorar scaling em tempo real" +echo " watch kubectl get pods -l app=node-bugado" +echo "" +echo "==============================================" +echo "" + +# Mostrar status atual +log_info "Status atual:" +echo "" +kubectl get pods -l app=node-bugado +echo "" +kubectl get scaledobject +echo "" +kubectl get hpa diff --git a/aula-05/teste-stress.sh b/aula-05/teste-stress.sh new file mode 100755 index 0000000..1ebc788 --- /dev/null +++ b/aula-05/teste-stress.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Teste de Stress para verificar resiliência do Ingress + Auto-Scaling +# Uso: ./teste-stress.sh [URL] [NUM_REQUESTS] + +URL="${1:-http://localhost}" +TOTAL="${2:-100}" +TIMEOUT=120 + +echo "============================================" +echo " Teste de Stress - Auto-Scaling com KEDA" +echo "============================================" +echo "URL: $URL" +echo "Requisições: $TOTAL" +echo "Timeout: ${TIMEOUT}s" +echo "============================================" +echo "" + +SUCCESS=0 +FAIL=0 + +for i in $(seq 1 $TOTAL); do + RESULT=$(curl -s -m $TIMEOUT "$URL" 2>&1) + + if echo "$RESULT" | grep -q "Req ->"; then + SUCCESS=$((SUCCESS + 1)) + echo -e "[$i/$TOTAL] \033[32mOK\033[0m - $RESULT" + else + FAIL=$((FAIL + 1)) + # Extrai apenas o título do erro se for HTML + if echo "$RESULT" | grep -q "