refactor: migrar GitLab → Gitea (aulas 10, 11, 13)

- Aula 10: Gitea + Registry + Actions + Runner (substituiu GitLab)
  - gitea-values.yaml: PostgreSQL standalone, Valkey standalone, ~800Mi RAM
  - setup.sh/cleanup.sh: namespace gitea, Helm gitea-charts/gitea + actions
  - README.md: documentação completa com de→para (GitLab/Harbor/Tekton vs Gitea)

- Aula 11: ArgoCD (GitOps) — removido GitLab Runner (runner vive na aula-10)
  - setup.sh: só ArgoCD, integração SSH com Gitea
  - node-bugado/.gitea/workflows/ci.yml: pipeline convertida

- Aula 13: Container Factory — atualizado para Gitea
  - setup.sh/cleanup.sh: referências GitLab → Gitea
  - pipelines/postgresql/ci.yml: Gitea Actions workflow
  - README.md: conexão com act_runner explicada

- CLAUDE.md: tabela de aulas atualizada
This commit is contained in:
ArgoCD Setup
2026-03-14 01:44:30 -03:00
parent ff7af56c30
commit d380cd8585
35 changed files with 3374 additions and 1202 deletions

409
aula-13/README.md Normal file
View File

@@ -0,0 +1,409 @@
# Aula 13 - Container Factory + Lazy Pulling (eStargz)
Crie suas próprias imagens Docker e aprenda **quando** usar eStargz para lazy pulling.
## Conexão com as aulas anteriores
```
┌────────────────────────────────────────────────────────────────┐
│ Aula 10: Gitea │
│ Git server + Container Registry + Gitea Actions (habilitado) │
│ Chart: gitea-charts/gitea │ Chart: gitea-charts/actions │
│ Pods: gitea, postgresql, │ Pod: act-runner + DinD │
│ valkey │ (executa os workflows) │
└────────────────────┬───────────────────────┬──────────────────┘
│ │
│ push código │ roda workflow
│ │ (.gitea/workflows/)
▼ ▼
┌────────────────────────────────────────────────────────────────┐
│ Aula 13: Container Factory │
│ │
│ 1. git push → Gitea recebe o código │
│ 2. act_runner detecta .gitea/workflows/ci.yml │
│ 3. Runner executa: docker buildx build --compression=estargz │
│ 4. Runner faz push da imagem pro Gitea Container Registry │
│ 5. kubectl apply → Kubernetes puxa do registry │
└────────────────────────────────────────────────────────────────┘
```
O **act_runner** (Gitea Actions Runner) é a peça que conecta o `git push` ao build da imagem.
Ele foi instalado na aula-10 via `gitea-charts/actions` — um StatefulSet com dois containers:
| Container | Função |
|-----------|--------|
| `act-runner` | Escuta o Gitea por jobs, executa workflows (sintaxe GitHub Actions) |
| `dind` (Docker-in-Docker) | Daemon Docker privilegiado que o runner usa para `docker build`, `docker push` |
Sem o runner, os arquivos `.gitea/workflows/*.yml` existem no repo mas nunca executam.
Nesta aula usamos o runner para **build automatizado de imagens eStargz** — o ciclo completo de Container Factory.
## Visão Geral
| Parte | Tema | Valor |
|-------|------|-------|
| **1** | Container Factory | Prático - criar imagens independentes |
| **2** | eStargz e Lazy Pulling | Educacional - quando usar e quando NÃO usar |
| **3** | Alternativas para Cold Start | Prático - soluções para KEDA/auto-scaling |
---
# Parte 1: Container Factory
## Por que criar suas próprias imagens?
### O Problema Bitnami
A partir de **28 de agosto de 2025**, a Broadcom descontinuou imagens gratuitas:
- Imagens movidas para repositório **legacy** (sem atualizações de segurança)
- Tags estáveis (`postgresql:13.7.0`) não disponíveis no tier gratuito
- Imagens atualizadas só via **Bitnami Secure** (~$72k/ano)
Referência: [Bitnami Issue #83267](https://github.com/bitnami/containers/issues/83267)
### A Solução: Container Factory
Criar imagens próprias baseadas nas imagens oficiais:
- Gratuitas e mantidas pela comunidade
- Menores e mais seguras
- Totalmente customizáveis
## Estrutura do Projeto
```
aula-13/
├── README.md
├── setup.sh
├── cleanup.sh
├── images/
│ ├── postgresql/ # Exemplo principal
│ │ ├── Dockerfile
│ │ └── postgresql.conf
│ └── devops-toolbox/ # Demonstração eStargz
│ ├── Dockerfile
│ └── entrypoint.sh
├── pipelines/
│ └── postgresql/
│ ├── ci.yml # Gitea Actions workflow
│ └── .gitlab-ci.yml # (legado - referência)
├── k8s/
│ ├── postgresql/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── pvc.yaml
│ └── prepull-daemonset.yaml # Alternativa para cold start
└── benchmarks/
├── benchmark-postgresql.sh
└── benchmark-toolbox.sh
```
## Instalação
### 1. Executar Setup
```bash
cd aula-13
./setup.sh
```
### 2. Criar Projeto no Gitea
1. Criar org `factory` em `https://gitea.kube.quest/-/admin/orgs`
2. Criar repositório `postgresql` na org
3. Push dos arquivos:
```bash
git clone git@gitea.kube.quest:factory/postgresql.git
cd postgresql
cp /path/to/aula-13/images/postgresql/* .
mkdir -p .gitea/workflows
cp /path/to/aula-13/pipelines/postgresql/ci.yml .gitea/workflows/ci.yml
git add . && git commit -m "Initial commit" && git push
```
### 3. Deploy
```bash
kubectl apply -f k8s/postgresql/ -n factory
kubectl get pods -n factory -w
```
---
# Parte 2: eStargz e Lazy Pulling
## O que é eStargz?
**eStargz** (Externally Seekable Stargz) permite **lazy pulling** - o container inicia ANTES de baixar a imagem completa:
```
Imagem Tradicional:
Download 100% (30s) → Extract (10s) → Start (1s) = 41s total
Imagem eStargz:
Download 5% (1s) → Start (1s) → Download resto em background = 2s para iniciar
```
## Quando eStargz FUNCIONA (casos reais)
| Empresa | Melhoria | Cenário |
|---------|----------|---------|
| **CERN** | 13x mais rápido | Pipelines de análise nuclear |
| **BuildBuddy** | 10x redução latência | Executor images |
| **AWS (SOCI)** | 10-15x melhoria | ML containers |
**Condições para sucesso:**
- Imagem > 250MB (comprimida)
- < 10% dos dados necessários no boot
- Container que NÃO precisa de todos os arquivos para iniciar
## Quando eStargz NÃO ajuda
### Aplicações que NÃO se beneficiam:
| Aplicação | Tamanho | Por que NÃO funciona |
|-----------|---------|---------------------|
| PostgreSQL | ~100MB | Database precisa de 100% dos arquivos |
| MongoDB | ~500MB | Database precisa de 100% dos arquivos |
| CockroachDB | ~400MB | Database precisa de 100% dos arquivos |
| n8n | ~500MB | Carrega TODAS as 400+ integrações no boot |
| Laravel Octane | ~400MB | Precarrega TODA a aplicação na RAM |
### Por que não funcionou nos testes?
Benchmark com PostgreSQL mostrou **performance idêntica** entre eStargz e GZIP:
| Imagem | Tempo (cold start) |
|--------|-------------------|
| PostgreSQL eStargz | ~10s |
| PostgreSQL GZIP | ~10s |
**Razões:**
1. Imagem pequena (~100MB) - overhead do lazy pull > benefício
2. PostgreSQL precisa de TODOS os arquivos para iniciar
3. Não há arquivos "opcionais" para lazy load
## Matriz de Decisão
| Cenário | Usar eStargz? | Motivo |
|---------|---------------|--------|
| Imagem < 250MB | ❌ **Não** | Overhead > benefício |
| Database (PostgreSQL, MySQL, MongoDB) | ❌ **Não** | Precisa de ~100% dos arquivos |
| Apps que precarregam (Laravel Octane, n8n) | ❌ **Não** | Carrega tudo no boot |
| Imagem > 1GB com uso parcial | ✅ **Sim** | Lazy pulling efetivo |
| ML/AI com múltiplos modelos | ✅ **Sim** | Só baixa modelo usado |
| DevOps toolbox (terraform, kubectl) | ✅ **Sim** | Só baixa ferramenta usada |
| Serverless/FaaS | ✅ **Sim** | Cold start crítico |
## Demonstração: DevOps Toolbox (eStargz vantajoso)
Para demonstrar quando eStargz **realmente funciona**, criamos uma imagem grande com uso parcial:
```dockerfile
# images/devops-toolbox/Dockerfile
FROM alpine:3.21
# Camada 1: Base (~50MB)
RUN apk add --no-cache bash curl wget jq git
# Camada 2: Terraform (~100MB)
RUN wget -q https://releases.hashicorp.com/terraform/1.9.0/terraform_1.9.0_linux_arm64.zip && \
unzip terraform_*.zip && mv terraform /usr/local/bin/ && rm terraform_*.zip
# Camada 3: Kubectl + Helm (~150MB)
RUN curl -sLO "https://dl.k8s.io/release/v1.31.0/bin/linux/arm64/kubectl" && \
chmod +x kubectl && mv kubectl /usr/local/bin/
# Camada 4: AWS CLI (~200MB)
RUN apk add --no-cache aws-cli
# Camada 5: Ansible (~150MB)
RUN apk add --no-cache python3 py3-pip && \
pip3 install ansible --break-system-packages --quiet
# Total: ~650MB - mas você só usa UMA ferramenta por vez!
```
**Resultado esperado:**
- GZIP: Baixa 650MB inteiro → ~60s
- eStargz: Baixa só camada necessária (~100MB) → ~10s
- **Melhoria: 6x mais rápido**
---
# Parte 3: Alternativas para Cold Start
Se suas aplicações são databases ou apps que precarregam, use estas alternativas:
## Opção 1: Pre-pull DaemonSet (Recomendado)
Garante que imagens já estão em TODOS os nodes antes do KEDA escalar:
```yaml
# k8s/prepull-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: image-prepuller
namespace: kube-system
spec:
selector:
matchLabels:
app: image-prepuller
template:
metadata:
labels:
app: image-prepuller
spec:
initContainers:
- name: prepull-postgres
image: gitea.kube.quest/factory/postgresql:17
command: ["echo", "Image pulled"]
imagePullPolicy: Always
- name: prepull-n8n
image: docker.n8n.io/n8nio/n8n:latest
command: ["echo", "Image pulled"]
imagePullPolicy: Always
containers:
- name: pause
image: gcr.io/google_containers/pause:3.2
imagePullSecrets:
- name: gitea-registry
```
**Resultado:** Quando KEDA escalar, imagens já estão em cache em todos os nodes.
## Opção 2: Over-provisioning
Manter `minReplicas > 0` para evitar cold starts:
```yaml
# ScaledObject com mínimo de 1 réplica
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
spec:
minReplicaCount: 1 # Sempre tem pelo menos 1 pod rodando
maxReplicaCount: 10
```
## Opção 3: Imagens menores
Reduzir tamanho da imagem para downloads mais rápidos:
| Base | Tamanho típico | Uso |
|------|----------------|-----|
| `alpine` | ~5MB | Apps Go, binários estáticos |
| `distroless` | ~20MB | Java, Node.js |
| `debian-slim` | ~80MB | Quando Alpine não funciona |
## Comparação de Soluções
| Solução | Cold Start | Complexidade | Custo |
|---------|------------|--------------|-------|
| Pre-pull DaemonSet | ⭐⭐⭐⭐⭐ | Baixa | Nenhum |
| Over-provisioning | ⭐⭐⭐⭐ | Baixa | $$ (pods sempre rodando) |
| Imagens menores | ⭐⭐⭐ | Média | Nenhum |
| eStargz | ⭐⭐ (só imagens grandes) | Alta | Nenhum |
---
# Apêndice: Configuração Técnica
## Pipeline CI com eStargz (Gitea Actions)
```yaml
# .gitea/workflows/ci.yml
name: Build PostgreSQL
on:
push:
branches: [main]
env:
REGISTRY: gitea.kube.quest
IMAGE_NAME: factory/postgresql
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ gitea.actor }} --password-stdin
- run: |
docker buildx build \
--output type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
.
```
| Parâmetro | Descrição |
|-----------|-----------|
| `compression=estargz` | Formato para lazy pulling |
| `force-compression=true` | Recomprime camadas da base |
| `oci-mediatypes=true` | Media types OCI (requerido) |
## Verificar stargz-snapshotter no Talos
```bash
# Extensão instalada
talosctl get extensionstatuses | grep stargz
# Configuração
talosctl read /etc/cri/conf.d/10-stargz-snapshotter.part
# Output: snapshotter = "stargz"
```
## Testar PostgreSQL
```bash
# Obter senha
PGPASSWORD=$(kubectl get secret postgresql-secret -n factory -o jsonpath='{.data.password}' | base64 -d)
# Conectar
kubectl run pg-client --rm -it --restart=Never \
--image=postgres:17-alpine \
--env=PGPASSWORD=$PGPASSWORD \
-- psql -h postgresql.factory.svc.cluster.local -U postgres -d app
```
---
# Conclusão
## Resumo
| Tema | Aprendizado |
|------|-------------|
| **Container Factory** | Crie imagens próprias para independência de terceiros |
| **eStargz** | Só vale para imagens > 250MB com uso parcial |
| **Cold Start** | Para databases, use pre-pull DaemonSet |
## Regra de Ouro
```
┌─────────────────────────────────────────────────────────────────┐
│ eStargz funciona quando: │
│ - Imagem > 250MB │
│ - < 10% dos dados necessários no boot │
│ - Container NÃO precisa de todos os arquivos para iniciar │
│ │
│ Para databases e apps que precarregam: │
│ → Use Pre-pull DaemonSet │
└─────────────────────────────────────────────────────────────────┘
```
## Referências
- [CERN: Lazy Pulling](https://indico.cern.ch/event/1338689/) - 13x mais rápido
- [BuildBuddy: SOCI](https://www.buildbuddy.io/blog/image-streaming/) - 10x redução latência
- [AWS: SOCI Snapshotter](https://aws.amazon.com/blogs/containers/under-the-hood-lazy-loading-container-images-with-seekable-oci-and-aws-fargate/)
- [NTT Labs: eStargz](https://medium.com/nttlabs/startup-containers-in-lightning-speed-with-lazy-image-distribution-on-containerd-243d94522361)
- [stargz-snapshotter](https://github.com/containerd/stargz-snapshotter)
## Cleanup
```bash
./cleanup.sh
```

View File

@@ -0,0 +1,188 @@
#!/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="registry.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 gitlab-registry \
--docker-server=registry.kube.quest \
--docker-username=root \
--docker-password="${GITLAB_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: gitlab-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

@@ -0,0 +1,170 @@
#!/bin/bash
# =============================================================================
# Benchmark: eStargz vs Traditional Image Pull
# =============================================================================
#
# Compara tempo de startup entre:
# - postgres:17-alpine (gzip tradicional)
# - registry.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="registry.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 gitlab-registry \
--docker-server=registry.kube.quest \
--docker-username=root \
--docker-password="${GITLAB_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":"gitlab-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

@@ -0,0 +1,129 @@
#!/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="registry.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 gitlab-registry \
--docker-server=registry.kube.quest \
--docker-username=root \
--docker-password="${GITLAB_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: gitlab-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

@@ -0,0 +1,138 @@
#!/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="registry.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: gitlab-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 gitlab-registry -n gitlab &>/dev/null; then
kubectl get secret gitlab-registry -n gitlab -o yaml | \
sed "s/namespace: gitlab/namespace: $NAMESPACE/" | \
kubectl apply -f - 2>/dev/null || true
else
log_warn "Secret gitlab-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 "$@"

71
aula-13/cleanup.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/bin/bash
# =============================================================================
# Aula 13 - Cleanup
# =============================================================================
set -e
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_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Carregar configuração
if [[ -f "${SCRIPT_DIR}/.env" ]]; then
source "${SCRIPT_DIR}/.env"
fi
DEPLOY_NAMESPACE="${DEPLOY_NAMESPACE:-factory}"
echo ""
echo "=========================================="
echo " Removendo Container Factory"
echo "=========================================="
echo ""
# Remover recursos do PostgreSQL
log_info "Removendo PostgreSQL..."
kubectl delete -f "${SCRIPT_DIR}/k8s/postgresql/deployment.yaml" -n ${DEPLOY_NAMESPACE} 2>/dev/null || true
kubectl delete -f "${SCRIPT_DIR}/k8s/postgresql/service.yaml" -n ${DEPLOY_NAMESPACE} 2>/dev/null || true
kubectl delete -f "${SCRIPT_DIR}/k8s/postgresql/configmap.yaml" -n ${DEPLOY_NAMESPACE} 2>/dev/null || true
kubectl delete secret postgresql-secret -n ${DEPLOY_NAMESPACE} 2>/dev/null || true
# Perguntar sobre PVC (dados serão perdidos!)
echo ""
log_warn "O PVC contém os dados do PostgreSQL!"
read -p "Remover PVC (DADOS SERÃO PERDIDOS)? [y/N]: " REMOVE_PVC
if [[ "$REMOVE_PVC" =~ ^[Yy]$ ]]; then
kubectl delete -f "${SCRIPT_DIR}/k8s/postgresql/pvc.yaml" -n ${DEPLOY_NAMESPACE} 2>/dev/null || true
log_success "PVC removido"
else
log_info "PVC mantido"
fi
# Remover namespace (opcional)
echo ""
read -p "Remover namespace ${DEPLOY_NAMESPACE}? [y/N]: " REMOVE_NS
if [[ "$REMOVE_NS" =~ ^[Yy]$ ]]; then
kubectl delete namespace ${DEPLOY_NAMESPACE} --timeout=60s 2>/dev/null || true
log_success "Namespace removido"
fi
# Remover .env
if [[ -f "${SCRIPT_DIR}/.env" ]]; then
rm "${SCRIPT_DIR}/.env"
log_info ".env removido"
fi
echo ""
log_success "Cleanup concluído!"
echo ""
echo "Nota: As imagens no registry não foram removidas."
echo "Para remover, acesse o Gitea Packages manualmente:"
echo " https://${GITEA_HOST:-gitea.kube.quest}/factory/postgresql/packages"
echo ""

View File

@@ -0,0 +1,77 @@
# =============================================================================
# Pipeline CI: DevOps Toolbox (eStargz + GZIP)
# =============================================================================
# Constrói imagem em ambos os formatos para benchmark
# =============================================================================
stages:
- build
- push
variables:
REGISTRY: registry.kube.quest
IMAGE_NAME: factory/devops-toolbox
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
# -----------------------------------------------------------------------------
# Build eStargz (lazy pulling)
# -----------------------------------------------------------------------------
build-estargz:
stage: build
image: docker:27-dind
services:
- docker:27-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $REGISTRY
- docker buildx create --use --name multiarch --driver docker-container
script:
- |
docker buildx build \
--platform linux/arm64,linux/amd64 \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:latest,push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--cache-from type=registry,ref=${REGISTRY}/${IMAGE_NAME}:cache \
--cache-to type=registry,ref=${REGISTRY}/${IMAGE_NAME}:cache,mode=max \
.
rules:
- if: $CI_COMMIT_BRANCH == "main"
# -----------------------------------------------------------------------------
# Build GZIP (tradicional, para benchmark)
# -----------------------------------------------------------------------------
build-gzip:
stage: build
image: docker:27-dind
services:
- docker:27-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $REGISTRY
- docker buildx create --use --name multiarch --driver docker-container
script:
- |
docker buildx build \
--platform linux/arm64,linux/amd64 \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:gzip,push=true,compression=gzip,oci-mediatypes=true \
--cache-from type=registry,ref=${REGISTRY}/${IMAGE_NAME}:cache \
.
rules:
- if: $CI_COMMIT_BRANCH == "main"
# -----------------------------------------------------------------------------
# Tag como versão
# -----------------------------------------------------------------------------
push-tags:
stage: push
image: docker:27-cli
services:
- docker:27-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $REGISTRY
script:
- docker buildx imagetools create -t ${REGISTRY}/${IMAGE_NAME}:v1 ${REGISTRY}/${IMAGE_NAME}:latest
rules:
- if: $CI_COMMIT_BRANCH == "main"
needs:
- build-estargz

View File

@@ -0,0 +1,90 @@
# =============================================================================
# DevOps Toolbox - Demonstração de eStargz
# =============================================================================
# Imagem grande (~650MB) com múltiplas ferramentas em camadas separadas.
# Ideal para demonstrar lazy pulling: você só usa UMA ferramenta por vez!
# =============================================================================
FROM alpine:3.21
LABEL maintainer="workshop"
LABEL description="DevOps toolbox for eStargz lazy pulling demonstration"
# -----------------------------------------------------------------------------
# Camada 1: Ferramentas básicas (~50MB)
# -----------------------------------------------------------------------------
RUN apk add --no-cache \
bash \
curl \
wget \
jq \
git \
openssh-client \
ca-certificates \
unzip
# -----------------------------------------------------------------------------
# Camada 2: Terraform (~100MB)
# -----------------------------------------------------------------------------
ARG TERRAFORM_VERSION=1.9.8
ARG TARGETARCH
RUN wget -q "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_${TARGETARCH}.zip" -O /tmp/terraform.zip && \
unzip -q /tmp/terraform.zip -d /usr/local/bin/ && \
rm /tmp/terraform.zip && \
chmod +x /usr/local/bin/terraform
# -----------------------------------------------------------------------------
# Camada 3: OpenTofu (~100MB)
# -----------------------------------------------------------------------------
ARG TOFU_VERSION=1.8.8
RUN wget -q "https://github.com/opentofu/opentofu/releases/download/v${TOFU_VERSION}/tofu_${TOFU_VERSION}_linux_${TARGETARCH}.zip" -O /tmp/tofu.zip && \
unzip -q /tmp/tofu.zip -d /usr/local/bin/ && \
rm /tmp/tofu.zip && \
chmod +x /usr/local/bin/tofu
# -----------------------------------------------------------------------------
# Camada 4: Kubectl (~50MB)
# -----------------------------------------------------------------------------
ARG KUBECTL_VERSION=1.31.4
RUN curl -sLO "https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/${TARGETARCH}/kubectl" && \
chmod +x kubectl && \
mv kubectl /usr/local/bin/
# -----------------------------------------------------------------------------
# Camada 5: Helm (~50MB)
# -----------------------------------------------------------------------------
ARG HELM_VERSION=3.16.4
RUN wget -q "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" -O /tmp/helm.tar.gz && \
tar -xzf /tmp/helm.tar.gz -C /tmp && \
mv /tmp/linux-${TARGETARCH}/helm /usr/local/bin/ && \
rm -rf /tmp/helm.tar.gz /tmp/linux-${TARGETARCH}
# -----------------------------------------------------------------------------
# Camada 6: AWS CLI (~200MB)
# -----------------------------------------------------------------------------
RUN apk add --no-cache aws-cli
# -----------------------------------------------------------------------------
# Camada 7: Python + Ansible (~150MB)
# -----------------------------------------------------------------------------
RUN apk add --no-cache python3 py3-pip && \
pip3 install --no-cache-dir ansible --break-system-packages --quiet
# -----------------------------------------------------------------------------
# Camada 8: k9s (~50MB)
# -----------------------------------------------------------------------------
ARG K9S_VERSION=0.32.7
RUN wget -q "https://github.com/derailed/k9s/releases/download/v${K9S_VERSION}/k9s_Linux_${TARGETARCH}.tar.gz" -O /tmp/k9s.tar.gz && \
tar -xzf /tmp/k9s.tar.gz -C /usr/local/bin/ k9s && \
rm /tmp/k9s.tar.gz
# -----------------------------------------------------------------------------
# Entrypoint
# -----------------------------------------------------------------------------
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
WORKDIR /workspace
ENTRYPOINT ["/entrypoint.sh"]
CMD ["--help"]

View File

@@ -0,0 +1,64 @@
#!/bin/bash
# =============================================================================
# DevOps Toolbox Entrypoint
# =============================================================================
# Executa a ferramenta especificada ou mostra ajuda
# =============================================================================
set -e
# Ferramentas disponíveis
TOOLS="terraform tofu kubectl helm aws ansible k9s"
show_help() {
echo "DevOps Toolbox - Demonstração de eStargz Lazy Pulling"
echo ""
echo "Uso: docker run toolbox <ferramenta> [argumentos]"
echo ""
echo "Ferramentas disponíveis:"
echo " terraform - Infrastructure as Code"
echo " tofu - OpenTofu (Terraform fork)"
echo " kubectl - Kubernetes CLI"
echo " helm - Kubernetes package manager"
echo " aws - AWS CLI"
echo " ansible - Configuration management"
echo " k9s - Kubernetes TUI"
echo ""
echo "Exemplos:"
echo " docker run toolbox terraform version"
echo " docker run toolbox kubectl version --client"
echo " docker run toolbox helm version"
echo ""
echo "Com eStargz, apenas a camada da ferramenta usada é baixada!"
}
show_versions() {
echo "Versões instaladas:"
echo ""
terraform version 2>/dev/null | head -1 || echo "terraform: não disponível"
tofu version 2>/dev/null | head -1 || echo "tofu: não disponível"
kubectl version --client 2>/dev/null | head -1 || echo "kubectl: não disponível"
helm version --short 2>/dev/null || echo "helm: não disponível"
aws --version 2>/dev/null || echo "aws: não disponível"
ansible --version 2>/dev/null | head -1 || echo "ansible: não disponível"
k9s version --short 2>/dev/null || echo "k9s: não disponível"
}
# Processa argumentos
case "$1" in
--help|-h|"")
show_help
;;
--versions|-v)
show_versions
;;
terraform|tofu|kubectl|helm|aws|ansible|k9s)
exec "$@"
;;
*)
echo "Erro: Ferramenta '$1' não reconhecida"
echo ""
show_help
exit 1
;;
esac

View File

@@ -0,0 +1,38 @@
stages:
- build
variables:
REGISTRY: registry.kube.quest
IMAGE_NAME: factory/large-test
build:
stage: build
image: docker:27-dind
services:
- docker:27-dind
variables:
DOCKER_TLS_CERTDIR: ""
DOCKER_HOST: tcp://docker:2375
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx create --use --name builder --driver docker-container
script:
# Build eStargz (lazy pulling)
- echo "Building eStargz version..."
- |
docker buildx build \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:latest,push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
.
# Build GZIP tradicional
- echo "Building GZIP version..."
- |
docker buildx build \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:gzip,push=true,compression=gzip,oci-mediatypes=true \
.
- echo "Images pushed:"
- echo " - ${REGISTRY}/${IMAGE_NAME}:latest (eStargz ~1.5GB)"
- echo " - ${REGISTRY}/${IMAGE_NAME}:gzip (GZIP ~1.5GB)"
tags:
- kubernetes

View File

@@ -0,0 +1,34 @@
# Imagem de teste grande (~1.5GB) para benchmark de lazy pulling
FROM alpine:3.21
# Camada 1: Base tools (~50MB)
RUN apk add --no-cache \
bash \
curl \
wget \
jq \
python3 \
py3-pip
# Camada 2: Dados dummy 1 (~300MB)
RUN dd if=/dev/urandom of=/data1.bin bs=1M count=300
# Camada 3: Dados dummy 2 (~300MB)
RUN dd if=/dev/urandom of=/data2.bin bs=1M count=300
# Camada 4: Dados dummy 3 (~300MB)
RUN dd if=/dev/urandom of=/data3.bin bs=1M count=300
# Camada 5: Dados dummy 4 (~300MB)
RUN dd if=/dev/urandom of=/data4.bin bs=1M count=300
# Camada 6: Dados dummy 5 (~300MB)
RUN dd if=/dev/urandom of=/data5.bin bs=1M count=300
# Script de teste que acessa apenas arquivos pequenos
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Entrypoint simples que NÃO acessa os arquivos grandes
# Isso permite testar o lazy pulling - container inicia sem precisar dos dados
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -0,0 +1,12 @@
#!/bin/bash
# Entrypoint simples que NÃO acessa os arquivos grandes
# Permite testar lazy pulling - container inicia sem baixar dados
echo "Container iniciado em $(date)"
echo "Hostname: $(hostname)"
echo "Este container tem ~1.5GB de dados que NÃO são acessados no startup"
# Loop infinito para manter container running
while true; do
sleep 3600
done

View File

@@ -0,0 +1,51 @@
# =============================================================================
# Dockerfile - PostgreSQL Production (Alpine)
# =============================================================================
#
# Imagem customizada PostgreSQL para substituir Bitnami.
# Otimizada para produção e formato eStargz (lazy pulling).
#
# Build (formato eStargz):
# docker buildx build \
# --output type=image,name=registry.kube.quest/factory/postgresql:17,push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
# .
#
# =============================================================================
FROM postgres:17-alpine
LABEL maintainer="workshop"
LABEL description="PostgreSQL 17 Alpine - Production Ready"
LABEL org.opencontainers.image.title="postgresql"
LABEL org.opencontainers.image.version="17"
# Variáveis de ambiente padrão
ENV POSTGRES_USER=postgres \
POSTGRES_DB=postgres \
PGDATA=/var/lib/postgresql/data/pgdata \
LANG=en_US.UTF-8
# Instalar dependências adicionais úteis
RUN apk add --no-cache \
tzdata \
curl \
jq
# Criar diretório para configurações customizadas
RUN mkdir -p /etc/postgresql/postgresql.conf.d
# Copiar configuração de produção
COPY postgresql.conf /etc/postgresql/postgresql.conf.d/00-production.conf
# Healthcheck nativo
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB} || exit 1
# Expor porta padrão
EXPOSE 5432
# Usuário não-root (já vem configurado na imagem oficial)
USER postgres
# Comando padrão com configuração customizada
CMD ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf.d/00-production.conf"]

View File

@@ -0,0 +1,77 @@
# =============================================================================
# PostgreSQL Production Configuration
# =============================================================================
# Otimizado para containers Kubernetes com ~1GB de memória
# Documentação: https://www.postgresql.org/docs/17/runtime-config.html
# =============================================================================
# -----------------------------------------------------------------------------
# Conexões
# -----------------------------------------------------------------------------
listen_addresses = '*'
max_connections = 100
superuser_reserved_connections = 3
# -----------------------------------------------------------------------------
# Memória (para container com 1GB)
# -----------------------------------------------------------------------------
# shared_buffers: ~25% da RAM disponível
shared_buffers = 256MB
# effective_cache_size: ~50% da RAM (estimativa para o planner)
effective_cache_size = 512MB
# work_mem: memória por operação de sort/hash (cuidado com max_connections)
work_mem = 8MB
# maintenance_work_mem: para VACUUM, CREATE INDEX, etc.
maintenance_work_mem = 64MB
# -----------------------------------------------------------------------------
# WAL (Write-Ahead Logging)
# -----------------------------------------------------------------------------
wal_level = replica
max_wal_size = 1GB
min_wal_size = 80MB
checkpoint_completion_target = 0.9
# -----------------------------------------------------------------------------
# Query Planner
# -----------------------------------------------------------------------------
# random_page_cost: baixo para SSD (1.1) vs HDD (4.0)
random_page_cost = 1.1
# effective_io_concurrency: alto para SSD
effective_io_concurrency = 200
# default_statistics_target: mais estatísticas = melhores planos
default_statistics_target = 100
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
# Log para stderr (compatível com kubectl logs)
log_destination = 'stderr'
logging_collector = off
# O que logar
log_statement = 'ddl'
log_min_duration_statement = 1000
# Formato do log
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
# -----------------------------------------------------------------------------
# Locale/Encoding
# -----------------------------------------------------------------------------
timezone = 'America/Sao_Paulo'
lc_messages = 'en_US.UTF-8'
# -----------------------------------------------------------------------------
# Performance
# -----------------------------------------------------------------------------
# JIT: desabilitar em containers pequenos (overhead de compilação)
jit = off
# Huge pages: requer configuração do host
huge_pages = off

View File

@@ -0,0 +1,12 @@
# =============================================================================
# ConfigMap - PostgreSQL Configuration
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: postgresql-config
labels:
app: postgresql
data:
# Nome do banco de dados padrão
database: "app"

View File

@@ -0,0 +1,111 @@
# =============================================================================
# Deployment - PostgreSQL (Container Factory)
# =============================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresql
labels:
app: postgresql
app.kubernetes.io/name: postgresql
app.kubernetes.io/component: database
spec:
replicas: 1
strategy:
type: Recreate # PostgreSQL não suporta rolling update
selector:
matchLabels:
app: postgresql
template:
metadata:
labels:
app: postgresql
spec:
terminationGracePeriodSeconds: 30
imagePullSecrets:
- name: gitlab-registry
securityContext:
runAsNonRoot: true
runAsUser: 70 # postgres user no Alpine
fsGroup: 70
seccompProfile:
type: RuntimeDefault
containers:
- name: postgresql
# Imagem da Container Factory (eStargz)
image: registry.kube.quest/factory/postgresql:17
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
name: postgresql
protocol: TCP
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: postgresql-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgresql-secret
key: password
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: postgresql-config
key: database
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
resources:
requests:
memory: "512Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "500m"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
# Liveness: reinicia se PostgreSQL travar
livenessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 6
# Readiness: remove do service se não estiver pronto
readinessProbe:
exec:
command:
- /bin/sh
- -c
- pg_isready -U $POSTGRES_USER -d $POSTGRES_DB
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
drop:
- ALL
volumes:
- name: data
persistentVolumeClaim:
claimName: postgresql-data

View File

@@ -0,0 +1,17 @@
# =============================================================================
# PersistentVolumeClaim - PostgreSQL Data
# =============================================================================
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgresql-data
labels:
app: postgresql
spec:
accessModes:
- ReadWriteOnce
# Hetzner Cloud Volumes (aula-08 CSI)
storageClassName: hcloud-volumes
resources:
requests:
storage: 10Gi

View File

@@ -0,0 +1,24 @@
# =============================================================================
# Secret - PostgreSQL Credentials (Template)
# =============================================================================
#
# IMPORTANTE: Este arquivo é um template.
# O setup.sh gera o secret automaticamente com senha aleatória.
#
# Para criar manualmente:
# kubectl create secret generic postgresql-secret \
# --from-literal=username=postgres \
# --from-literal=password=SUA_SENHA_AQUI \
# -n factory
#
# =============================================================================
apiVersion: v1
kind: Secret
metadata:
name: postgresql-secret
labels:
app: postgresql
type: Opaque
stringData:
username: postgres
password: CHANGE_ME_USE_SETUP_SH

View File

@@ -0,0 +1,18 @@
# =============================================================================
# Service - PostgreSQL
# =============================================================================
apiVersion: v1
kind: Service
metadata:
name: postgresql
labels:
app: postgresql
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
protocol: TCP
name: postgresql
selector:
app: postgresql

View File

@@ -0,0 +1,131 @@
# =============================================================================
# Pre-pull DaemonSet - Alternativa para Cold Start
# =============================================================================
# Garante que imagens críticas estejam em cache em TODOS os nodes.
# Quando KEDA/Cluster Autoscaler criar pods, imagens já estarão disponíveis.
# =============================================================================
#
# COMO USAR:
# 1. Edite a lista de initContainers com suas imagens
# 2. kubectl apply -f prepull-daemonset.yaml
# 3. Aguarde todos os pods ficarem Ready
# 4. Imagens estarão em cache em todos os nodes
#
# QUANDO USAR:
# - Databases (PostgreSQL, MongoDB) que precisam de 100% dos arquivos
# - Apps que precarregam (n8n, Laravel Octane)
# - Qualquer imagem onde eStargz NÃO ajuda
#
# =============================================================================
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: image-prepuller
namespace: kube-system
labels:
app: image-prepuller
purpose: cold-start-optimization
spec:
selector:
matchLabels:
app: image-prepuller
template:
metadata:
labels:
app: image-prepuller
spec:
# Tolera todos os taints para rodar em TODOS os nodes
tolerations:
- operator: Exists
# InitContainers baixam as imagens e terminam
initContainers:
# ---------------------------------------------------------------------
# PostgreSQL (Container Factory)
# ---------------------------------------------------------------------
- name: prepull-postgresql
image: registry.kube.quest/factory/postgresql:17
command: ["echo", "PostgreSQL image cached"]
imagePullPolicy: Always
resources:
requests:
cpu: 1m
memory: 1Mi
limits:
cpu: 10m
memory: 10Mi
# ---------------------------------------------------------------------
# n8n
# ---------------------------------------------------------------------
- name: prepull-n8n
image: docker.n8n.io/n8nio/n8n:latest
command: ["echo", "n8n image cached"]
imagePullPolicy: Always
resources:
requests:
cpu: 1m
memory: 1Mi
limits:
cpu: 10m
memory: 10Mi
# ---------------------------------------------------------------------
# PostgreSQL Oficial (para clientes)
# ---------------------------------------------------------------------
- name: prepull-postgres-alpine
image: postgres:17-alpine
command: ["echo", "PostgreSQL Alpine image cached"]
imagePullPolicy: Always
resources:
requests:
cpu: 1m
memory: 1Mi
limits:
cpu: 10m
memory: 10Mi
# ---------------------------------------------------------------------
# Redis
# ---------------------------------------------------------------------
- name: prepull-redis
image: redis:7-alpine
command: ["echo", "Redis image cached"]
imagePullPolicy: Always
resources:
requests:
cpu: 1m
memory: 1Mi
limits:
cpu: 10m
memory: 10Mi
# Container principal apenas mantém o DaemonSet vivo
containers:
- name: pause
image: gcr.io/google_containers/pause:3.9
resources:
requests:
cpu: 1m
memory: 1Mi
limits:
cpu: 10m
memory: 10Mi
# Secrets para registries privados
imagePullSecrets:
- name: gitlab-registry
---
# =============================================================================
# Secret para Registry Privado (template)
# =============================================================================
# Crie este secret em kube-system se usar registry privado:
#
# kubectl create secret docker-registry gitlab-registry \
# --namespace=kube-system \
# --docker-server=registry.kube.quest \
# --docker-username=<usuario> \
# --docker-password=<token>
# =============================================================================

View File

@@ -0,0 +1,183 @@
# =============================================================================
# GitLab CI/CD Pipeline - PostgreSQL Container Factory
# =============================================================================
#
# Build de imagem PostgreSQL customizada em formato eStargz.
# Push para registry.kube.quest com lazy pulling habilitado.
#
# Requisitos:
# - GitLab Runner com Docker-in-Docker (aula-11)
# - BuildKit habilitado
#
# Uso:
# 1. Criar grupo 'factory' no GitLab
# 2. Criar projeto 'postgresql' dentro do grupo
# 3. Copiar Dockerfile, postgresql.conf e este .gitlab-ci.yml
# 4. Push para main - pipeline roda automaticamente
#
# =============================================================================
stages:
- build
- test
- push
variables:
# Registry do GitLab (aula-10)
REGISTRY: ${CI_REGISTRY}
IMAGE_NAME: ${CI_PROJECT_PATH}
POSTGRES_VERSION: "17"
# BuildKit para suporte a eStargz
DOCKER_BUILDKIT: "1"
BUILDKIT_PROGRESS: plain
# Docker-in-Docker TLS connection
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_CERT_PATH: "/certs/client"
DOCKER_TLS_VERIFY: "1"
# =============================================================================
# BUILD - Construir imagem em formato eStargz
# =============================================================================
build:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
# Aguardar Docker daemon estar pronto
- until docker info; do sleep 1; done
# Login no registry
- docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
# Configurar buildx com driver docker (usa daemon existente)
- docker buildx create --name estargz-builder --driver docker --use || true
- docker buildx inspect --bootstrap
script:
- echo "Building ${IMAGE_NAME}:${POSTGRES_VERSION}-${CI_COMMIT_SHA:0:8} with eStargz compression"
# Build com formato eStargz
- |
docker buildx build \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:${POSTGRES_VERSION}-${CI_COMMIT_SHA:0:8},push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${CI_COMMIT_SHA}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--label "org.opencontainers.image.source=${CI_PROJECT_URL}" \
--build-arg POSTGRES_VERSION=${POSTGRES_VERSION} \
.
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG
tags:
- kubernetes
- docker
# =============================================================================
# TEST - Testar imagem construída
# =============================================================================
test:
stage: test
image: docker:24
services:
- docker:24-dind
before_script:
- until docker info; do sleep 1; done
- docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
script:
- echo "Testing PostgreSQL image..."
# Iniciar container de teste
- |
docker run -d --name pg-test \
-e POSTGRES_PASSWORD=testpassword \
-e POSTGRES_DB=testdb \
${REGISTRY}/${IMAGE_NAME}:${POSTGRES_VERSION}-${CI_COMMIT_SHA:0:8}
# Aguardar inicialização (30s max)
- |
for i in $(seq 1 30); do
if docker exec pg-test pg_isready -U postgres -d testdb 2>/dev/null; then
echo "PostgreSQL ready!"
break
fi
echo "Waiting for PostgreSQL... ($i/30)"
sleep 1
done
# Verificar healthcheck
- docker exec pg-test pg_isready -U postgres -d testdb
# Testar conexão e queries básicas
- docker exec pg-test psql -U postgres -d testdb -c "SELECT version();"
- docker exec pg-test psql -U postgres -d testdb -c "SHOW shared_buffers;"
- docker exec pg-test psql -U postgres -d testdb -c "SHOW max_connections;"
# Testar criação de tabela
- docker exec pg-test psql -U postgres -d testdb -c "CREATE TABLE test (id serial PRIMARY KEY, name text);"
- docker exec pg-test psql -U postgres -d testdb -c "INSERT INTO test (name) VALUES ('test');"
- docker exec pg-test psql -U postgres -d testdb -c "SELECT * FROM test;"
- docker exec pg-test psql -U postgres -d testdb -c "DROP TABLE test;"
# Cleanup
- docker stop pg-test && docker rm pg-test
- echo "All tests passed!"
needs:
- build
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG
tags:
- kubernetes
- docker
# =============================================================================
# PUSH - Tag como versão e latest
# =============================================================================
push:
stage: push
image: docker:24
services:
- docker:24-dind
before_script:
- until docker info; do sleep 1; done
- docker login -u ${CI_REGISTRY_USER} -p ${CI_REGISTRY_PASSWORD} ${CI_REGISTRY}
- docker buildx create --name estargz-builder --driver docker --use || true
- docker buildx inspect --bootstrap
script:
- echo "Tagging and pushing final images..."
# Re-tag como versão (17)
- |
docker buildx build \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:${POSTGRES_VERSION},push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${CI_COMMIT_SHA}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
.
# Re-tag como latest
- |
docker buildx build \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:latest,push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${CI_COMMIT_SHA}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
.
# Build versão GZIP (para benchmark)
- |
docker buildx build \
--output type=image,name=${REGISTRY}/${IMAGE_NAME}:${POSTGRES_VERSION}-gzip,push=true,compression=gzip,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${CI_COMMIT_SHA}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
.
- echo "Images pushed:"
- echo " - ${REGISTRY}/${IMAGE_NAME}:${POSTGRES_VERSION} (eStargz)"
- echo " - ${REGISTRY}/${IMAGE_NAME}:${POSTGRES_VERSION}-gzip (tradicional)"
- echo " - ${REGISTRY}/${IMAGE_NAME}:latest (eStargz)"
needs:
- test
rules:
- if: $CI_COMMIT_BRANCH == "main"
tags:
- kubernetes
- docker

View File

@@ -0,0 +1,157 @@
# =============================================================================
# Gitea Actions Workflow - PostgreSQL Container Factory
# =============================================================================
#
# Build de imagem PostgreSQL customizada em formato eStargz.
# Push para Gitea Container Registry com lazy pulling habilitado.
#
# Requisitos:
# - Gitea Actions Runner com Docker (aula-10)
# - Secret REGISTRY_TOKEN configurado no repo
#
# Uso:
# 1. Criar org 'factory' no Gitea
# 2. Criar repositório 'postgresql' na org
# 3. Copiar Dockerfile, postgresql.conf e este ci.yml para .gitea/workflows/
# 4. Push para main - pipeline roda automaticamente
#
# =============================================================================
name: Build PostgreSQL
on:
push:
branches: [main]
tags: ['*']
env:
REGISTRY: gitea.kube.quest
IMAGE_NAME: factory/postgresql
POSTGRES_VERSION: "17"
jobs:
# ===========================================================================
# BUILD - Construir imagem em formato eStargz
# ===========================================================================
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} \
-u ${{ gitea.actor }} --password-stdin
- name: Build eStargz image
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
echo "Building ${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }}-${SHORT_SHA} with eStargz compression"
docker buildx build \
--output type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }}-${SHORT_SHA},push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--build-arg POSTGRES_VERSION=${{ env.POSTGRES_VERSION }} \
.
# ===========================================================================
# TEST - Testar imagem construída
# ===========================================================================
test:
runs-on: ubuntu-latest
needs: build
steps:
- name: Login to Gitea Registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} \
-u ${{ gitea.actor }} --password-stdin
- name: Test PostgreSQL image
run: |
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
echo "Testing PostgreSQL image..."
docker run -d --name pg-test \
-e POSTGRES_PASSWORD=testpassword \
-e POSTGRES_DB=testdb \
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }}-${SHORT_SHA}
# Aguardar inicialização (30s max)
for i in $(seq 1 30); do
if docker exec pg-test pg_isready -U postgres -d testdb 2>/dev/null; then
echo "PostgreSQL ready!"
break
fi
echo "Waiting for PostgreSQL... ($i/30)"
sleep 1
done
# Verificar healthcheck
docker exec pg-test pg_isready -U postgres -d testdb
# Testar conexão e queries básicas
docker exec pg-test psql -U postgres -d testdb -c "SELECT version();"
docker exec pg-test psql -U postgres -d testdb -c "SHOW shared_buffers;"
docker exec pg-test psql -U postgres -d testdb -c "SHOW max_connections;"
# Testar criação de tabela
docker exec pg-test psql -U postgres -d testdb -c "CREATE TABLE test (id serial PRIMARY KEY, name text);"
docker exec pg-test psql -U postgres -d testdb -c "INSERT INTO test (name) VALUES ('test');"
docker exec pg-test psql -U postgres -d testdb -c "SELECT * FROM test;"
docker exec pg-test psql -U postgres -d testdb -c "DROP TABLE test;"
# Cleanup
docker stop pg-test && docker rm pg-test
echo "All tests passed!"
# ===========================================================================
# PUSH - Tag como versão e latest
# ===========================================================================
push:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} \
-u ${{ gitea.actor }} --password-stdin
- name: Push version and latest tags (eStargz)
run: |
echo "Tagging and pushing final images..."
# Tag como versão (17)
docker buildx build \
--output type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }},push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
.
# Tag como latest
docker buildx build \
--output type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest,push=true,compression=estargz,force-compression=true,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
.
- name: Push GZIP version (for benchmark)
run: |
docker buildx build \
--output type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }}-gzip,push=true,compression=gzip,oci-mediatypes=true \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
.
echo "Images pushed:"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }} (eStargz)"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.POSTGRES_VERSION }}-gzip (GZIP)"
echo " - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest (eStargz)"

234
aula-13/setup.sh Executable file
View File

@@ -0,0 +1,234 @@
#!/bin/bash
# =============================================================================
# Aula 13 - Container Factory (eStargz Images)
# =============================================================================
#
# Este script configura:
# 1. Namespace para deploy de imagens customizadas
# 2. Secrets e ConfigMaps
# 3. Instruções para criar repositório no Gitea
#
# Pré-requisitos:
# - Cluster Kubernetes com Talos + stargz-snapshotter (aula-07/08)
# - Gitea instalado (aula-10)
# - Gitea Actions Runner (aula-10)
#
# =============================================================================
set -e
# Cores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
# =============================================================================
# VERIFICAR PRÉ-REQUISITOS
# =============================================================================
log_info "Verificando pré-requisitos..."
# Verificar kubectl
if ! command -v kubectl &> /dev/null; then
log_error "kubectl não encontrado. Instale com: brew install kubectl"
exit 1
fi
# Verificar conexão com cluster
if ! kubectl cluster-info &> /dev/null; then
log_error "Não foi possível conectar ao cluster Kubernetes"
log_info "Verifique se KUBECONFIG está configurado corretamente"
exit 1
fi
# Verificar se Gitea está instalado
if ! kubectl get namespace gitea &> /dev/null; then
log_error "Namespace 'gitea' não encontrado"
log_info "Execute primeiro a aula-10 para instalar o Gitea"
exit 1
fi
log_success "Pré-requisitos verificados"
# =============================================================================
# CARREGAR CONFIGURAÇÃO EXISTENTE
# =============================================================================
# Carregar configuração local PRIMEIRO (se existir)
if [[ -f "$ENV_FILE" ]]; then
log_info "Carregando configuração local..."
source "$ENV_FILE"
fi
# Se não tiver configuração local, tentar herdar da aula-10
if [[ -z "$DOMAIN" ]]; then
AULA10_ENV="${SCRIPT_DIR}/../aula-10/.env"
if [[ -f "$AULA10_ENV" ]]; then
log_info "Herdando configuração da aula-10..."
source "$AULA10_ENV"
fi
fi
# =============================================================================
# COLETAR CONFIGURAÇÃO
# =============================================================================
echo ""
echo "=========================================="
echo " Container Factory - eStargz Images"
echo "=========================================="
echo ""
# Domínio
if [[ -z "$DOMAIN" ]]; then
read -p "Domínio base (ex: kube.quest): " DOMAIN
fi
log_info "Domínio: ${DOMAIN}"
GITEA_HOST="gitea.${DOMAIN}"
# Namespace para deploy
if [[ -z "$DEPLOY_NAMESPACE" ]]; then
DEFAULT_NS="factory"
read -p "Namespace para deploy [${DEFAULT_NS}]: " DEPLOY_NAMESPACE
DEPLOY_NAMESPACE="${DEPLOY_NAMESPACE:-$DEFAULT_NS}"
fi
log_info "Namespace: ${DEPLOY_NAMESPACE}"
# Gerar senha PostgreSQL se não existir
if [[ -z "$POSTGRES_PASSWORD" ]]; then
POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
log_info "Senha PostgreSQL gerada automaticamente"
fi
# Salvar configuração
cat > "$ENV_FILE" << EOF
# Configuração gerada pelo setup.sh - $(date)
DOMAIN=${DOMAIN}
GITEA_HOST=${GITEA_HOST}
DEPLOY_NAMESPACE=${DEPLOY_NAMESPACE}
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
EOF
log_success "Configuração salva em ${ENV_FILE}"
# =============================================================================
# CRIAR NAMESPACE
# =============================================================================
echo ""
log_info "=== Criando Namespace ==="
kubectl create namespace ${DEPLOY_NAMESPACE} --dry-run=client -o yaml | kubectl apply -f -
log_success "Namespace ${DEPLOY_NAMESPACE} criado"
# =============================================================================
# CRIAR SECRET DO POSTGRESQL
# =============================================================================
log_info "Criando secret do PostgreSQL..."
kubectl create secret generic postgresql-secret \
--namespace ${DEPLOY_NAMESPACE} \
--from-literal=username=postgres \
--from-literal=password="${POSTGRES_PASSWORD}" \
--dry-run=client -o yaml | kubectl apply -f -
log_success "Secret postgresql-secret criado"
# =============================================================================
# CRIAR CONFIGMAP
# =============================================================================
log_info "Criando ConfigMap do PostgreSQL..."
kubectl apply -f "${SCRIPT_DIR}/k8s/postgresql/configmap.yaml" -n ${DEPLOY_NAMESPACE}
log_success "ConfigMap postgresql-config criado"
# =============================================================================
# INSTRUÇÕES PARA CRIAR REPOSITÓRIO
# =============================================================================
echo ""
echo "=========================================="
echo " Próximos Passos"
echo "=========================================="
echo ""
echo -e "${CYAN}1. Criar organização 'factory' no Gitea:${NC}"
echo " URL: https://${GITEA_HOST}/-/admin/orgs"
echo " Nome: factory"
echo " Visibilidade: Private"
echo ""
echo -e "${CYAN}2. Criar repositório 'postgresql' na org:${NC}"
echo " URL: https://${GITEA_HOST}/repo/create"
echo " Owner: factory"
echo " Nome: postgresql"
echo ""
echo -e "${CYAN}3. Clonar e copiar os arquivos:${NC}"
echo ""
echo " git clone git@${GITEA_HOST}:factory/postgresql.git"
echo " cd postgresql"
echo " cp ${SCRIPT_DIR}/images/postgresql/* ."
echo " mkdir -p .gitea/workflows"
echo " cp ${SCRIPT_DIR}/pipelines/postgresql/ci.yml .gitea/workflows/ci.yml"
echo ""
echo -e "${CYAN}4. Push inicial:${NC}"
echo ""
echo " git add ."
echo " git commit -m 'Initial commit: PostgreSQL factory image'"
echo " git push -u origin main"
echo ""
echo -e "${CYAN}5. Aguardar pipeline (Gitea Actions):${NC}"
echo " https://${GITEA_HOST}/factory/postgresql/actions"
echo ""
echo -e "${CYAN}6. Após pipeline completo, deploy no cluster:${NC}"
echo ""
echo " kubectl apply -f ${SCRIPT_DIR}/k8s/postgresql/pvc.yaml -n ${DEPLOY_NAMESPACE}"
echo " kubectl apply -f ${SCRIPT_DIR}/k8s/postgresql/deployment.yaml -n ${DEPLOY_NAMESPACE}"
echo " kubectl apply -f ${SCRIPT_DIR}/k8s/postgresql/service.yaml -n ${DEPLOY_NAMESPACE}"
echo ""
echo "=========================================="
echo " Credenciais PostgreSQL"
echo "=========================================="
echo " Host: postgresql.${DEPLOY_NAMESPACE}.svc.cluster.local"
echo " Port: 5432"
echo " User: postgres"
echo " Pass: ${POSTGRES_PASSWORD}"
echo " DB: app"
echo "=========================================="
echo ""
echo -e "${CYAN}7. Testar conexão:${NC}"
echo ""
echo " kubectl run pg-client --rm -it --restart=Never \\"
echo " --image=postgres:17-alpine \\"
echo " --env=PGPASSWORD=${POSTGRES_PASSWORD} \\"
echo " -- psql -h postgresql.${DEPLOY_NAMESPACE}.svc.cluster.local -U postgres -d app"
echo ""
echo "=========================================="
echo " Container Registry (Gitea Packages)"
echo "=========================================="
echo ""
echo " # Login"
echo " docker login ${GITEA_HOST}"
echo ""
echo " # Imagens são publicadas automaticamente pelo Gitea Actions"
echo " # Após pipeline: ${GITEA_HOST}/factory/postgresql/packages"
echo ""
echo "=========================================="
echo " Verificar Lazy Pulling (eStargz)"
echo "=========================================="
echo ""
echo " # Ver tempo de startup do pod"
echo " kubectl get pods -n ${DEPLOY_NAMESPACE} -w"
echo ""
echo " # Ver logs do stargz-snapshotter (se tiver acesso ao node)"
echo " talosctl -n <node-ip> logs stargz-snapshotter"
echo ""