# 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 ```