diff --git a/aula-13/benchmarks/benchmark-clean.sh b/aula-13/benchmarks/benchmark-clean.sh deleted file mode 100755 index bd8efda..0000000 --- a/aula-13/benchmarks/benchmark-clean.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/bash -# ============================================================================= -# Benchmark LIMPO: eStargz vs Traditional Image Pull -# ============================================================================= -# -# Este benchmark força execução em um node SEM cache das imagens -# para medir tempo REAL de pull. -# -# ============================================================================= - -set -e - -NAMESPACE="benchmark-clean" -ESTARGZ_IMAGE="gitea.kube.quest/factory/postgresql:17" -TRADITIONAL_IMAGE="postgres:17-alpine" -TARGET_NODE="talos-msadg4-worker-0" # Node sem cache - -echo "========================================================================" -echo "Benchmark LIMPO: eStargz vs Traditional Image Pull" -echo "========================================================================" -echo "" -echo "Target node: $TARGET_NODE (sem cache de imagens)" -echo "" -echo "Comparando:" -echo " Tradicional: $TRADITIONAL_IMAGE" -echo " eStargz: $ESTARGZ_IMAGE" -echo "" - -# Verificar cluster -echo "[1/6] Verificando cluster..." -kubectl cluster-info >/dev/null || { echo "ERRO: Cluster inacessível"; exit 1; } -echo " Cluster OK" - -# Limpar ambiente anterior -echo "[2/6] Limpando ambiente anterior..." -kubectl delete namespace $NAMESPACE --ignore-not-found=true --wait=true 2>/dev/null || true -echo " Ambiente limpo" - -# Criar namespace -echo "[3/6] Criando namespace de teste..." -kubectl create namespace $NAMESPACE -kubectl create secret docker-registry gitea-registry \ - --docker-server=gitea.kube.quest \ - --docker-username=root \ - --docker-password="${GITEA_TOKEN:-glpat-dummy}" \ - -n $NAMESPACE 2>/dev/null || true -echo " Namespace criado" - -# Teste 1: Imagem tradicional -echo "" -echo "========================================================================" -echo "[4/6] TESTE 1: Imagem Tradicional (gzip) - PULL REAL" -echo "========================================================================" -echo "Iniciando em $(date)" -T1_START=$(date +%s) - -cat <>> Tempo total: ${TIME1}s <<<" - -# Teste 2: Imagem eStargz -echo "" -echo "========================================================================" -echo "[5/6] TESTE 2: Imagem eStargz (lazy pulling) - PULL REAL" -echo "========================================================================" -echo "Iniciando em $(date)" -T2_START=$(date +%s) - -cat <>> Tempo total: ${TIME2}s <<<" - -# Resultados -echo "" -echo "========================================================================" -echo "[6/6] RESULTADOS" -echo "========================================================================" -echo "" - -# Status dos pods -echo "Status dos Pods:" -kubectl get pods -n $NAMESPACE -o wide -echo "" - -# Eventos completos -echo "Todos os Eventos (ordenados por tempo):" -kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' \ - -o custom-columns='TIMESTAMP:.lastTimestamp,REASON:.reason,POD:.involvedObject.name,MESSAGE:.message' -echo "" - -# Verificar se houve pull real -echo "Análise de Pull:" -TRAD_PULL=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=pg-traditional,reason=Pulled -o jsonpath='{.items[0].message}' 2>/dev/null) -ESTARGZ_PULL=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=pg-estargz,reason=Pulled -o jsonpath='{.items[0].message}' 2>/dev/null) - -echo " Tradicional: $TRAD_PULL" -echo " eStargz: $ESTARGZ_PULL" -echo "" - -# Tabela de resultados -echo "┌─────────────────────────────────────────────────────────────────┐" -echo "│ RESULTADOS DO BENCHMARK (PULL REAL) │" -echo "├───────────────────┬─────────────────┬─────────────────────────────┤" -echo "│ Métrica │ Tradicional │ eStargz │" -echo "├───────────────────┼─────────────────┼─────────────────────────────┤" -printf "│ Tempo até Ready │ %12ss │ %12ss │\n" "$TIME1" "$TIME2" -echo "├───────────────────┼─────────────────┼─────────────────────────────┤" - -if [ "$TIME1" -gt "$TIME2" ]; then - DIFF=$((TIME1 - TIME2)) - PERCENT=$(( (DIFF * 100) / TIME1 )) - echo "│ Diferença │ baseline │ -${DIFF}s (${PERCENT}% mais rápido) │" -elif [ "$TIME2" -gt "$TIME1" ]; then - DIFF=$((TIME2 - TIME1)) - PERCENT=$(( (DIFF * 100) / TIME2 )) - echo "│ Diferença │ +${DIFF}s mais rápido │ baseline │" -else - echo "│ Diferença │ igual │ igual │" -fi - -echo "└───────────────────┴─────────────────┴─────────────────────────────┘" - -echo "" -echo "Nota: Este benchmark usou imagePullPolicy: Always no node '$TARGET_NODE'" -echo " que não tinha as imagens em cache, forçando pull real." -echo "" -echo "O benefício do eStargz (lazy pulling) é mais significativo em:" -echo " - Imagens maiores (1GB+)" -echo " - Scale-out events (novos nodes)" -echo " - Cold starts" -echo "" -echo "Para limpar: kubectl delete namespace $NAMESPACE" diff --git a/aula-13/benchmarks/benchmark-postgresql.sh b/aula-13/benchmarks/benchmark-postgresql.sh deleted file mode 100755 index 2691e2e..0000000 --- a/aula-13/benchmarks/benchmark-postgresql.sh +++ /dev/null @@ -1,170 +0,0 @@ -#!/bin/bash -# ============================================================================= -# Benchmark: eStargz vs Traditional Image Pull -# ============================================================================= -# -# Compara tempo de startup entre: -# - postgres:17-alpine (gzip tradicional) -# - gitea.kube.quest/factory/postgresql:17 (eStargz) -# -# Este script usa timestamps dos eventos do Kubernetes para medir: -# - Tempo de pull (Pulling -> Pulled) -# - Tempo total (Scheduled -> Started) -# -# ============================================================================= - -set -e - -NAMESPACE="benchmark-test" -ESTARGZ_IMAGE="gitea.kube.quest/factory/postgresql:17" -TRADITIONAL_IMAGE="postgres:17-alpine" - -echo "========================================================================" -echo "Benchmark: eStargz vs Traditional Image Pull" -echo "========================================================================" -echo "" -echo "Comparando:" -echo " Tradicional: $TRADITIONAL_IMAGE" -echo " eStargz: $ESTARGZ_IMAGE" -echo "" - -# Verificar cluster -echo "[1/6] Verificando cluster..." -kubectl cluster-info >/dev/null || { echo "ERRO: Cluster inacessível"; exit 1; } -echo " Cluster OK" - -# Limpar ambiente anterior -echo "[2/6] Limpando ambiente anterior..." -kubectl delete namespace $NAMESPACE --ignore-not-found=true --wait=true 2>/dev/null || true -echo " Ambiente limpo" - -# Criar namespace -echo "[3/6] Criando namespace de teste..." -kubectl create namespace $NAMESPACE -kubectl create secret docker-registry gitea-registry \ - --docker-server=gitea.kube.quest \ - --docker-username=root \ - --docker-password="${GITEA_TOKEN:-glpat-dummy}" \ - -n $NAMESPACE 2>/dev/null || true -echo " Namespace criado" - -# Teste 1: Imagem tradicional -echo "" -echo "========================================================================" -echo "[4/6] TESTE 1: Imagem Tradicional (gzip)" -echo "========================================================================" -T1_START=$(date +%s) - -kubectl run pg-traditional --image=$TRADITIONAL_IMAGE --restart=Never \ - --env=POSTGRES_PASSWORD=benchmarktest \ - -n $NAMESPACE 2>&1 | grep -v "Warning:" - -kubectl wait --for=condition=Ready pod/pg-traditional -n $NAMESPACE --timeout=180s - -T1_END=$(date +%s) -TIME1=$((T1_END - T1_START)) -echo "Tempo total: ${TIME1}s" - -# Teste 2: Imagem eStargz -echo "" -echo "========================================================================" -echo "[5/6] TESTE 2: Imagem eStargz (lazy pulling)" -echo "========================================================================" -T2_START=$(date +%s) - -kubectl run pg-estargz --image=$ESTARGZ_IMAGE --restart=Never \ - --env=POSTGRES_PASSWORD=benchmarktest \ - --overrides='{"spec":{"imagePullSecrets":[{"name":"gitea-registry"}]}}' \ - -n $NAMESPACE 2>&1 | grep -v "Warning:" - -kubectl wait --for=condition=Ready pod/pg-estargz -n $NAMESPACE --timeout=180s - -T2_END=$(date +%s) -TIME2=$((T2_END - T2_START)) -echo "Tempo total: ${TIME2}s" - -# Resultados -echo "" -echo "========================================================================" -echo "[6/6] RESULTADOS" -echo "========================================================================" -echo "" - -# Status dos pods -echo "Status dos Pods:" -kubectl get pods -n $NAMESPACE -o wide -echo "" - -# Eventos completos -echo "Todos os Eventos (ordenados por tempo):" -kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' \ - -o custom-columns='TIMESTAMP:.lastTimestamp,REASON:.reason,POD:.involvedObject.name,MESSAGE:.message' -echo "" - -# Verificar se houve pull real ou cache hit -echo "Análise de Pull:" -TRAD_PULL=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=pg-traditional,reason=Pulled -o jsonpath='{.items[0].message}' 2>/dev/null) -ESTARGZ_PULL=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=pg-estargz,reason=Pulled -o jsonpath='{.items[0].message}' 2>/dev/null) - -echo " Tradicional: $TRAD_PULL" -echo " eStargz: $ESTARGZ_PULL" -echo "" - -# Tabela de resultados -echo "┌─────────────────────────────────────────────────────────────────┐" -echo "│ RESULTADOS DO BENCHMARK │" -echo "├───────────────────┬─────────────────┬─────────────────────────────┤" -echo "│ Métrica │ Tradicional │ eStargz │" -echo "├───────────────────┼─────────────────┼─────────────────────────────┤" -printf "│ Tempo até Ready │ %12ss │ %12ss │\n" "$TIME1" "$TIME2" -echo "├───────────────────┼─────────────────┼─────────────────────────────┤" - -if [ "$TIME1" -gt 0 ] && [ "$TIME2" -gt 0 ]; then - if [ "$TIME1" -gt "$TIME2" ]; then - DIFF=$((TIME1 - TIME2)) - echo "│ Diferença │ baseline │ -${DIFF}s mais rápido │" - elif [ "$TIME2" -gt "$TIME1" ]; then - DIFF=$((TIME2 - TIME1)) - echo "│ Diferença │ -${DIFF}s mais rápido │ baseline │" - else - echo "│ Diferença │ igual │ igual │" - fi -fi - -echo "└───────────────────┴─────────────────┴─────────────────────────────┘" - -# Verificar cache hit -if echo "$TRAD_PULL" | grep -q "already present"; then - TRAD_CACHED="SIM" -else - TRAD_CACHED="NAO" -fi - -if echo "$ESTARGZ_PULL" | grep -q "already present"; then - ESTARGZ_CACHED="SIM" -else - ESTARGZ_CACHED="NAO" -fi - -echo "" -echo "Cache Status:" -echo " Tradicional em cache: $TRAD_CACHED" -echo " eStargz em cache: $ESTARGZ_CACHED" - -if [ "$TRAD_CACHED" = "SIM" ] || [ "$ESTARGZ_CACHED" = "SIM" ]; then - echo "" - echo "AVISO: Imagens em cache - benchmark não reflete tempo real de pull!" - echo "" - echo "Para benchmark preciso, limpe o cache dos worker nodes com:" - echo "" - echo " # Via talosctl (para cada worker node):" - echo " export TALOSCONFIG=/private/data/app/workshop/aula-08/talosconfig" - echo " WORKER_IP=46.224.192.153 # IP do worker" - echo " talosctl -n \$WORKER_IP service restart containerd" - echo "" - echo " # OU escale um novo worker sem cache" -fi - -echo "" -echo "Namespace de teste mantido. Para limpar:" -echo " kubectl delete namespace $NAMESPACE" diff --git a/aula-13/benchmarks/benchmark-pull-only.sh b/aula-13/benchmarks/benchmark-pull-only.sh deleted file mode 100755 index 570cee3..0000000 --- a/aula-13/benchmarks/benchmark-pull-only.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/bash -# ============================================================================= -# Benchmark de PULL: eStargz vs Traditional -# ============================================================================= -# -# Mede apenas o tempo de PULL das imagens (não espera container ficar Ready) -# Executa em node limpo sem cache. -# -# ============================================================================= - -set -e - -NAMESPACE="benchmark-pull" -ESTARGZ_IMAGE="gitea.kube.quest/factory/postgresql:17" -TRADITIONAL_IMAGE="postgres:17-alpine" -TARGET_NODE="worker-pool-6bea48339a15ab6e" # Node 128.140.11.113 - sem cache - -echo "========================================================================" -echo "Benchmark de PULL: eStargz vs Traditional" -echo "========================================================================" -echo "" -echo "Target node: $TARGET_NODE (sem cache)" -echo "" - -# Setup -kubectl delete namespace $NAMESPACE --ignore-not-found=true --wait=true 2>/dev/null || true -kubectl create namespace $NAMESPACE -kubectl create secret docker-registry gitea-registry \ - --docker-server=gitea.kube.quest \ - --docker-username=root \ - --docker-password="${GITEA_TOKEN:-glpat-dummy}" \ - -n $NAMESPACE 2>/dev/null || true - -echo "" -echo "========================================================================" -echo "TESTE 1: Pull de Imagem Tradicional (gzip)" -echo "========================================================================" - -cat </dev/null) - if [ -n "$PULLED" ]; then - echo "RESULTADO: $PULLED" - break - fi - PULLING=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=test-traditional,reason=Pulling -o jsonpath='{.items[0].message}' 2>/dev/null) - if [ -n "$PULLING" ]; then - echo -n "." - fi - sleep 1 -done - -echo "" -echo "========================================================================" -echo "TESTE 2: Pull de Imagem eStargz (lazy pulling)" -echo "========================================================================" - -cat </dev/null) - if [ -n "$PULLED" ]; then - echo "RESULTADO: $PULLED" - break - fi - PULLING=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=test-estargz,reason=Pulling -o jsonpath='{.items[0].message}' 2>/dev/null) - if [ -n "$PULLING" ]; then - echo -n "." - fi - sleep 1 -done - -echo "" -echo "========================================================================" -echo "RESUMO" -echo "========================================================================" -echo "" -echo "Todos os eventos de pull:" -kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' \ - -o custom-columns='TIME:.lastTimestamp,REASON:.reason,POD:.involvedObject.name,MESSAGE:.message' \ - | grep -E "Pull|pull" - -echo "" -echo "Status dos pods:" -kubectl get pods -n $NAMESPACE -o wide - -echo "" -echo "Para limpar: kubectl delete namespace $NAMESPACE" diff --git a/aula-13/benchmarks/benchmark-toolbox.sh b/aula-13/benchmarks/benchmark-toolbox.sh deleted file mode 100755 index 02145b0..0000000 --- a/aula-13/benchmarks/benchmark-toolbox.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/bash -# ============================================================================= -# Benchmark: DevOps Toolbox - eStargz vs GZIP -# ============================================================================= -# Compara tempo de startup usando apenas UMA ferramenta (terraform version) -# para demonstrar o benefício do lazy pulling em imagens grandes. -# ============================================================================= - -set -e - -NAMESPACE="benchmark-toolbox" -REGISTRY="gitea.kube.quest" -IMAGE_NAME="factory/devops-toolbox" - -# Cores -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } -log_ok() { echo -e "${GREEN}[OK]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -cleanup() { - log_info "Limpando recursos..." - kubectl delete namespace $NAMESPACE --ignore-not-found --wait=false 2>/dev/null || true -} - -measure_startup() { - local name=$1 - local image=$2 - local tag=$3 - - log_info "Testando $name ($tag)..." - - # Criar pod - cat </dev/null || true - kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/$name -n $NAMESPACE --timeout=300s 2>/dev/null || true - - local end_time=$(date +%s.%N) - local duration=$(echo "$end_time - $start_time" | bc) - - echo "$duration" -} - -main() { - echo "" - echo "==========================================" - echo " Benchmark: DevOps Toolbox" - echo " eStargz vs GZIP" - echo "==========================================" - echo "" - - # Verificar se imagens existem - log_info "Verificando imagens no registry..." - - # Limpar namespace anterior - cleanup - sleep 5 - - # Criar namespace - kubectl create namespace $NAMESPACE 2>/dev/null || true - - # Copiar secret do registry - if kubectl get secret gitea-registry -n gitea &>/dev/null; then - kubectl get secret gitea-registry -n gitea -o yaml | \ - sed "s/namespace: gitea/namespace: $NAMESPACE/" | \ - kubectl apply -f - 2>/dev/null || true - else - log_warn "Secret gitea-registry não encontrado. Usando imagens públicas." - fi - - echo "" - log_info "Iniciando benchmarks..." - echo "" - - # Teste 1: eStargz - log_info "=== Teste 1: eStargz (lazy pulling) ===" - time_estargz=$(measure_startup "toolbox-estargz" "$IMAGE_NAME" "latest") - log_ok "eStargz: ${time_estargz}s" - - # Limpar para teste justo - kubectl delete pod toolbox-estargz -n $NAMESPACE --wait=true 2>/dev/null || true - sleep 5 - - # Teste 2: GZIP - log_info "=== Teste 2: GZIP (tradicional) ===" - time_gzip=$(measure_startup "toolbox-gzip" "$IMAGE_NAME" "gzip") - log_ok "GZIP: ${time_gzip}s" - - # Resultados - echo "" - echo "==========================================" - echo " RESULTADOS" - echo "==========================================" - echo "" - echo "| Formato | Tempo |" - echo "|----------|----------|" - printf "| eStargz | %6.1fs |\n" "$time_estargz" - printf "| GZIP | %6.1fs |\n" "$time_gzip" - echo "" - - # Calcular diferença - if command -v bc &>/dev/null; then - speedup=$(echo "scale=1; $time_gzip / $time_estargz" | bc) - echo "Speedup eStargz: ${speedup}x mais rápido" - fi - - echo "" - log_info "Para ver logs: kubectl logs -n $NAMESPACE toolbox-estargz" - log_info "Para limpar: kubectl delete namespace $NAMESPACE" -} - -# Executar -main "$@" diff --git a/aula-13/benchmarks/benchmark.sh b/aula-13/benchmarks/benchmark.sh new file mode 100755 index 0000000..c435572 --- /dev/null +++ b/aula-13/benchmarks/benchmark.sh @@ -0,0 +1,485 @@ +#!/bin/bash +# ============================================================================= +# Benchmark: eStargz vs GZIP — Cold Pull Performance +# ============================================================================= +# +# Metodologia: +# +# 1. VERIFICAÇÃO — confirma stargz-snapshotter ativo no worker +# 2. PREPARAÇÃO — namespace, registry secret, identificação do worker +# 3. EXECUÇÃO — para cada imagem × formato × iteração: +# a) Remove imagem do cache do worker (via crictl em pod privilegiado) +# b) Cria pod pinado no worker +# c) Mede tempo de pull (evento Pulling→Pulled) e total (até Ready) +# d) Remove pod +# 4. RELATÓRIO — médias por imagem/formato, tabela comparativa +# +# Controles de validade: +# - Mesma imagem base, mesmo Dockerfile, só a compressão muda +# - Pod pinado no mesmo worker (elimina variação de rede entre nodes) +# - Evento "Pulled" checado para descartar cache hits silenciosos +# - Múltiplas iterações com média +# +# IMPORTANTE — Cache de imagens: +# O benchmark precisa de um worker node SEM as imagens em cache. +# Não é possível limpar cache de forma confiável no Talos (sem crictl/ctr). +# A forma mais segura é escalar um worker novo via cluster autoscaler: +# +# kubectl apply -f ../test-autoscaler.yaml # Força scale-up +# kubectl get nodes -w # Esperar novo node +# ./benchmark.sh # Rodar no node limpo +# +# O script detecta cache e avisa antes de rodar. +# +# Pré-requisitos: +# - kubectl configurado (KUBECONFIG) +# - Imagens preparadas com ./prepare-images.sh +# - stargz-snapshotter ativo nos workers (extensão Talos) +# - Worker node SEM cache das imagens de benchmark +# +# Uso: +# ./benchmark.sh # 3 iterações (padrão) +# BENCH_ITERATIONS=5 ./benchmark.sh # 5 iterações +# +# ============================================================================= + +set -euo pipefail + +# ───────────────────────────────────────────────────────────── +# Configuração +# ───────────────────────────────────────────────────────────── + +REGISTRY="${REGISTRY:-gitea.kube.quest}" +ORG="bench" +NAMESPACE="benchmark" +ITERATIONS="${BENCH_ITERATIONS:-3}" +TALOSCONFIG="${TALOSCONFIG:-$(cd "$(dirname "$0")/../.." && pwd)/aula-08/talosconfig}" + +WORKER_NODE="" +WORKER_IP="" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +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"; } + +# Timestamp em milissegundos (compatível com macOS + Linux) +ts_ms() { python3 -c "import time; print(int(time.time() * 1000))"; } + +# Converter timestamp ISO 8601 → epoch ms +iso_to_ms() { + python3 -c " +from datetime import datetime, timezone +s = '${1}'.rstrip('Z') +try: + dt = datetime.fromisoformat(s).replace(tzinfo=timezone.utc) +except: + dt = datetime.strptime(s, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc) +print(int(dt.timestamp() * 1000)) +" +} + +# ───────────────────────────────────────────────────────────── +# 1. PRÉ-FLIGHT +# ───────────────────────────────────────────────────────────── + +preflight() { + echo "" + echo -e "${CYAN}════════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Benchmark: eStargz vs GZIP — Cold Pull Performance${NC}" + echo -e "${CYAN}════════════════════════════════════════════════════════════${NC}" + echo "" + echo " Registry: ${REGISTRY}/${ORG}" + echo " Iterações: ${ITERATIONS}" + echo "" + + # Detectar worker + WORKER_NODE=$(kubectl get nodes \ + -l '!node-role.kubernetes.io/control-plane' \ + -o jsonpath='{.items[0].metadata.name}') + WORKER_IP=$(kubectl get node "$WORKER_NODE" \ + -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') + + if [[ -z "$WORKER_NODE" ]]; then + log_error "Nenhum worker node encontrado" + exit 1 + fi + log_info "Worker: ${WORKER_NODE} (${WORKER_IP})" + + # Verificar stargz-snapshotter + log_info "Verificando stargz-snapshotter..." + local snapshotter_ok=false + + if [[ -f "$TALOSCONFIG" ]]; then + local snap_conf + snap_conf=$(TALOSCONFIG="$TALOSCONFIG" talosctl -n "$WORKER_IP" \ + read /etc/cri/conf.d/10-stargz-snapshotter.part 2>/dev/null || echo "") + + if echo "$snap_conf" | grep -q "stargz"; then + snapshotter_ok=true + fi + + if [[ "$snapshotter_ok" == "false" ]]; then + local ext + ext=$(TALOSCONFIG="$TALOSCONFIG" talosctl -n "$WORKER_IP" \ + get extensionstatuses 2>/dev/null || echo "") + if echo "$ext" | grep -qi "stargz"; then + snapshotter_ok=true + fi + fi + fi + + if [[ "$snapshotter_ok" == "true" ]]; then + log_success "stargz-snapshotter ativo" + else + echo "" + log_warn "stargz-snapshotter NÃO detectado em ${WORKER_NODE}" + log_warn "Sem ele, imagens eStargz são tratadas como gzip — resultados serão idênticos." + echo "" + echo -n " Continuar mesmo assim? [s/N]: " + read -r choice + [[ "$choice" == "s" || "$choice" == "S" ]] || exit 1 + fi + + # Verificar imagens no registry + log_info "Verificando imagens no registry..." + for img in nginx node postgres; do + for fmt in gzip estargz; do + local ref="${REGISTRY}/${ORG}/${img}:${fmt}" + if ! docker manifest inspect "$ref" &>/dev/null; then + log_error "Imagem não encontrada: ${ref}" + log_error "Execute ./prepare-images.sh primeiro" + exit 1 + fi + done + done + log_success "Todas as imagens encontradas" + + # Verificar se o node tem cache (invalida resultados) + verify_clean_node +} + +# ───────────────────────────────────────────────────────────── +# 2. SETUP DO NAMESPACE +# ───────────────────────────────────────────────────────────── + +setup_namespace() { + log_info "Preparando namespace ${NAMESPACE}..." + + kubectl delete namespace "$NAMESPACE" --ignore-not-found --wait 2>/dev/null || true + kubectl create namespace "$NAMESPACE" + kubectl label namespace "$NAMESPACE" \ + pod-security.kubernetes.io/enforce=privileged --overwrite + + # Registry secret — usar credenciais do docker config + local docker_cfg="${HOME}/.docker/config.json" + if [[ -f "$docker_cfg" ]]; then + kubectl create secret generic bench-registry \ + --from-file=.dockerconfigjson="$docker_cfg" \ + --type=kubernetes.io/dockerconfigjson \ + -n "$NAMESPACE" 2>/dev/null || true + else + log_warn "~/.docker/config.json não encontrado — pull de imagens privadas pode falhar" + fi + + log_success "Namespace pronto" +} + +# ───────────────────────────────────────────────────────────── +# 3. LIMPEZA DE CACHE +# ───────────────────────────────────────────────────────────── + +verify_clean_node() { + # Verifica se o worker node tem as imagens de benchmark em cache. + # Se tiver, o benchmark não é válido — precisa de node fresh. + log_info "Verificando cache de imagens no worker..." + + local cached_count=0 + local cached_images="" + + # Usa talosctl para listar imagens no node + if [[ -f "$TALOSCONFIG" ]]; then + for img in nginx node postgres; do + for fmt in gzip estargz; do + local ref="${REGISTRY}/${ORG}/${img}:${fmt}" + if TALOSCONFIG="$TALOSCONFIG" talosctl -n "$WORKER_IP" \ + image list 2>/dev/null | grep -q "$ref"; then + cached_count=$((cached_count + 1)) + cached_images="${cached_images} ${ref}\n" + fi + done + done + fi + + if [[ "$cached_count" -gt 0 ]]; then + echo "" + log_warn "O worker ${WORKER_NODE} tem ${cached_count} imagens de benchmark em cache:" + echo -e "$cached_images" + log_warn "Para resultados válidos, use um worker SEM cache." + echo "" + echo " Opções:" + echo " 1. Escalar um worker novo (cluster autoscaler cria node limpo):" + echo " kubectl apply -f ../test-autoscaler.yaml" + echo " # Aguardar novo node ficar Ready" + echo " # Re-rodar benchmark (vai usar o novo node)" + echo "" + echo " 2. Continuar mesmo assim (resultados podem ter cache hits)" + echo "" + echo -n " Continuar com cache? [s/N]: " + read -r choice + [[ "$choice" == "s" || "$choice" == "S" ]] || exit 1 + else + log_success "Worker sem cache de benchmark — resultados serão válidos" + fi +} + +# ───────────────────────────────────────────────────────────── +# 4. EXECUTAR UM TESTE +# Saída: pull_ms total_ms cached(yes/no) +# ───────────────────────────────────────────────────────────── + +run_single_test() { + local image=$1 # nginx, node, postgres + local format=$2 # gzip, estargz + local run=$3 # número da iteração + local image_ref="${REGISTRY}/${ORG}/${image}:${format}" + local pod_name="b-${image}-${format}-${run}" + + # Configuração específica por imagem + local env_yaml="" + local cmd_yaml="" + case $image in + postgres) + env_yaml=" env: + - name: POSTGRES_PASSWORD + value: benchtest" + ;; + *) + # nginx e node precisam de um command para não sair imediatamente + cmd_yaml=" command: [\"sleep\", \"infinity\"]" + ;; + esac + + # Registrar tempo de parede + local t_start + t_start=$(ts_ms) + + # Criar pod + cat </dev/null 2>&1 +apiVersion: v1 +kind: Pod +metadata: + name: ${pod_name} +spec: + nodeName: ${WORKER_NODE} + restartPolicy: Never + terminationGracePeriodSeconds: 0 + imagePullSecrets: + - name: bench-registry + containers: + - name: app + image: ${image_ref} +${cmd_yaml} +${env_yaml} +EOF + + # Esperar Ready + kubectl wait --for=condition=Ready "pod/${pod_name}" \ + -n "$NAMESPACE" --timeout=300s >/dev/null 2>&1 + + local t_ready + t_ready=$(ts_ms) + local total_ms=$(( t_ready - t_start )) + + # Extrair tempo de pull dos eventos do Kubernetes + sleep 1 # dar tempo pro event ser registrado + + local pull_ms=-1 + local cached="no" + + local pull_msg + pull_msg=$(kubectl get events -n "$NAMESPACE" \ + --field-selector "involvedObject.name=${pod_name},reason=Pulled" \ + -o jsonpath='{.items[0].message}' 2>/dev/null || echo "") + + if echo "$pull_msg" | grep -qi "already present"; then + cached="yes" + pull_ms=0 + log_warn " ⚠ CACHE HIT — resultado inválido" >&2 + else + # Tentar extrair timestamps dos eventos + local ts_pulling ts_pulled + ts_pulling=$(kubectl get events -n "$NAMESPACE" \ + --field-selector "involvedObject.name=${pod_name},reason=Pulling" \ + -o jsonpath='{.items[0].firstTimestamp}' 2>/dev/null || echo "") + ts_pulled=$(kubectl get events -n "$NAMESPACE" \ + --field-selector "involvedObject.name=${pod_name},reason=Pulled" \ + -o jsonpath='{.items[0].firstTimestamp}' 2>/dev/null || echo "") + + if [[ -n "$ts_pulling" && -n "$ts_pulled" ]]; then + local ms_start ms_end + ms_start=$(iso_to_ms "$ts_pulling") + ms_end=$(iso_to_ms "$ts_pulled") + pull_ms=$(( ms_end - ms_start )) + fi + fi + + # Limpar pod + kubectl delete pod "$pod_name" -n "$NAMESPACE" \ + --grace-period=0 --force >/dev/null 2>&1 || true + + # Esperar pod ser removido antes da próxima iteração + kubectl wait --for=delete "pod/${pod_name}" \ + -n "$NAMESPACE" --timeout=30s >/dev/null 2>&1 || true + + echo "${pull_ms} ${total_ms} ${cached}" +} + +# ───────────────────────────────────────────────────────────── +# 5. RELATÓRIO (lê resultados do RESULTS_DIR) +# ───────────────────────────────────────────────────────────── + +# Helpers para ler/escrever resultados em arquivos (compatível com bash 3.2) +rset() { echo "$2" > "${RESULTS_DIR}/$1"; } +rget() { cat "${RESULTS_DIR}/$1" 2>/dev/null || echo "${2:-0}"; } +radd() { local cur; cur=$(rget "$1" 0); local val="${2:-0}"; rset "$1" $(( cur + val )); } + +print_report() { + echo "" + echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════${NC}" + echo -e "${BOLD} RESULTADOS — eStargz vs GZIP Cold Pull${NC}" + echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════${NC}" + echo "" + echo " Worker: ${WORKER_NODE}" + echo " Iterações: ${ITERATIONS}" + echo "" + + printf " %-12s │ %-8s │ %10s │ %10s │ %10s │ %7s\n" \ + "IMAGEM" "FORMATO" "PULL (ms)" "TOTAL (ms)" "PULL (s)" "CACHE?" + echo " ─────────────┼──────────┼────────────┼────────────┼────────────┼────────" + + for img in nginx node postgres; do + for fmt in gzip estargz; do + local pull_avg; pull_avg=$(rget "${img}_${fmt}_pull_avg" "?") + local total_avg; total_avg=$(rget "${img}_${fmt}_total_avg" "?") + local cache_hits; cache_hits=$(rget "${img}_${fmt}_cache" 0) + local pull_sec="?" + + if [[ "$pull_avg" != "?" ]] && [[ "$pull_avg" -ge 0 ]] 2>/dev/null; then + pull_sec=$(python3 -c "print(f'{${pull_avg}/1000:.1f}')") + fi + + printf " %-12s │ %-8s │ %10s │ %10s │ %10s │ %d/%d\n" \ + "$img" "$fmt" "$pull_avg" "$total_avg" "$pull_sec" \ + "$cache_hits" "$ITERATIONS" + done + echo " ─────────────┼──────────┼────────────┼────────────┼────────────┼────────" + done + + echo "" + + # Comparação resumida + echo -e " ${BOLD}Comparação (tempo médio de pull):${NC}" + echo "" + for img in nginx node postgres; do + local gzip_pull; gzip_pull=$(rget "${img}_gzip_pull_avg" 0) + local estargz_pull; estargz_pull=$(rget "${img}_estargz_pull_avg" 0) + + if [[ "$gzip_pull" != "?" ]] && [[ "$estargz_pull" != "?" ]] && [[ "$gzip_pull" -gt 0 ]] && [[ "$estargz_pull" -gt 0 ]]; then + local diff_ms=$(( gzip_pull - estargz_pull )) + local ratio + ratio=$(python3 -c "print(f'{${gzip_pull}/${estargz_pull}:.1f}')" 2>/dev/null || echo "?") + + if [[ "$diff_ms" -gt 0 ]]; then + echo -e " ${img}: eStargz ${GREEN}${ratio}x mais rápido${NC} (${diff_ms}ms menos)" + elif [[ "$diff_ms" -lt 0 ]]; then + local abs_diff=$(( -diff_ms )) + echo -e " ${img}: eStargz ${RED}${abs_diff}ms mais lento${NC}" + else + echo " ${img}: resultados idênticos" + fi + else + echo " ${img}: dados insuficientes para comparação" + fi + done + + echo "" + echo -e "${CYAN}════════════════════════════════════════════════════════════════════════════${NC}" + echo "" + echo " Notas:" + echo " - PULL (ms): tempo entre eventos Pulling→Pulled (resolução ~1s)" + echo " - TOTAL (ms): tempo de parede até pod Ready (inclui pull + startup)" + echo " - CACHE? mostra quantos testes tiveram cache hit (deveria ser 0/N)" + echo " - Se CACHE? > 0, a limpeza de cache falhou e o resultado é inválido" + echo "" +} + +# ───────────────────────────────────────────────────────────── +# MAIN +# ───────────────────────────────────────────────────────────── + +# Diretório temporário para acumular resultados (substitui declare -A) +RESULTS_DIR=$(mktemp -d) +trap "rm -rf $RESULTS_DIR" EXIT + +preflight +setup_namespace + +for img in nginx node postgres; do + echo "" + echo -e "${CYAN}────────────────────────────────────────────────${NC}" + echo -e " ${BOLD}${img}${NC}" + echo -e "${CYAN}────────────────────────────────────────────────${NC}" + + for fmt in gzip estargz; do + key="${img}_${fmt}" + rset "${key}_pull_sum" 0 + rset "${key}_total_sum" 0 + rset "${key}_count" 0 + rset "${key}_cache" 0 + + for run in $(seq 1 "$ITERATIONS"); do + echo -n " ${fmt} #${run}/${ITERATIONS} ... " + + result=$(run_single_test "$img" "$fmt" "$run") + pull_ms=$(echo "$result" | awk '{print $1}') + total_ms=$(echo "$result" | awk '{print $2}') + cached=$(echo "$result" | awk '{print $3}') + + if [[ "$cached" == "yes" ]]; then + radd "${key}_cache" 1 + echo -e "${YELLOW}cache hit${NC} (total: ${total_ms}ms)" + else + radd "${key}_pull_sum" "$pull_ms" + radd "${key}_total_sum" "$total_ms" + radd "${key}_count" 1 + echo "pull: ${pull_ms}ms total: ${total_ms}ms" + fi + done + + # Calcular médias + count=$(rget "${key}_count" 0) + if [[ "$count" -gt 0 ]]; then + rset "${key}_pull_avg" $(( $(rget "${key}_pull_sum") / count )) + rset "${key}_total_avg" $(( $(rget "${key}_total_sum") / count )) + else + rset "${key}_pull_avg" "?" + rset "${key}_total_avg" "?" + fi + done +done + +print_report + +# Limpeza +log_info "Limpando namespace de benchmark..." +kubectl delete namespace "$NAMESPACE" --ignore-not-found >/dev/null 2>&1 || true +log_success "Benchmark concluído" diff --git a/aula-13/benchmarks/prepare-images.sh b/aula-13/benchmarks/prepare-images.sh new file mode 100755 index 0000000..fc8b97c --- /dev/null +++ b/aula-13/benchmarks/prepare-images.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# ============================================================================= +# Prepara imagens de benchmark em formato GZIP e eStargz +# ============================================================================= +# +# Constrói a MESMA imagem em dois formatos de compressão e envia ao registry. +# Isso garante que a única variável no benchmark é o formato de compressão. +# +# Pré-requisitos: +# - docker com buildx +# - Acesso ao registry (docker login) +# - Organização "bench" criada no Gitea +# +# Uso: +# export REGISTRY=gitea.kube.quest +# ./prepare-images.sh +# +# ============================================================================= + +set -euo pipefail + +REGISTRY="${REGISTRY:-gitea.kube.quest}" +ORG="bench" +PLATFORM="linux/arm64" # Hetzner CAX = ARM64 + +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_error() { echo -e "${RED}[ERRO]${NC} $1"; } + +echo "" +echo -e "${CYAN}════════════════════════════════════════════════════${NC}" +echo -e "${CYAN} Preparando imagens de benchmark (gzip + estargz)${NC}" +echo -e "${CYAN}════════════════════════════════════════════════════${NC}" +echo "" +echo " Registry: ${REGISTRY}" +echo " Org: ${ORG}" +echo " Platform: ${PLATFORM}" +echo "" + +# ───────────────────────────────────────────────────────────── +# Pré-requisitos +# ───────────────────────────────────────────────────────────── + +if ! command -v docker &>/dev/null; then + log_error "docker não encontrado" + exit 1 +fi + +log_info "Login no registry..." +docker login "$REGISTRY" || { log_error "Falha no login"; exit 1; } + +# Criar builder multi-platform se necessário +BUILDER_NAME="bench-builder" +if ! docker buildx inspect "$BUILDER_NAME" &>/dev/null; then + log_info "Criando builder multi-platform..." + docker buildx create --name "$BUILDER_NAME" --driver docker-container +fi +docker buildx use "$BUILDER_NAME" + +# ───────────────────────────────────────────────────────────── +# Imagens a construir +# ───────────────────────────────────────────────────────────── + +# Cada entrada: nome_local:imagem_base:descrição +IMAGES=( + "nginx:nginx:1.27-alpine:Web server (~45MB comprimido)" + "node:node:22-alpine:Runtime Node.js (~130MB comprimido)" + "postgres:postgres:17-alpine:Banco de dados (~100MB comprimido)" +) + +TMPDIR=$(mktemp -d) +trap "rm -rf $TMPDIR" EXIT + +for entry in "${IMAGES[@]}"; do + IFS=':' read -r name base_image base_tag description <<< "$entry" + base="${base_image}:${base_tag}" + + echo "" + echo -e "${CYAN}────────────────────────────────────────────${NC}" + echo -e " ${name} — ${description}" + echo -e " Base: ${base}" + echo -e "${CYAN}────────────────────────────────────────────${NC}" + + # Dockerfile idêntico para ambos os formatos + cat > "${TMPDIR}/Dockerfile" <