fix(aula-13): reescrever benchmarks eStargz vs GZIP

Benchmarks antigos eram falhos: node hardcoded, imagens diferentes,
sem verificação de snapshotter, sem controle de cache, 1 iteração.

Novos scripts:
- prepare-images.sh: constrói mesma imagem em gzip e estargz
- benchmark.sh: múltiplas iterações, detecção de cache hits,
  verificação de snapshotter, 3 tipos de imagem (nginx/node/postgres)

Requer worker node sem cache (node fresh via cluster autoscaler).
This commit is contained in:
ArgoCD Setup
2026-03-14 07:48:51 -03:00
parent 7167e6ee11
commit f5cb6f0581
6 changed files with 612 additions and 625 deletions

View File

@@ -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 <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pg-traditional
namespace: $NAMESPACE
spec:
nodeName: $TARGET_NODE
restartPolicy: Never
containers:
- name: postgres
image: $TRADITIONAL_IMAGE
imagePullPolicy: Always
env:
- name: POSTGRES_PASSWORD
value: benchmarktest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
EOF
kubectl wait --for=condition=Ready pod/pg-traditional -n $NAMESPACE --timeout=300s
T1_END=$(date +%s)
TIME1=$((T1_END - T1_START))
echo "Finalizado em $(date)"
echo ">>> 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 <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: pg-estargz
namespace: $NAMESPACE
spec:
nodeName: $TARGET_NODE
restartPolicy: Never
imagePullSecrets:
- name: gitea-registry
containers:
- name: postgres
image: $ESTARGZ_IMAGE
imagePullPolicy: Always
env:
- name: POSTGRES_PASSWORD
value: benchmarktest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
EOF
kubectl wait --for=condition=Ready pod/pg-estargz -n $NAMESPACE --timeout=300s
T2_END=$(date +%s)
TIME2=$((T2_END - T2_START))
echo "Finalizado em $(date)"
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
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"

View File

@@ -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"

View File

@@ -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 <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-traditional
namespace: $NAMESPACE
spec:
nodeName: $TARGET_NODE
restartPolicy: Never
containers:
- name: postgres
image: $TRADITIONAL_IMAGE
imagePullPolicy: Always
command: ["sleep", "infinity"]
env:
- name: POSTGRES_PASSWORD
value: test
EOF
echo "Aguardando pull..."
sleep 2
while true; do
PULLED=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=test-traditional,reason=Pulled -o jsonpath='{.items[0].message}' 2>/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 <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-estargz
namespace: $NAMESPACE
spec:
nodeName: $TARGET_NODE
restartPolicy: Never
imagePullSecrets:
- name: gitea-registry
containers:
- name: postgres
image: $ESTARGZ_IMAGE
imagePullPolicy: Always
command: ["sleep", "infinity"]
env:
- name: POSTGRES_PASSWORD
value: test
EOF
echo "Aguardando pull..."
sleep 2
while true; do
PULLED=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=test-estargz,reason=Pulled -o jsonpath='{.items[0].message}' 2>/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"

View File

@@ -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 <<EOF | kubectl apply -f - -n $NAMESPACE
apiVersion: v1
kind: Pod
metadata:
name: $name
spec:
containers:
- name: toolbox
image: ${REGISTRY}/${IMAGE_NAME}:${tag}
command: ["terraform", "version"]
imagePullPolicy: Always
restartPolicy: Never
imagePullSecrets:
- name: gitea-registry
EOF
# Medir tempo até completar
local start_time=$(date +%s.%N)
# Aguardar pod completar ou falhar
kubectl wait --for=condition=Ready pod/$name -n $NAMESPACE --timeout=300s 2>/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 "$@"

485
aula-13/benchmarks/benchmark.sh Executable file
View File

@@ -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 <<EOF | kubectl apply -f - -n "$NAMESPACE" >/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"

View File

@@ -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" <<EOF
FROM ${base}
EOF
# ── GZIP ──
log_info "Building ${name}:gzip ..."
docker buildx build \
--platform "$PLATFORM" \
--output "type=image,name=${REGISTRY}/${ORG}/${name}:gzip,push=true,compression=gzip" \
-f "${TMPDIR}/Dockerfile" \
"${TMPDIR}" --quiet
log_success "${REGISTRY}/${ORG}/${name}:gzip"
# ── eStargz ──
log_info "Building ${name}:estargz ..."
docker buildx build \
--platform "$PLATFORM" \
--output "type=image,name=${REGISTRY}/${ORG}/${name}:estargz,push=true,compression=estargz,force-compression=true,oci-mediatypes=true" \
-f "${TMPDIR}/Dockerfile" \
"${TMPDIR}" --quiet
log_success "${REGISTRY}/${ORG}/${name}:estargz"
done
echo ""
echo -e "${CYAN}════════════════════════════════════════════════════${NC}"
echo -e "${GREEN} Imagens prontas para benchmark${NC}"
echo -e "${CYAN}════════════════════════════════════════════════════${NC}"
echo ""
for entry in "${IMAGES[@]}"; do
IFS=':' read -r name _ _ _ <<< "$entry"
echo " ${REGISTRY}/${ORG}/${name}:gzip"
echo " ${REGISTRY}/${ORG}/${name}:estargz"
done
echo ""
echo "Próximo passo: ./benchmark.sh"
echo ""