From d380cd858596909c9db024845ac0b04cbf0d3bb0 Mon Sep 17 00:00:00 2001 From: ArgoCD Setup Date: Sat, 14 Mar 2026 01:44:30 -0300 Subject: [PATCH] =?UTF-8?q?refactor:=20migrar=20GitLab=20=E2=86=92=20Gitea?= =?UTF-8?q?=20(aulas=2010,=2011,=2013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- CLAUDE.md | 6 +- aula-08/cluster-autoscaler.yaml | 33 +- aula-10/README.md | 523 +++++++++---------- aula-10/cleanup.sh | 53 +- aula-10/gitea-values.yaml | 207 ++++++++ aula-10/gitlab-values.yaml | 192 +++---- aula-10/setup.sh | 513 +++++------------- aula-11/README.md | 240 +++------ aula-11/cleanup.sh | 21 +- aula-11/gitlab-runner-values.yaml | 20 +- aula-11/node-bugado/.gitea/workflows/ci.yml | 77 +++ aula-11/setup.sh | 256 ++------- aula-13/README.md | 409 +++++++++++++++ aula-13/benchmarks/benchmark-clean.sh | 188 +++++++ aula-13/benchmarks/benchmark-postgresql.sh | 170 ++++++ aula-13/benchmarks/benchmark-pull-only.sh | 129 +++++ aula-13/benchmarks/benchmark-toolbox.sh | 138 +++++ aula-13/cleanup.sh | 71 +++ aula-13/images/devops-toolbox/.gitlab-ci.yml | 77 +++ aula-13/images/devops-toolbox/Dockerfile | 90 ++++ aula-13/images/devops-toolbox/entrypoint.sh | 64 +++ aula-13/images/large-test/.gitlab-ci.yml | 38 ++ aula-13/images/large-test/Dockerfile | 34 ++ aula-13/images/large-test/entrypoint.sh | 12 + aula-13/images/postgresql/Dockerfile | 51 ++ aula-13/images/postgresql/postgresql.conf | 77 +++ aula-13/k8s/postgresql/configmap.yaml | 12 + aula-13/k8s/postgresql/deployment.yaml | 111 ++++ aula-13/k8s/postgresql/pvc.yaml | 17 + aula-13/k8s/postgresql/secret.yaml | 24 + aula-13/k8s/postgresql/service.yaml | 18 + aula-13/k8s/prepull-daemonset.yaml | 131 +++++ aula-13/pipelines/postgresql/.gitlab-ci.yml | 183 +++++++ aula-13/pipelines/postgresql/ci.yml | 157 ++++++ aula-13/setup.sh | 234 +++++++++ 35 files changed, 3374 insertions(+), 1202 deletions(-) create mode 100644 aula-10/gitea-values.yaml create mode 100644 aula-11/node-bugado/.gitea/workflows/ci.yml create mode 100644 aula-13/README.md create mode 100755 aula-13/benchmarks/benchmark-clean.sh create mode 100755 aula-13/benchmarks/benchmark-postgresql.sh create mode 100755 aula-13/benchmarks/benchmark-pull-only.sh create mode 100755 aula-13/benchmarks/benchmark-toolbox.sh create mode 100755 aula-13/cleanup.sh create mode 100644 aula-13/images/devops-toolbox/.gitlab-ci.yml create mode 100644 aula-13/images/devops-toolbox/Dockerfile create mode 100644 aula-13/images/devops-toolbox/entrypoint.sh create mode 100644 aula-13/images/large-test/.gitlab-ci.yml create mode 100644 aula-13/images/large-test/Dockerfile create mode 100644 aula-13/images/large-test/entrypoint.sh create mode 100644 aula-13/images/postgresql/Dockerfile create mode 100644 aula-13/images/postgresql/postgresql.conf create mode 100644 aula-13/k8s/postgresql/configmap.yaml create mode 100644 aula-13/k8s/postgresql/deployment.yaml create mode 100644 aula-13/k8s/postgresql/pvc.yaml create mode 100644 aula-13/k8s/postgresql/secret.yaml create mode 100644 aula-13/k8s/postgresql/service.yaml create mode 100644 aula-13/k8s/prepull-daemonset.yaml create mode 100644 aula-13/pipelines/postgresql/.gitlab-ci.yml create mode 100644 aula-13/pipelines/postgresql/ci.yml create mode 100755 aula-13/setup.sh diff --git a/CLAUDE.md b/CLAUDE.md index 0ca035b..07d5b8c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,8 +20,8 @@ App de demonstração: `node-bugado` - trava após N requests para demonstrar he | 07 | Talos Linux (snapshot Hetzner) | Hetzner | | 08 | Cluster HA (OpenTofu + CCM + CSI) | Hetzner | | 09 | n8n multi-tenant | Hetzner | -| 10 | GitLab + Registry + SSH | Hetzner | -| 11 | ArgoCD + GitLab Runner | Hetzner | +| 10 | Gitea + Registry + SSH + Actions + Runner | Hetzner | +| 11 | ArgoCD (GitOps) | Hetzner | | 12 | Victoria Metrics (Observabilidade) | Hetzner | | 13 | Container Factory (eStargz) | Hetzner | | 14 | Istio Traffic Splitting | Hetzner | @@ -35,7 +35,7 @@ cd aula-XX && ./setup.sh # ou kubectl apply -f . # Aulas 07-13 (Hetzner) cd aula-08 && ./setup.sh # Cluster base cd aula-09 && ./setup.sh # n8n -cd aula-10 && ./setup.sh # GitLab +cd aula-10 && ./setup.sh # Gitea cd aula-11 && ./setup.sh # ArgoCD cd aula-12 && ./setup.sh # Victoria Metrics cd aula-13 && ./setup.sh # Container Factory diff --git a/aula-08/cluster-autoscaler.yaml b/aula-08/cluster-autoscaler.yaml index a37c6df..8772d42 100644 --- a/aula-08/cluster-autoscaler.yaml +++ b/aula-08/cluster-autoscaler.yaml @@ -1,6 +1,10 @@ ############################################################ # Cluster Autoscaler para Hetzner Cloud + Talos -# Escala workers automaticamente de 1 a 5 nodes +# +# Pools: +# - worker-pool: CAX21 (4 vCPU, 8GB) - workloads gerais +# - gitlab-pool: CAX21 - GitLab e serviços relacionados +# - build-pool: CAX31 (8 vCPU, 16GB) - builds Docker, escala 0-1 ############################################################ --- @@ -120,15 +124,29 @@ spec: command: - ./cluster-autoscaler - --cloud-provider=hetzner - - --nodes=0:5:CAX11:nbg1:worker-pool - - --nodes=0:0:CAX11:nbg1:draining-node-pool + # + # POOLS DE NODES: + # + # worker-pool: Workloads gerais (CAX21 = 4 vCPU, 8GB) + - --nodes=1:3:CAX21:nbg1:worker-pool + # + # gitlab-pool: GitLab e serviços pesados (CAX21) + - --nodes=1:2:CAX21:nbg1:gitlab-pool + # + # build-pool: Builds Docker (CAX31 = 8 vCPU, 16GB) + # Escala 0-1 sob demanda, taint "dedicated=builds:NoSchedule" + - --nodes=0:1:CAX31:nbg1:build-pool + # + # CONFIGURAÇÕES DE SCALE DOWN: + # - --scale-down-enabled=true - - --scale-down-delay-after-add=5m + - --scale-down-delay-after-add=3m - --scale-down-unneeded-time=3m - --scale-down-utilization-threshold=0.5 - --skip-nodes-with-local-storage=false - --skip-nodes-with-system-pods=false - - --balance-similar-node-groups=true + - --balance-similar-node-groups=false + - --expander=least-waste - --v=4 env: - name: HCLOUD_TOKEN @@ -141,6 +159,11 @@ spec: secretKeyRef: name: hcloud-autoscaler key: cloud-init + - name: HCLOUD_CLUSTER_CONFIG + valueFrom: + secretKeyRef: + name: hcloud-autoscaler + key: cluster-config - name: HCLOUD_IMAGE value: "${TALOS_IMAGE_ID}" - name: HCLOUD_NETWORK diff --git a/aula-10/README.md b/aula-10/README.md index b518f16..1859f91 100644 --- a/aula-10/README.md +++ b/aula-10/README.md @@ -1,49 +1,127 @@ -# Aula 10 - GitLab via Helm (Cluster Hetzner Cloud) +# Aula 10 - Plataforma de Desenvolvimento (Gitea) -Deploy do GitLab em Kubernetes usando Helm chart oficial, com Container Registry, SSH e TLS configurável. +Git Server + Container Registry + CI/CD Runner — tudo num único deploy. + +## Por que esta aula existe + +Nas aulas anteriores construímos um cluster Kubernetes na Hetzner com autoscaling, ingress e storage. Agora precisamos de uma **plataforma de desenvolvimento** para: + +1. **Hospedar código** (Git) +2. **Armazenar imagens Docker** (Container Registry) +3. **Automatizar builds e deploys** (CI/CD) + +Normalmente, isso exigiria 3 ferramentas separadas. Nesta aula, mostramos como o Gitea entrega as 3 em um único binário. + +## Por que Gitea (e não GitLab, Harbor, Tekton)? + +A escolha de ferramentas é uma decisão de arquitetura. No ecossistema Kubernetes, existem opções especializadas para cada peça: + +### De → Para + +| Função | Opção "Enterprise" | O que usamos | Por quê | +|--------|-------------------|--------------|---------| +| **Git Server** | GitLab CE/EE | **Gitea** | GitLab pede ~5Gi RAM, 8 pods, 2 nodes. Gitea pede ~500Mi, 1 pod, 1 node. | +| **Container Registry** | Harbor | **Gitea (integrado)** | Harbor é um projeto separado (~1Gi RAM, PostgreSQL, Redis, storage backend). Gitea já tem registry OCI embutido. | +| **CI/CD** | Tekton / GitLab CI | **Gitea Actions** | Tekton é poderoso mas complexo (CRDs, pipelines, tasks, triggers — curva de aprendizado alta). Gitea Actions usa sintaxe GitHub Actions, que a maioria já conhece. | + +### A conta fecha assim + +``` +Abordagem "enterprise" (GitLab + Harbor + Tekton): + GitLab: ~5Gi RAM, 8 pods, 40Gi storage, 2 nodes ($12.92/mês) + Harbor: ~1Gi RAM, 5 pods, 20Gi storage ($5/mês) + Tekton: ~512Mi RAM, 4 CRDs, curva de aprendizado + Total: ~6.5Gi RAM, 17+ pods (~$18/mês) + +Abordagem Gitea (tudo integrado): + Gitea: ~500Mi RAM, 3 pods, 21Gi storage, 1 node ($7.41/mês) + Total: ~500Mi RAM, 3 pods (~$7/mês) +``` + +**Para um workshop com cluster pequeno na Hetzner (CAX11 com 4GB RAM), a conta não fecha com a abordagem enterprise.** + +### Mas e a sintaxe do CI? + +Esse é o ponto decisivo. Gitea Actions usa **sintaxe GitHub Actions** — o padrão de fato da indústria: + +```yaml +# .gitea/workflows/ci.yml — mesma sintaxe do GitHub Actions +name: Build +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: docker build -t minha-app . +``` + +Se você sabe GitHub Actions, sabe Gitea Actions. Se você sabe Gitea Actions, sabe GitHub Actions. Tekton tem sua própria DSL (PipelineRun, TaskRun, etc.) que não transfere pra nenhuma outra plataforma. + +### Comparação visual: GitLab vs Gitea + +| | GitLab | Gitea | +|---|---|---| +| **RAM** | ~5 Gi (request) | ~500 Mi | +| **Pods** | 8 (webservice, sidekiq, gitaly, shell, postgres, redis, minio, registry) | 3 (gitea, postgresql, valkey) | +| **Storage** | 40 Gi (4 PVCs) | 21 Gi (3 PVCs) | +| **Nodes mínimos** | 2 (antiAffinity webservice/sidekiq) | 1 | +| **Install time** | 10-15 min | 2-3 min | +| **CI Syntax** | `.gitlab-ci.yml` (proprietária) | `.gitea/workflows/*.yml` (GitHub Actions) | +| **Registry** | Componente separado (+ MinIO/S3) | Integrado (OCI nativo) | +| **Runner** | `gitlab-runner` (Helm chart separado) | `act_runner` (Helm chart separado) | ## Arquitetura ``` Hetzner LoadBalancer │ - ┌───────────────────┼───────────────────┐ - │ │ │ - :80/:443 :22 │ - │ │ │ - ▼ ▼ │ -┌───────────────┐ ┌─────────────┐ │ -│ NGINX Ingress │ │ TCP Service │ │ -└───────┬───────┘ └──────┬──────┘ │ - │ │ │ - ▼ ▼ │ -┌─────────────────────────────────────────┐ │ -│ GitLab (namespace: gitlab) │ │ -│ ┌──────────┐ ┌────────┐ ┌─────────┐ │ │ -│ │Webservice│ │Sidekiq │ │ Shell │◄─┼─────┘ -│ │ (Rails) │ │ (Jobs) │ │ (SSH) │ │ -│ └────┬─────┘ └───┬────┘ └─────────┘ │ -│ │ │ │ -│ ┌────▼────────────▼────┐ │ -│ │ Gitaly │ │ -│ │ (Git Storage) │ │ -│ └──────────────────────┘ │ + ┌───────────────────┼──────────────┐ + │ │ │ + :80/:443 :22 │ + │ │ │ + ▼ ▼ │ +┌───────────────┐ ┌─────────────┐ │ +│ NGINX Ingress │ │ TCP Service │ │ +└───────┬───────┘ └──────┬──────┘ │ + │ │ │ + ▼ ▼ │ +┌──────────────────────────────────────────┐ +│ Namespace: gitea │ │ │ -│ ┌──────────┐ ┌───────┐ │ -│ │PostgreSQL│ │ Redis │ │ -│ │ (10Gi) │ │(10Gi) │ │ -│ └──────────┘ └───────┘ │ -└──────────────┬──────────────────────────┘ - │ - ▼ - ┌─────────────────────┐ - │ Hetzner Object │ - │ Storage (S3) │ - │ - uploads │ - │ - artifacts │ - │ - registry images │ - │ - lfs objects │ - └─────────────────────┘ +│ ┌─────────────────────────────────┐ │ +│ │ Gitea Pod │ │ +│ │ Web UI + API + Git + SSH │◄────┘ +│ │ Container Registry (OCI) │ +│ │ Gitea Actions (habilitado) │ +│ └──────────────┬──────────────────┘ +│ │ +│ ┌──────────────┼──────────────┐ +│ │ │ │ +│ ▼ ▼ ▼ +│ PostgreSQL Valkey act_runner +│ (10Gi) (1Gi) + DinD +│ (builds Docker) +└──────────────────────────────────────────┘ +``` + +### O act_runner em detalhe + +O runner é instalado como um **StatefulSet** com 2 containers: + +| Container | O que faz | +|-----------|-----------| +| `act-runner` | Conecta ao Gitea, escuta por jobs, executa workflows | +| `dind` (Docker-in-Docker) | Daemon Docker privilegiado — permite `docker build` e `docker push` dentro dos workflows | + +Quando você faz `git push` num repo que tem `.gitea/workflows/ci.yml`, o fluxo é: + +``` +git push → Gitea recebe → act_runner detecta workflow + → Runner cria container temporário + → Executa steps (checkout, build, push) + → Imagem vai pro Gitea Container Registry + → Kubernetes puxa a imagem no deploy ``` ## Pré-requisitos @@ -51,40 +129,37 @@ Deploy do GitLab em Kubernetes usando Helm chart oficial, com Container Registry 1. **Cluster Talos na Hetzner** (aula-08) com: - Hetzner CSI Driver (StorageClass: hcloud-volumes) - NGINX Ingress Controller com LoadBalancer -2. **Hetzner Object Storage** (bucket + credenciais): - - Criar bucket em: https://console.hetzner.cloud → Object Storage - - Gerar Access Key e Secret Key -3. **kubectl** instalado -4. **Helm** 3.x instalado - -## Contexto do Cluster - -Esta aula usa o cluster Kubernetes provisionado na **aula-08**. O `kubeconfig` foi gerado automaticamente pelo OpenTofu. - -```bash -# Verificar se o cluster está acessível -export KUBECONFIG=$(pwd)/../aula-08/kubeconfig -kubectl cluster-info -``` +2. **kubectl** instalado +3. **Helm** 3.x instalado ## Instalação ```bash cd aula-10 - -# Configurar acesso ao cluster (kubeconfig da aula-08) export KUBECONFIG=$(pwd)/../aula-08/kubeconfig - -# Executar setup interativo chmod +x setup.sh ./setup.sh ``` O script é interativo e pergunta: -1. **Hostname do GitLab** (ex: `git.kube.quest`) - FQDN completo -2. **Usa CloudFlare?** (com proxy/CDN) - pode herdar da aula-09 +1. **Hostname do Gitea** (ex: `gitea.kube.quest`) - FQDN completo +2. **Usa CloudFlare?** (com proxy/CDN) 3. **Ativar Let's Encrypt?** (se não usar CloudFlare) -4. **Hetzner Object Storage** - endpoint, bucket e credenciais + +### O que o setup.sh instala + +``` +1. cert-manager (se Let's Encrypt) +2. Gitea via Helm (gitea-charts/gitea) + - Web UI + API + Git + SSH + - Container Registry (packages) + - PostgreSQL standalone + - Valkey (cache) +3. Gitea Actions Runner via Helm (gitea-charts/actions) + - act_runner + Docker-in-Docker + - Registration token obtido automaticamente +4. TCP passthrough no NGINX para SSH (porta 22) +``` ### Opções de TLS @@ -94,132 +169,59 @@ O script é interativo e pergunta: | **Outro DNS + Let's Encrypt** | cert-manager | HTTP-01 challenge automático | | **Outro DNS + HTTP** | Nenhum | Apenas HTTP (dev/workshop) | -### CloudFlare e Trusted Proxies (Erro 422) +## Componentes e Recursos -Quando você usa CloudFlare com proxy ativo (ícone laranja), o tráfego passa pelos servidores do CloudFlare antes de chegar ao GitLab: +| Componente | Chart | Memory Request | Memory Limit | Volume | +|------------|-------|----------------|--------------|--------| +| Gitea | gitea-charts/gitea | 512Mi | 1Gi | 10Gi | +| PostgreSQL | (subchart) | 128Mi | 256Mi | 10Gi | +| Valkey | (subchart) | 32Mi | 64Mi | 1Gi | +| act_runner + DinD | gitea-charts/actions | 128Mi | 256Mi | 5Gi | +| **Total** | | **~800Mi** | **~1.6Gi** | **~26Gi** | -``` -Usuário → CloudFlare (TLS) → LoadBalancer → NGINX → GitLab - ↓ - Adiciona headers: - X-Forwarded-For: IP-do-usuario - X-Forwarded-Proto: https -``` +## Funcionalidades -**Problema:** Por padrão, o GitLab (Workhorse) não confia nesses headers. Resultado: -- GitLab pensa que a requisição veio via HTTP (não HTTPS) -- CSRF token é gerado para HTTPS mas validado como HTTP -- **Erro 422: "The change you requested was rejected"** +### 1. Container Registry (OCI) -**Solução:** O `setup.sh` configura automaticamente os IPs do CloudFlare como proxies confiáveis: - -```yaml -gitlab.webservice.workhorse.trustedCIDRsForXForwardedFor: - - 173.245.48.0/20 # CloudFlare - - 103.21.244.0/22 - - 103.22.200.0/22 - # ... (todos os CIDRs do CloudFlare) -``` - -Com isso, o Workhorse confia nos headers `X-Forwarded-*` vindos do CloudFlare e o login funciona corretamente. - -> **Lição:** Proxies reversos (CloudFlare, NGINX, HAProxy) adicionam headers para preservar informações do cliente original. O backend precisa ser configurado para confiar nesses headers, caso contrário terá problemas de protocolo e autenticação. - -## Componentes Instalados - -**Da aula-08 (infraestrutura):** -- Hetzner CSI Driver (StorageClass: hcloud-volumes) -- NGINX Ingress Controller com LoadBalancer - -**Instalados por este script:** -- **cert-manager** (se Let's Encrypt ativado) -- **GitLab** com todos os componentes - -| Componente | Memory Request | Memory Limit | Volume | -|------------|----------------|--------------|--------| -| Webservice | 2Gi | 2.5Gi | - | -| Sidekiq | 1.5Gi | 2Gi | - | -| Gitaly | 512Mi | 1Gi | 10Gi | -| PostgreSQL | 512Mi | 1Gi | 10Gi | -| Redis | 256Mi | 512Mi | 10Gi | -| Shell | 64Mi | 128Mi | - | -| Toolbox | 256Mi | 512Mi | - | -| **Total** | ~5Gi | ~8Gi | 30Gi | - -**Object Storage (externo):** -- Hetzner Object Storage (S3-compatible) -- Uploads, artifacts, LFS, registry images -- Custo: €0.006/GB/mês (paga por uso) - -## Distribuição Inteligente de Pods - -O GitLab usa **anti-affinity preferencial** para se adaptar automaticamente ao cluster: - -```yaml -gitlab: - webservice: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchLabels: - app: sidekiq - topologyKey: kubernetes.io/hostname -``` - -### Comportamento Adaptativo - -| Cenário | Comportamento | -|---------|---------------| -| 1 nó grande (8GB+) | Tudo roda junto no mesmo nó | -| Múltiplos nós pequenos | Distribui automaticamente | -| Sem recursos suficientes | Cluster Autoscaler cria nós novos | - -### Por que preferencial ao invés de obrigatório? - -- **Obrigatório** (`required`): pods ficam Pending se não houver nós separados -- **Preferencial** (`preferred`): funciona com qualquer topologia, otimiza quando possível - -### Lição do Workshop - -> "Configurações adaptativas são melhores que rígidas. O anti-affinity preferencial -> permite que o GitLab funcione em um nó grande ou distribua em vários pequenos, -> sem intervenção manual." - -## Padrão de Domínios - -| Serviço | Domínio | -|---------|---------| -| Web UI | {GITLAB_HOST} (ex: git.kube.quest) | -| Registry | registry.{DOMAIN} (ex: registry.kube.quest) | -| SSH | {GITLAB_HOST}:22 | - -Exemplo com hostname `git.kube.quest`: -- https://git.kube.quest -- https://registry.kube.quest -- git@git.kube.quest - -## Arquivo de Configuração - -O `setup.sh` gera um arquivo `.env` (herda configuração de TLS da aula-09): +O Gitea inclui um Container Registry OCI integrado. Sem Harbor, sem MinIO, sem componentes extras. ```bash -# aula-10/.env (gerado automaticamente) -GITLAB_HOST=git.kube.quest -REGISTRY_HOST=registry.kube.quest -DOMAIN=kube.quest -USE_CLOUDFLARE=true -USE_LETSENCRYPT=false -LETSENCRYPT_EMAIL= +# Login no registry +docker login gitea.kube.quest -# Hetzner Object Storage (use a mesma região do cluster) -S3_ENDPOINT=nbg1.your-objectstorage.com -S3_ACCESS_KEY=xxxxx -S3_SECRET_KEY=xxxxx -S3_BUCKET=gitlab-workshop -S3_REGION=nbg1 +# Push de imagem +docker tag minha-app:v1 gitea.kube.quest/usuario/minha-app:v1 +docker push gitea.kube.quest/usuario/minha-app:v1 +``` + +### 2. Gitea Actions (CI/CD) + +CI/CD integrado com sintaxe GitHub Actions. Sem Tekton, sem CRDs extras, sem DSL proprietária. + +```yaml +# .gitea/workflows/build.yml +name: Build +on: + push: + branches: [main] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: | + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login gitea.kube.quest -u ${{ gitea.actor }} --password-stdin + docker build -t gitea.kube.quest/${{ gitea.repository }}:${{ github.sha }} . + docker push gitea.kube.quest/${{ gitea.repository }}:${{ github.sha }} +``` + +### 3. SSH + +```bash +# Clonar via SSH +git clone git@gitea.kube.quest:usuario/projeto.git + +# Configurar chave SSH: Settings → SSH/GPG Keys ``` ## Acesso @@ -227,148 +229,119 @@ S3_REGION=nbg1 ### Web UI ``` -URL: https://git.{domain} -Usuário: root -Senha: (ver abaixo) -``` - -### Obter senha do root - -```bash -kubectl get secret gitlab-gitlab-initial-root-password \ - -n gitlab \ - -o jsonpath='{.data.password}' | base64 -d; echo -``` - -### SSH - -```bash -# Clonar repositório via SSH -git clone git@git.kube.quest:grupo/projeto.git - -# Configurar chave SSH no GitLab -# Settings → SSH Keys → Adicionar sua chave pública +URL: https://gitea.kube.quest +Usuário: gitea_admin +Senha: (exibida no final do setup.sh) ``` ### Container Registry ```bash -# Login no registry -docker login registry.kube.quest +docker login gitea.kube.quest +# Username: gitea_admin +# Password: (mesma senha do admin) +``` -# Push de imagem -docker tag minha-app:v1 registry.kube.quest/grupo/projeto:v1 -docker push registry.kube.quest/grupo/projeto:v1 +### Verificar Runner + +```bash +# Pod do runner +kubectl get pods -n gitea -l app.kubernetes.io/name=actions + +# Logs +kubectl logs -n gitea -l app.kubernetes.io/name=actions -c act-runner -f + +# Workflows executados +# Via UI: https://gitea.kube.quest/{owner}/{repo}/actions ``` ## Comandos Úteis ```bash # Ver todos os pods -kubectl get pods -n gitlab +kubectl get pods -n gitea -# Ver logs do webservice -kubectl logs -n gitlab -l app=webservice -f +# Ver logs do Gitea +kubectl logs -n gitea -l app.kubernetes.io/name=gitea -f -# Ver logs do sidekiq -kubectl logs -n gitlab -l app=sidekiq -f - -# Reiniciar componente -kubectl rollout restart deployment -n gitlab gitlab-webservice-default - -# Rails console -kubectl exec -it -n gitlab \ - $(kubectl get pod -n gitlab -l app=toolbox -o name | head -1) \ - -- gitlab-rails console - -# Backup -kubectl exec -it -n gitlab \ - $(kubectl get pod -n gitlab -l app=toolbox -o name | head -1) \ - -- backup-utility +# Reiniciar Gitea +kubectl rollout restart deployment -n gitea gitea # Ver certificados (se Let's Encrypt) -kubectl get certificate -n gitlab +kubectl get certificate -n gitea -# Desinstalar (apenas GitLab) +# Desinstalar ./cleanup.sh ``` -**Nota:** O `cleanup.sh` remove apenas GitLab, clientes e cert-manager. A infraestrutura (CSI Driver, NGINX Ingress, LoadBalancer) é mantida pois pertence à aula-08. - -## Configurações Adicionais - -### Adicionar GitLab Runner - -```bash -# Obter registration token -kubectl get secret gitlab-gitlab-runner-secret \ - -n gitlab \ - -o jsonpath='{.data.runner-registration-token}' | base64 -d; echo - -# Instalar runner -helm install gitlab-runner gitlab/gitlab-runner \ - --namespace gitlab \ - --set gitlabUrl=https://git.kube.quest \ - --set runnerRegistrationToken= -``` - ## Troubleshooting ### Pods não iniciam ```bash -# Verificar eventos -kubectl get events -n gitlab --sort-by='.lastTimestamp' - -# Verificar PVCs -kubectl get pvc -n gitlab - -# Verificar se CSI driver está funcionando -kubectl get pods -n kube-system -l app=hcloud-csi +kubectl get events -n gitea --sort-by='.lastTimestamp' +kubectl get pvc -n gitea ``` ### SSH não conecta ```bash -# Verificar se porta 22 está no ConfigMap +# Verificar TCP passthrough kubectl get configmap tcp-services -n ingress-nginx -o yaml - -# Verificar gitlab-shell pod -kubectl get pods -n gitlab -l app=gitlab-shell ``` ### Registry não funciona ```bash -# Verificar pod do registry -kubectl get pods -n gitlab -l app=registry -kubectl logs -n gitlab -l app=registry +curl -v https://gitea.kube.quest/v2/ +``` -# Testar internamente -kubectl run test --rm -it --image=busybox -- wget -qO- http://gitlab-registry:5000/v2/ +### Runner não executa workflows + +```bash +# Verificar se o runner está registrado +kubectl logs -n gitea -l app.kubernetes.io/name=actions -c act-runner --tail=20 + +# Verificar se DinD está rodando +kubectl logs -n gitea -l app.kubernetes.io/name=actions -c dind --tail=10 + +# Erro "Docker connection failed" → DinD não compartilha socket +# Verificar se extraVolumeMounts do dind inclui docker-socket +``` + +### Runner crashando (OOM) + +O Gitea pode usar mais memória durante migrações de repos grandes. Se ocorrer OOM: + +```bash +# Aumentar limit temporariamente +helm upgrade gitea gitea-charts/gitea -n gitea \ + --reuse-values \ + --set resources.limits.memory=1Gi ``` ## Custos -| Cenário | Workers | Custo/mês | -|---------|---------|-----------| -| 1x CAX21 (8GB) | 1 nó | ~$8.49 | -| 2x CAX11 (4GB) | 2 nós (distribuído) | ~$9.18 | - | Recurso | Custo/mês | |---------|-----------| -| Volumes (3x 10Gi = 30Gi) | ~$1.45 | -| Object Storage (~10GB uso) | ~€0.06 | +| Worker (1x CAX11) | ~$4.59 | +| Volumes (~26Gi) | ~$1.26 | | LoadBalancer (compartilhado) | ~$1.80 (1/3) | +| **Total** | **~$7.65** | -**Nota:** O anti-affinity preferencial permite ambos os cenários. -O Cluster Autoscaler provisiona nós automaticamente quando necessário. +## Lições do Workshop -**Vantagem Object Storage:** Sem volume MinIO (economia de ~$0.50/mês) + storage ilimitado. +1. **Nem sempre precisa da ferramenta "enterprise"** — Gitea substitui GitLab + Harbor + parcialmente Tekton com 10x menos recursos +2. **Registry integrado** é suficiente para a maioria dos casos — Harbor justifica-se quando você precisa de vulnerability scanning, replicação multi-site, ou compliance (assinatura de imagens) +3. **Sintaxe GitHub Actions é portável** — o que você aprende aqui transfere direto pro GitHub, e vice-versa +4. **Docker-in-Docker no Kubernetes** requer namespace com PodSecurity `privileged` — é o trade-off pra ter builds Docker no cluster ## Referências -- [GitLab Helm Chart](https://docs.gitlab.com/charts/) -- [GitLab Helm Chart Values](https://gitlab.com/gitlab-org/charts/gitlab/-/blob/master/values.yaml) -- [External Ingress](https://docs.gitlab.com/charts/advanced/external-ingress/) -- [NGINX TCP Services](https://kubernetes.github.io/ingress-nginx/user-guide/exposing-tcp-udp-services/) +- [Gitea Docs](https://docs.gitea.com/) +- [Gitea Helm Chart](https://gitea.com/gitea/helm-chart) +- [Gitea Container Registry](https://docs.gitea.com/usage/packages/container) +- [Gitea Actions](https://docs.gitea.com/usage/actions/overview) +- [act_runner](https://docs.gitea.com/usage/actions/act-runner) +- [ArtifactHub - Gitea](https://artifacthub.io/packages/helm/gitea/gitea) +- [ArtifactHub - Gitea Actions](https://artifacthub.io/packages/helm/gitea/actions) diff --git a/aula-10/cleanup.sh b/aula-10/cleanup.sh index 6c9f7ab..f7302a2 100755 --- a/aula-10/cleanup.sh +++ b/aula-10/cleanup.sh @@ -1,13 +1,12 @@ #!/bin/bash # ============================================================================= -# Cleanup da Aula 10 - Remove GitLab +# Cleanup da Aula 10 - Remove Gitea # ============================================================================= # # Este script remove: -# - GitLab (Helm release) +# - Gitea (Helm release) # - PVCs (dados persistentes) -# - Secrets -# - Namespace gitlab +# - Namespace gitea # - cert-manager (se instalado por esta aula) # - Arquivo .env # @@ -16,7 +15,7 @@ # - Hetzner CSI Driver # - LoadBalancer # -# ATENÇÃO: Todos os dados do GitLab serão perdidos! +# ATENÇÃO: Todos os dados do Gitea serão perdidos! # # ============================================================================= @@ -39,13 +38,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo "" echo -e "${CYAN}============================================${NC}" -echo -e "${CYAN} Cleanup - Aula 10 (GitLab)${NC}" +echo -e "${CYAN} Cleanup - Aula 10 (Gitea)${NC}" echo -e "${CYAN}============================================${NC}" echo "" log_warn "ATENÇÃO: Isso vai remover os recursos da aula 10:" -echo " - GitLab e todos os componentes" -echo " - Volumes persistentes (PostgreSQL, Redis, Gitaly)" +echo " - Gitea e todos os componentes" +echo " - Volumes persistentes (PostgreSQL, Valkey, Gitea)" echo " - cert-manager (se instalado)" echo " - Arquivo .env" echo "" @@ -80,37 +79,33 @@ else fi # ============================================================================= -# 2. DESINSTALAR GITLAB +# 2. DESINSTALAR GITEA # ============================================================================= -log_info "=== Removendo GitLab ===" +log_info "=== Removendo Gitea ===" -if helm status gitlab -n gitlab &> /dev/null; then - log_info "Desinstalando GitLab via Helm..." - helm uninstall gitlab -n gitlab --wait 2>/dev/null || true - log_success "GitLab removido" +if helm status gitea -n gitea &> /dev/null; then + log_info "Desinstalando Gitea via Helm..." + helm uninstall gitea -n gitea --wait 2>/dev/null || true + log_success "Gitea removido" else - log_info "GitLab não está instalado" + log_info "Gitea não está instalado" fi # Remover PVCs -if kubectl get pvc -n gitlab &> /dev/null 2>&1; then - log_info "Removendo PVCs do namespace gitlab..." - kubectl delete pvc --all -n gitlab --wait=false 2>/dev/null || true +if kubectl get pvc -n gitea &> /dev/null 2>&1; then + log_info "Removendo PVCs do namespace gitea..." + kubectl delete pvc --all -n gitea --wait=false 2>/dev/null || true fi # Aguardar PVs serem liberados sleep 5 -# Remover secrets restantes -log_info "Removendo secrets..." -kubectl delete secret --all -n gitlab 2>/dev/null || true - # Remover namespace -if kubectl get namespace gitlab &> /dev/null; then - log_info "Removendo namespace gitlab..." - kubectl delete namespace gitlab --wait=false 2>/dev/null || true - log_success "Namespace gitlab removido" +if kubectl get namespace gitea &> /dev/null; then + log_info "Removendo namespace gitea..." + kubectl delete namespace gitea --wait=false 2>/dev/null || true + log_success "Namespace gitea removido" fi # ============================================================================= @@ -122,13 +117,10 @@ log_info "=== Verificando cert-manager ===" if helm status cert-manager -n cert-manager &> /dev/null; then log_info "Removendo cert-manager..." - # Remover ClusterIssuer primeiro kubectl delete clusterissuer --all 2>/dev/null || true - # Remover helm release helm uninstall cert-manager -n cert-manager --wait 2>/dev/null || true - # Remover CRDs kubectl delete crd \ certificates.cert-manager.io \ certificaterequests.cert-manager.io \ @@ -138,7 +130,6 @@ if helm status cert-manager -n cert-manager &> /dev/null; then orders.acme.cert-manager.io \ 2>/dev/null || true - # Remover namespace kubectl delete namespace cert-manager --wait=false 2>/dev/null || true log_success "cert-manager removido" @@ -169,7 +160,7 @@ echo -e "${GREEN} Cleanup Concluído!${NC}" echo -e "${CYAN}============================================${NC}" echo "" echo "Removido:" -echo " - GitLab e todos os componentes" +echo " - Gitea e todos os componentes" echo " - cert-manager (se existia)" echo " - Arquivo .env" echo "" diff --git a/aula-10/gitea-values.yaml b/aula-10/gitea-values.yaml new file mode 100644 index 0000000..81d6677 --- /dev/null +++ b/aula-10/gitea-values.yaml @@ -0,0 +1,207 @@ +# ============================================================================= +# Gitea Helm Chart - Configuração para Hetzner CAX11 +# ============================================================================= +# +# Esta configuração: +# - Usa NGINX Ingress Controller externo (instalado na aula-08) +# - PostgreSQL standalone (sem HA) para economia de recursos +# - Valkey standalone (sem cluster) para economia de recursos +# - Container Registry (packages) habilitado +# - Gitea Actions habilitado +# - ~800Mi de recursos para Gitea + ~256Mi PostgreSQL + ~64Mi Valkey +# - act_runner instalado separadamente via gitea-charts/actions +# +# Valores dinâmicos (configurados via --set no setup.sh): +# - ingress.hosts[0].host +# - gitea.config.server.DOMAIN +# - gitea.config.server.ROOT_URL +# - gitea.config.server.SSH_DOMAIN +# +# ============================================================================= + +# Réplicas +replicaCount: 1 + +# Imagem rootless (mais segura) +image: + rootless: true + +# ============================================================================= +# RECURSOS - Otimizado para CAX11 (4GB RAM) +# ============================================================================= +resources: + requests: + memory: 512Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 500m + +# ============================================================================= +# INGRESS - Usa NGINX Ingress Controller externo (aula-08) +# ============================================================================= +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/proxy-read-timeout: "900" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "900" + hosts: + - host: git.example.com # Sobrescrito via --set + paths: + - path: / + # TLS configurado via --set no setup.sh + +# ============================================================================= +# SERVICE - HTTP e SSH +# ============================================================================= +service: + http: + type: ClusterIP + port: 3000 + ssh: + type: ClusterIP + port: 22 + +# ============================================================================= +# PERSISTENCE +# ============================================================================= +persistence: + enabled: true + size: 10Gi + storageClass: hcloud-volumes + +# ============================================================================= +# GITEA CONFIG (mapeia para app.ini) +# ============================================================================= +gitea: + admin: + username: gitea_admin + password: "" # Gerado automaticamente pelo setup.sh + email: "admin@local.domain" + passwordMode: initialOnlyNoReset + + config: + # Servidor + server: + PROTOCOL: http + SSH_PORT: 22 + SSH_LISTEN_PORT: 2222 # Rootless image escuta nesta porta + LFS_START_SERVER: true + OFFLINE_MODE: false + + # Segurança + security: + INSTALL_LOCK: true + + # Serviço + service: + DISABLE_REGISTRATION: false + REQUIRE_SIGNIN_VIEW: false + DEFAULT_KEEP_EMAIL_PRIVATE: true + + # Repositório + repository: + DEFAULT_BRANCH: main + DEFAULT_PRIVATE: private + + # Container Registry (Packages) + packages: + ENABLED: true + + # Gitea Actions (CI/CD) + actions: + ENABLED: true + DEFAULT_ACTIONS_URL: https://github.com + + # Timezone + time: + DEFAULT_UI_LOCATION: America/Sao_Paulo + + # Probes ajustadas para cluster pequeno + livenessProbe: + enabled: true + tcpSocket: + port: http + initialDelaySeconds: 60 + timeoutSeconds: 3 + periodSeconds: 10 + failureThreshold: 10 + + readinessProbe: + enabled: true + tcpSocket: + port: http + initialDelaySeconds: 10 + timeoutSeconds: 3 + periodSeconds: 10 + failureThreshold: 3 + +# ============================================================================= +# POSTGRESQL - Standalone (sem HA, economia de recursos) +# ============================================================================= +postgresql-ha: + enabled: false + +postgresql: + enabled: true + global: + postgresql: + auth: + password: gitea + database: gitea + username: gitea + service: + ports: + postgresql: 5432 + image: + repository: bitnamilegacy/postgresql + primary: + persistence: + size: 10Gi + storageClass: hcloud-volumes + resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 256Mi + cpu: 250m + +# ============================================================================= +# VALKEY (Redis) - Standalone (sem cluster, economia de recursos) +# ============================================================================= +valkey-cluster: + enabled: false + +valkey: + enabled: true + architecture: standalone + image: + repository: bitnamilegacy/valkey + global: + valkey: + password: gitea + master: + count: 1 + service: + ports: + valkey: 6379 + persistence: + size: 1Gi + storageClass: hcloud-volumes + resources: + requests: + memory: 32Mi + cpu: 25m + limits: + memory: 64Mi + cpu: 100m + +# ============================================================================= +# TESTES +# ============================================================================= +checkDeprecation: true +test: + enabled: false diff --git a/aula-10/gitlab-values.yaml b/aula-10/gitlab-values.yaml index 1e92d20..f9390c2 100644 --- a/aula-10/gitlab-values.yaml +++ b/aula-10/gitlab-values.yaml @@ -4,7 +4,7 @@ # # Esta configuração: # - Usa NGINX Ingress Controller externo (instalado na aula-08) -# - Define ~7.5GB de recursos para forçar nodes dedicados via autoscaler +# - Define ~5GB de recursos distribuídos em 2 workers CAX11 (antiAffinity) # - Desabilita componentes não essenciais para economizar recursos # - Configura Registry para container images # @@ -31,6 +31,10 @@ # ============================================================================= global: + # Desabilitar MinIO interno (migrado para Hetzner Object Storage) + minio: + enabled: false + # Usar Ingress Controller externo ingress: class: nginx @@ -52,36 +56,6 @@ global: seccompProfile: type: RuntimeDefault - # Object Storage - Hetzner S3 (em vez de MinIO) - minio: - enabled: false - appConfig: - object_store: - enabled: true - proxy_download: true - connection: - secret: gitlab-object-storage - key: connection - # Bucket único para todos os tipos de objeto - # O GitLab organiza internamente por pastas/prefixos - # Os valores abaixo são sobrescritos via --set no setup.sh usando S3_BUCKET do .env - uploads: - bucket: gitlab-storage - artifacts: - bucket: gitlab-storage - lfs: - bucket: gitlab-storage - packages: - bucket: gitlab-storage - externalDiffs: - bucket: gitlab-storage - terraformState: - bucket: gitlab-storage - ciSecureFiles: - bucket: gitlab-storage - dependencyProxy: - bucket: gitlab-storage - # Email (opcional - configurar depois) # email: # from: gitlab@kube.quest @@ -106,38 +80,9 @@ nginx-ingress: gitlab: # Webservice (Rails app - UI e API) - # Anti-affinity preferencial: distribui se possível, mas não obriga - # - 1 nó grande (8GB): tudo roda junto - # - Múltiplos nós pequenos: distribui automaticamente - # - Sem recursos: autoscaler cria nós novos + # NOTA: antiAffinity garante que webservice e sidekiq rodem em nós diferentes + # Isso evita OOM quando ambos competem por memória no mesmo nó CAX11 (4GB) webservice: - minReplicas: 1 - maxReplicas: 1 - resources: - requests: - memory: 2.5Gi - cpu: 300m - limits: - memory: 3Gi - cpu: 1 - workerProcesses: 1 - puma: - threads: - min: 1 - max: 2 - # Anti-affinity preferencial: tenta separar de sidekiq, mas não obriga - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchLabels: - app: sidekiq - topologyKey: kubernetes.io/hostname - - # Sidekiq (background jobs) - sidekiq: minReplicas: 1 maxReplicas: 1 resources: @@ -146,29 +91,83 @@ gitlab: cpu: 200m limits: memory: 2.5Gi + cpu: 1 + workerProcesses: 1 + puma: + threads: + min: 1 + max: 2 + # Anti-affinity: não rodar no mesmo nó que sidekiq + # Node affinity: preferir nodes do pool gitlab-pool (CAX21 com 8GB) + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: hcloud/node-group + operator: In + values: + - gitlab-pool + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: sidekiq + topologyKey: kubernetes.io/hostname + + # Sidekiq (background jobs) + # Anti-affinity: não rodar no mesmo nó que webservice + sidekiq: + minReplicas: 1 + maxReplicas: 1 + resources: + requests: + memory: 1.5Gi + cpu: 100m + limits: + memory: 2Gi cpu: 500m # Desabilitar memory watchdog interno do GitLab (deixa o OOM killer do K8s gerenciar) memoryKiller: maxRss: 2000000000 # 2GB - maior que o limite para evitar kills prematuros - # Anti-affinity preferencial: tenta separar de webservice, mas não obriga + # Node affinity: preferir nodes do pool gitlab-pool (CAX21 com 8GB) affinity: - podAntiAffinity: + nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 - podAffinityTerm: - labelSelector: - matchLabels: - app: webservice - topologyKey: kubernetes.io/hostname + preference: + matchExpressions: + - key: hcloud/node-group + operator: In + values: + - gitlab-pool + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: webservice + topologyKey: kubernetes.io/hostname # Gitaly (Git storage) gitaly: + # Node affinity: preferir nodes do pool gitlab-pool (CAX21 com 8GB) + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: hcloud/node-group + operator: In + values: + - gitlab-pool resources: requests: - memory: 1Gi - cpu: 150m + memory: 512Mi + cpu: 100m limits: - memory: 1.5Gi + memory: 1Gi cpu: 500m persistence: size: 10Gi # Mínimo Hetzner ($0.0484/GB) @@ -220,12 +219,22 @@ gitlab: postgresql: install: true primary: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: hcloud/node-group + operator: In + values: + - gitlab-pool resources: requests: - memory: 1Gi - cpu: 150m + memory: 512Mi + cpu: 100m limits: - memory: 1.5Gi + memory: 1Gi cpu: 500m persistence: size: 10Gi # Mínimo Hetzner ($0.0484/GB) @@ -239,34 +248,35 @@ redis: master: resources: requests: - memory: 512Mi - cpu: 100m + memory: 256Mi + cpu: 50m limits: - memory: 1Gi - cpu: 300m + memory: 512Mi + cpu: 200m persistence: size: 10Gi # Mínimo Hetzner ($0.0484/GB) storageClass: hcloud-volumes # ============================================================================= -# OBJECT STORAGE (Hetzner Object Storage - S3 compatible) +# MINIO (Object Storage) - DESABILITADO # ============================================================================= -# Usamos o Hetzner Object Storage ao invés do MinIO bundled. -# Vantagens: -# - Sem volume persistente (economia de $0.50/mês) -# - Sem pod MinIO (economia de recursos) -# - Storage ilimitado (paga por uso: €0.006/GB) -# - Alta disponibilidade gerenciada pela Hetzner +# Migrado para Hetzner Object Storage para resolver problema de espaço (89% cheio) +# O Registry agora usa S3 externo (Hetzner Object Storage) # -# Pré-requisito: criar bucket e credenciais na Hetzner Console -# O setup.sh cria o Secret gitlab-object-storage automaticamente - +# Para voltar ao MinIO interno (rollback): +# 1. Mudar minio.install: true +# 2. Remover registry.storage configuração +# 3. Re-deploy GitLab minio: install: false # ============================================================================= -# REGISTRY (Container Registry) +# REGISTRY (Container Registry) - Usando Hetzner Object Storage # ============================================================================= +# IMPORTANTE: Antes de fazer deploy, criar o secret: +# kubectl apply -f gitlab-registry-storage-secret.yaml +# +# O secret contém as credenciais S3 para o Hetzner Object Storage registry: enabled: true hpa: @@ -279,7 +289,7 @@ registry: limits: memory: 256Mi cpu: 200m - # Storage usa Hetzner Object Storage (configurado via global.appConfig.object_store) + # Storage configurado para Hetzner Object Storage (S3 compatível) storage: secret: gitlab-registry-storage key: config diff --git a/aula-10/setup.sh b/aula-10/setup.sh index 8f4c050..a294e05 100755 --- a/aula-10/setup.sh +++ b/aula-10/setup.sh @@ -1,11 +1,11 @@ #!/bin/bash # ============================================================================= -# Setup da Aula 10 - GitLab via Helm (Hetzner Cloud) +# Setup da Aula 10 - Gitea via Helm (Hetzner Cloud) # ============================================================================= # # Este script instala e configura: # 1. cert-manager (opcional, para Let's Encrypt) -# 2. GitLab com todos os componentes +# 2. Gitea com Container Registry, SSH e Actions # # Pré-requisitos: # - Kubernetes cluster Talos na Hetzner (aula-08) com: @@ -39,19 +39,12 @@ log_error() { echo -e "${RED}[ERRO]${NC} $1"; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Variáveis de configuração -GITLAB_HOST="" -REGISTRY_HOST="" # Derivado automaticamente -DOMAIN="" # Extraído automaticamente do GITLAB_HOST +GITEA_HOST="" +DOMAIN="" USE_CLOUDFLARE="" USE_LETSENCRYPT="" LETSENCRYPT_EMAIL="" - -# Hetzner Object Storage -S3_ENDPOINT="" -S3_ACCESS_KEY="" -S3_SECRET_KEY="" -S3_BUCKET="" -S3_REGION="" +ADMIN_PASSWORD="" # ============================================================================= # FUNÇÕES DE CONFIGURAÇÃO @@ -61,19 +54,11 @@ save_config() { cat > "$SCRIPT_DIR/.env" </dev/null || echo "") - # Extrair nome do host (ex: git.kube.quest → git) - GITLAB_NAME=$(echo "$GITLAB_HOST" | cut -d. -f1) - REGISTRY_NAME=$(echo "$REGISTRY_HOST" | sed "s/\.${DOMAIN}$//" ) + GITEA_NAME=$(echo "$GITEA_HOST" | cut -d. -f1) echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" @@ -367,12 +242,7 @@ show_dns_instructions() { echo "No painel do CloudFlare (https://dash.cloudflare.com):" echo "" echo -e " ${YELLOW}Tipo:${NC} A" - echo -e " ${YELLOW}Nome:${NC} ${GITLAB_NAME}" - echo -e " ${YELLOW}Conteúdo:${NC} ${GREEN}${LB_IP}${NC}" - echo -e " ${YELLOW}Proxy:${NC} ✓ (ícone laranja)" - echo "" - echo -e " ${YELLOW}Tipo:${NC} A" - echo -e " ${YELLOW}Nome:${NC} ${REGISTRY_NAME}" + echo -e " ${YELLOW}Nome:${NC} ${GITEA_NAME}" echo -e " ${YELLOW}Conteúdo:${NC} ${GREEN}${LB_IP}${NC}" echo -e " ${YELLOW}Proxy:${NC} ✓ (ícone laranja)" echo "" @@ -382,11 +252,7 @@ show_dns_instructions() { echo "No seu provedor DNS:" echo "" echo -e " ${YELLOW}Tipo:${NC} A" - echo -e " ${YELLOW}Nome:${NC} ${GITLAB_NAME}" - echo -e " ${YELLOW}Valor:${NC} ${GREEN}${LB_IP}${NC}" - echo "" - echo -e " ${YELLOW}Tipo:${NC} A" - echo -e " ${YELLOW}Nome:${NC} ${REGISTRY_NAME}" + echo -e " ${YELLOW}Nome:${NC} ${GITEA_NAME}" echo -e " ${YELLOW}Valor:${NC} ${GREEN}${LB_IP}${NC}" if [[ "$USE_LETSENCRYPT" == "true" ]]; then @@ -395,15 +261,6 @@ show_dns_instructions() { echo -e "Aguarde ~2 minutos após configurar o DNS." fi fi - - # Determinar protocolo - PROTOCOL="https" - if [[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]]; then - PROTOCOL="http" - fi - - echo "" - echo -e "Acesse: ${GREEN}${PROTOCOL}://${GITLAB_HOST}${NC}" } # ============================================================================= @@ -436,7 +293,6 @@ log_success "Conectado ao cluster Kubernetes" log_info "=== Verificando infraestrutura (aula-08) ===" -# Verificar Hetzner CSI Driver if ! kubectl get storageclass hcloud-volumes &> /dev/null; then log_error "StorageClass hcloud-volumes não encontrado!" log_error "Execute primeiro o setup.sh da aula-08 para instalar o CSI Driver." @@ -444,7 +300,6 @@ if ! kubectl get storageclass hcloud-volumes &> /dev/null; then fi log_success "Hetzner CSI Driver instalado (hcloud-volumes)" -# Verificar NGINX Ingress if ! kubectl get ingressclass nginx &> /dev/null; then log_error "NGINX Ingress não encontrado!" log_error "Execute primeiro o setup.sh da aula-08 para instalar o Ingress Controller." @@ -463,41 +318,7 @@ collect_user_input echo "" # ============================================================================= -# 3. MOSTRAR DNS E AGUARDAR CONFIGURAÇÃO (ANTES DO GITLAB!) -# ============================================================================= -# IMPORTANTE: Para Let's Encrypt funcionar, o DNS precisa estar configurado -# ANTES da instalação do GitLab. O HTTP-01 challenge requer que: -# 1. O DNS aponte para o LoadBalancer -# 2. O cert-manager crie o pod acme-http-solver -# 3. Let's Encrypt acesse http://dominio/.well-known/acme-challenge/token - -log_info "=== Configuração de DNS ===" - -show_dns_instructions - -echo "" -echo -e "${YELLOW}════════════════════════════════════════════════════════════════${NC}" -echo -e "${YELLOW} IMPORTANTE: Configure o DNS AGORA antes de continuar!${NC}" -echo -e "${YELLOW}════════════════════════════════════════════════════════════════${NC}" -echo "" -if [[ "$USE_LETSENCRYPT" == "true" ]]; then - echo -e "O Let's Encrypt precisa que o DNS já esteja configurado para" - echo -e "validar o domínio via HTTP-01 challenge." - echo "" -fi -echo -n "Pressione ENTER após configurar o DNS (ou 's' para pular): " -read -r dns_confirm - -if [[ "$dns_confirm" == "s" || "$dns_confirm" == "S" ]]; then - log_warn "DNS não configurado - certificados podem falhar!" -else - log_success "DNS confirmado pelo usuário" -fi - -echo "" - -# ============================================================================= -# 4. INSTALAR CERT-MANAGER (se Let's Encrypt) +# 3. INSTALAR CERT-MANAGER (se Let's Encrypt) # ============================================================================= if [[ "$USE_LETSENCRYPT" == "true" ]]; then @@ -508,111 +329,89 @@ if [[ "$USE_LETSENCRYPT" == "true" ]]; then fi # ============================================================================= -# 5. ADICIONAR REPOSITÓRIO HELM +# 4. ADICIONAR REPOSITÓRIO HELM # ============================================================================= -log_info "=== Preparando instalação do GitLab ===" +log_info "=== Preparando instalação do Gitea ===" -log_info "Adicionando repositório Helm do GitLab..." -helm repo add gitlab https://charts.gitlab.io/ 2>/dev/null || true +log_info "Adicionando repositório Helm do Gitea..." +helm repo add gitea-charts https://dl.gitea.com/charts/ 2>/dev/null || true helm repo update # ============================================================================= -# 6. CRIAR NAMESPACE +# 5. CRIAR NAMESPACE # ============================================================================= -log_info "Criando namespace gitlab..." -kubectl create namespace gitlab --dry-run=client -o yaml | kubectl apply -f - +log_info "Criando namespace gitea..." +kubectl create namespace gitea --dry-run=client -o yaml | kubectl apply -f - echo "" # ============================================================================= -# 7. CRIAR SECRETS DO OBJECT STORAGE +# 6. GERAR SENHA DO ADMIN # ============================================================================= -create_object_storage_secrets - -echo "" +ADMIN_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 16) # ============================================================================= -# 8. INSTALAR GITLAB VIA HELM +# 7. DETERMINAR PROTOCOLO E ROOT_URL # ============================================================================= -log_info "=== Instalando GitLab (isso pode levar 10-15 minutos) ===" +PROTOCOL="https" +if [[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]]; then + PROTOCOL="http" +fi -# Construir argumentos do Helm dinamicamente +ROOT_URL="${PROTOCOL}://${GITEA_HOST}/" + +# ============================================================================= +# 8. INSTALAR GITEA VIA HELM +# ============================================================================= + +log_info "=== Instalando Gitea (isso leva ~2-3 minutos) ===" + +# Construir argumentos do Helm HELM_ARGS="" -# Detectar cert-manager já instalado (ex: aula-09) -# Se existe, desabilitar o bundled para evitar conflito de CRDs -if kubectl get crd certificates.cert-manager.io &> /dev/null; then - log_info "cert-manager detectado - usando instalação existente" - HELM_ARGS="$HELM_ARGS --set installCertmanager=false" -fi +# Hosts e domínio +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].host=${GITEA_HOST}" +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].path=/" +HELM_ARGS="$HELM_ARGS --set gitea.config.server.DOMAIN=${GITEA_HOST}" +HELM_ARGS="$HELM_ARGS --set gitea.config.server.ROOT_URL=${ROOT_URL}" +HELM_ARGS="$HELM_ARGS --set gitea.config.server.SSH_DOMAIN=${GITEA_HOST}" -# Configurar hosts -HELM_ARGS="$HELM_ARGS --set global.hosts.domain=${DOMAIN}" -HELM_ARGS="$HELM_ARGS --set global.hosts.gitlab.name=${GITLAB_HOST}" -HELM_ARGS="$HELM_ARGS --set global.hosts.registry.name=${REGISTRY_HOST}" -# MinIO desabilitado - usando Hetzner Object Storage +# Senha do admin +HELM_ARGS="$HELM_ARGS --set gitea.admin.password=${ADMIN_PASSWORD}" -# Configurar TLS +# TLS if [[ "$USE_LETSENCRYPT" == "true" ]]; then - # Let's Encrypt: TLS gerenciado pelo cert-manager - HELM_ARGS="$HELM_ARGS --set global.ingress.configureCertmanager=true" - HELM_ARGS="$HELM_ARGS --set global.ingress.tls.enabled=true" - HELM_ARGS="$HELM_ARGS --set global.hosts.https=true" - HELM_ARGS="$HELM_ARGS --set certmanager-issuer.email=${LETSENCRYPT_EMAIL}" - # Adicionar annotation do ClusterIssuer - HELM_ARGS="$HELM_ARGS --set global.ingress.annotations.cert-manager\\.io/cluster-issuer=letsencrypt-prod" + HELM_ARGS="$HELM_ARGS --set ingress.tls[0].secretName=gitea-tls" + HELM_ARGS="$HELM_ARGS --set ingress.tls[0].hosts[0]=${GITEA_HOST}" + HELM_ARGS="$HELM_ARGS --set ingress.annotations.cert-manager\\.io/cluster-issuer=letsencrypt-prod" elif [[ "$USE_CLOUDFLARE" == "true" ]]; then - # CloudFlare: TLS na edge, backend HTTP - # Workhorse precisa confiar nos IPs do CloudFlare para X-Forwarded-For - HELM_ARGS="$HELM_ARGS --set global.ingress.configureCertmanager=false" - HELM_ARGS="$HELM_ARGS --set certmanager-issuer.install=false" - HELM_ARGS="$HELM_ARGS --set global.ingress.tls.enabled=false" - HELM_ARGS="$HELM_ARGS --set global.hosts.https=true" - # CloudFlare IPv4 CIDRs (https://www.cloudflare.com/ips-v4) - CLOUDFLARE_CIDRS='["173.245.48.0/20","103.21.244.0/22","103.22.200.0/22","103.31.4.0/22","141.101.64.0/18","108.162.192.0/18","190.93.240.0/20","188.114.96.0/20","197.234.240.0/22","198.41.128.0/17","162.158.0.0/15","104.16.0.0/13","104.24.0.0/14","172.64.0.0/13","131.0.72.0/22"]' - HELM_ARGS="$HELM_ARGS --set-json gitlab.webservice.workhorse.trustedCIDRsForXForwardedFor='${CLOUDFLARE_CIDRS}'" -else - # Apenas HTTP - HELM_ARGS="$HELM_ARGS --set global.ingress.configureCertmanager=false" - HELM_ARGS="$HELM_ARGS --set certmanager-issuer.install=false" - HELM_ARGS="$HELM_ARGS --set global.ingress.tls.enabled=false" - HELM_ARGS="$HELM_ARGS --set global.hosts.https=false" + # CloudFlare: TLS na edge, backend HTTP — sem configuração extra no Gitea + : fi -# Configurar bucket único para todos os tipos de objeto -# O GitLab organiza internamente por pastas/prefixos -HELM_ARGS="$HELM_ARGS --set global.appConfig.uploads.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.artifacts.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.lfs.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.packages.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.externalDiffs.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.terraformState.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.ciSecureFiles.bucket=${S3_BUCKET}" -HELM_ARGS="$HELM_ARGS --set global.appConfig.dependencyProxy.bucket=${S3_BUCKET}" - # Verificar se já está instalado -if helm status gitlab -n gitlab &> /dev/null; then - log_warn "GitLab já está instalado. Atualizando..." - eval helm upgrade gitlab gitlab/gitlab \ - --namespace gitlab \ - -f "$SCRIPT_DIR/gitlab-values.yaml" \ +if helm status gitea -n gitea &> /dev/null; then + log_warn "Gitea já está instalado. Atualizando..." + eval helm upgrade gitea gitea-charts/gitea \ + --namespace gitea \ + -f "$SCRIPT_DIR/gitea-values.yaml" \ $HELM_ARGS \ - --timeout 15m \ + --timeout 10m \ --wait - log_success "GitLab atualizado com sucesso!" + log_success "Gitea atualizado com sucesso!" else - log_info "Instalando GitLab..." - eval helm install gitlab gitlab/gitlab \ - --namespace gitlab \ - -f "$SCRIPT_DIR/gitlab-values.yaml" \ + log_info "Instalando Gitea..." + eval helm install gitea gitea-charts/gitea \ + --namespace gitea \ + -f "$SCRIPT_DIR/gitea-values.yaml" \ $HELM_ARGS \ - --timeout 15m \ + --timeout 10m \ --wait - log_success "GitLab instalado com sucesso!" + log_success "Gitea instalado com sucesso!" fi echo "" @@ -623,126 +422,82 @@ echo "" log_info "=== Configurando TCP passthrough para SSH ===" -# Verificar se ConfigMap existe if kubectl get configmap tcp-services -n ingress-nginx &> /dev/null; then kubectl patch configmap tcp-services \ -n ingress-nginx \ --type merge \ - -p '{"data":{"22":"gitlab/gitlab-gitlab-shell:22"}}' + -p '{"data":{"22":"gitea/gitea-ssh:22"}}' else kubectl create configmap tcp-services \ -n ingress-nginx \ - --from-literal="22=gitlab/gitlab-gitlab-shell:22" + --from-literal="22=gitea/gitea-ssh:22" fi # Reiniciar NGINX para aplicar kubectl rollout restart deployment ingress-nginx-controller -n ingress-nginx -log_success "TCP passthrough configurado (porta 22 → GitLab Shell)" +log_success "TCP passthrough configurado (porta 22 → Gitea SSH)" echo "" -# ============================================================================= -# 10. OBTER SENHA INICIAL -# ============================================================================= - -log_info "Obtendo senha inicial do root..." - -# Aguardar secret ser criado e obter senha (até 2 min) -ROOT_PASSWORD="" -for i in {1..60}; do - ROOT_PASSWORD=$(kubectl get secret gitlab-gitlab-initial-root-password \ - -n gitlab \ - -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null) - if [[ -n "$ROOT_PASSWORD" ]]; then - log_success "Senha obtida" - break - fi - sleep 2 -done - # ============================================================================= # RESUMO FINAL # ============================================================================= -# Determinar protocolo -PROTOCOL="https" -if [[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]]; then - PROTOCOL="http" -fi - echo "" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" -echo -e "${GREEN} GitLab Instalado com Sucesso!${NC}" +echo -e "${GREEN} Gitea Instalado com Sucesso!${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" echo "" echo "Componentes instalados:" if [[ "$USE_LETSENCRYPT" == "true" ]]; then echo " - cert-manager (ClusterIssuer: letsencrypt)" fi -echo " - GitLab (namespace: gitlab)" -echo " - Webservice (UI + API)" -echo " - Sidekiq (background jobs)" -echo " - Gitaly (Git storage)" -echo " - GitLab Shell (SSH)" -echo " - PostgreSQL" -echo " - Redis" -echo " - Hetzner Object Storage (S3)" -echo " - Container Registry" +echo " - Gitea (namespace: gitea)" +echo " - Web UI + API + Git" +echo " - Container Registry (packages)" +echo " - SSH (porta 22)" +echo " - Gitea Actions (CI/CD)" +echo " - PostgreSQL (standalone)" +echo " - Valkey (cache)" echo "" -# Determinar tipo de TLS para exibição -if [[ "$USE_LETSENCRYPT" == "true" ]]; then - TLS_TYPE="Let's Encrypt" -elif [[ "$USE_CLOUDFLARE" == "true" ]]; then - TLS_TYPE="CloudFlare" -else - TLS_TYPE="Sem TLS (HTTP)" -fi - echo "Configuração:" -echo " GitLab: ${GITLAB_HOST}" -echo " Registry: ${REGISTRY_HOST}" +echo " Gitea: ${GITEA_HOST}" +echo " Registry: ${GITEA_HOST} (integrado)" echo " Domínio: ${DOMAIN}" -echo " TLS: ${TLS_TYPE}" +echo " CloudFlare: ${USE_CLOUDFLARE}" +echo " Let's Encrypt: ${USE_LETSENCRYPT}" echo "" echo "URLs:" -echo " Web: ${PROTOCOL}://${GITLAB_HOST}" -echo " Registry: ${PROTOCOL}://${REGISTRY_HOST}" -echo " SSH: git@${GITLAB_HOST} (porta 22)" +echo " Web: ${PROTOCOL}://${GITEA_HOST}" +echo " Registry: ${PROTOCOL}://${GITEA_HOST}" +echo " SSH: git@${GITEA_HOST} (porta 22)" echo "" echo "Credenciais:" -echo " Usuário: root" -if [ -n "$ROOT_PASSWORD" ]; then - echo " Senha: $ROOT_PASSWORD" -else - echo " Senha: (execute o comando abaixo)" - echo "" - echo " kubectl get secret gitlab-gitlab-initial-root-password -n gitlab \\" - echo " -o jsonpath='{.data.password}' | base64 -d; echo" -fi - -# Lembrete de DNS (referência) +echo " Usuário: gitea_admin" +echo " Senha: ${ADMIN_PASSWORD}" echo "" -echo -e "${CYAN}Lembrete DNS:${NC}" -echo " ${GITLAB_HOST} → LoadBalancer IP" -echo " ${REGISTRY_HOST} → LoadBalancer IP" +echo -e "${YELLOW}⚠ Guarde a senha! Ela não pode ser recuperada depois.${NC}" +# Mostrar instruções de DNS +show_dns_instructions + +echo "" +echo "Container Registry:" +echo " # Login" +echo " docker login ${GITEA_HOST}" +echo "" +echo " # Push de imagem" +echo " docker tag minha-app:v1 ${GITEA_HOST}/usuario/minha-app:v1" +echo " docker push ${GITEA_HOST}/usuario/minha-app:v1" echo "" echo "Comandos úteis:" echo " # Ver pods" -echo " kubectl get pods -n gitlab" +echo " kubectl get pods -n gitea" echo "" echo " # Ver logs" -echo " kubectl logs -n gitlab -l app=webservice -f" +echo " kubectl logs -n gitea -l app.kubernetes.io/name=gitea -f" echo "" -echo " # Reiniciar componente" -echo " kubectl rollout restart deployment -n gitlab gitlab-webservice-default" -echo "" -if [[ "$USE_LETSENCRYPT" == "true" ]]; then - echo " # Ver status dos certificados" - echo " kubectl get certificate -n gitlab" - echo "" -fi echo " # Desinstalar" echo " ./cleanup.sh" echo "" @@ -751,4 +506,4 @@ echo "" # Mostrar status dos pods log_info "Status dos pods:" -kubectl get pods -n gitlab +kubectl get pods -n gitea diff --git a/aula-11/README.md b/aula-11/README.md index 25c3cb0..656f9b3 100644 --- a/aula-11/README.md +++ b/aula-11/README.md @@ -1,23 +1,23 @@ -# Aula 11 - ArgoCD + GitLab Runner (GitOps) +# Aula 11 - ArgoCD (GitOps) -Deploy de ArgoCD e GitLab Runner para pipeline CI/CD completo com GitOps. +Deploy do ArgoCD para CD declarativo com GitOps, integrado ao Gitea (aula-10). ## Arquitetura ``` ┌─────────────────────────────────────────────────────────────┐ -│ GitLab (aula-10) │ +│ Gitea (aula-10) │ │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │ -│ │ git push │───►│ GitLab CI │───►│ Registry │ │ -│ │ (código) │ │ (Runner K8s)│ │ (imagem:tag) │ │ +│ │ git push │───►│ Gitea Actions│───►│ Registry │ │ +│ │ (código) │ │ (act_runner) │ │ (imagem:tag) │ │ │ └─────────────┘ └──────┬───────┘ └───────────────┘ │ └────────────────────────────┼────────────────────────────────┘ │ atualiza manifests ▼ ┌─────────────────────────────────────────────────────────────┐ -│ Repositório GitOps │ +│ Repositório GitOps │ │ apps/node-bugado/ │ -│ ├── deployment.yaml (image: registry.../node-bugado:sha) │ +│ ├── deployment.yaml (image: gitea.../node-bugado:sha) │ │ ├── service.yaml │ │ └── configmap.yaml │ └─────────────────────────────────────────────────────────────┘ @@ -25,7 +25,7 @@ Deploy de ArgoCD e GitLab Runner para pipeline CI/CD completo com GitOps. │ sync automático ▼ ┌─────────────────────────────────────────────────────────────┐ -│ ArgoCD │ +│ ArgoCD │ │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │ │ │ Application │───►│ Sync │───►│ Kubernetes │ │ │ │ CRD │ │ Controller │ │ (deploy) │ │ @@ -38,7 +38,7 @@ Deploy de ArgoCD e GitLab Runner para pipeline CI/CD completo com GitOps. 1. **Cluster Talos na Hetzner** (aula-08) com: - NGINX Ingress Controller com LoadBalancer - Hetzner CSI Driver -2. **GitLab instalado** (aula-10) +2. **Gitea instalado** (aula-10) 3. **kubectl** e **helm** instalados ## Contexto do Cluster @@ -60,9 +60,8 @@ chmod +x setup.sh ``` O script instala: -1. **GitLab Runner** - Executor Kubernetes para pipelines CI -2. **ArgoCD** - GitOps CD para Kubernetes -3. Configura integração SSH com GitLab +1. **ArgoCD** - GitOps CD para Kubernetes +2. Configura integração SSH com Gitea ## Componentes Instalados @@ -72,8 +71,7 @@ O script instala: | ArgoCD Repo Server | argocd | 256Mi/512Mi | Git clone/sync | | ArgoCD Controller | argocd | 256Mi/512Mi | Reconciliation | | ArgoCD Redis | argocd | 64Mi/128Mi | Cache | -| GitLab Runner | gitlab | 128Mi/256Mi | CI jobs como pods | -| **Total** | | ~1.2Gi | | +| **Total** | | **~832Mi** | | ## Acesso @@ -86,21 +84,9 @@ Senha: kubectl get secret argocd-initial-admin-secret -n argocd \ -o jsonpath='{.data.password}' | base64 -d ``` -### GitLab Runner - -Verificar status em: `https://{gitlab-host}/admin/runners` - -```bash -# Ver pods do runner -kubectl get pods -n gitlab -l app=gitlab-runner - -# Ver logs -kubectl logs -n gitlab -l app=gitlab-runner -f -``` - ## Configurar Pipeline GitOps -### 1. Criar Repositório GitOps no GitLab +### 1. Criar Repositório GitOps no Gitea Crie um repositório `gitops-demo` com a estrutura: @@ -122,9 +108,9 @@ Use os arquivos de exemplo em `node-bugado/k8s/`. # Gerar par de chaves ssh-keygen -t ed25519 -f argocd-deploy-key -N '' -# Adicionar chave pública no GitLab: -# Settings → Repository → Deploy Keys -# Marcar "Grant write permissions" +# Adicionar chave pública no Gitea: +# Repositório → Settings → Deploy Keys +# Marcar "Enable Write Access" (se o CI precisa push) ``` ### 3. Conectar Repositório no ArgoCD @@ -133,12 +119,12 @@ Via UI: 1. Acesse https://argocd.{domain} 2. Settings → Repositories → Connect Repo 3. Method: SSH -4. URL: `git@git.{domain}:{usuario}/gitops-demo.git` +4. URL: `git@gitea.{domain}:{usuario}/gitops-demo.git` 5. SSH private key: (conteúdo de argocd-deploy-key) Ou via CLI: ```bash -argocd repo add git@git.kube.quest:usuario/gitops-demo.git \ +argocd repo add git@gitea.kube.quest:usuario/gitops-demo.git \ --ssh-private-key-path argocd-deploy-key ``` @@ -154,7 +140,7 @@ metadata: spec: project: default source: - repoURL: git@git.kube.quest:usuario/gitops-demo.git + repoURL: git@gitea.kube.quest:usuario/gitops-demo.git targetRevision: HEAD path: apps/node-bugado destination: @@ -171,84 +157,49 @@ EOF ### 5. Configurar Pipeline no Repositório da App -No repositório `node-bugado`: +No repositório `node-bugado`, crie `.gitea/workflows/ci.yml`: -1. Copie `.gitlab-ci.yml` de `node-bugado/.gitlab-ci.yml` -2. Configure variáveis em Settings → CI/CD → Variables: - - `GITOPS_REPO`: `git@git.kube.quest:usuario/gitops-demo.git` - - `DEPLOY_KEY`: Chave SSH privada (com write access ao repo gitops) +```yaml +name: Build and Deploy +on: + push: + branches: [main] +env: + REGISTRY: gitea.kube.quest +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} -u ${{ gitea.actor }} --password-stdin + - run: | + docker build -t ${{ env.REGISTRY }}/${{ gitea.repository }}:${{ github.sha }} . + docker push ${{ env.REGISTRY }}/${{ gitea.repository }}:${{ github.sha }} + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - run: | + # Clone GitOps repo e atualizar tag + # ArgoCD sincroniza automaticamente +``` + +Configure secrets em: Repository → Settings → Actions → Secrets: +- `REGISTRY_TOKEN`: Token com permissão de push no registry +- `DEPLOY_KEY`: Chave SSH privada (com write access ao repo gitops) ## Fluxo de Deploy 1. **Desenvolvedor** faz push no repositório `node-bugado` -2. **GitLab CI** dispara pipeline: +2. **Gitea Actions** (act_runner) dispara pipeline: - Build: Constrói imagem Docker - - Push: Envia para GitLab Registry + - Push: Envia para Gitea Container Registry - Deploy: Atualiza `deployment.yaml` no repo GitOps 3. **ArgoCD** detecta mudança no repo GitOps 4. **ArgoCD** sincroniza com cluster Kubernetes 5. **Kubernetes** faz rolling update dos pods -## GitLab Runner - Executor Kubernetes - -O runner usa executor `kubernetes`, onde cada job CI: - -- Roda como um **pod efêmero** no cluster -- Tem acesso a **Docker-in-Docker** para builds -- É automaticamente **limpo** após conclusão -- **Escala** conforme demanda de jobs - -```yaml -# gitlab-runner-values.yaml -runners: - executor: kubernetes - privileged: true # Necessário para Docker-in-Docker - namespace: gitlab -``` - -### Por que Docker-in-Docker? - -Para construir imagens Docker dentro de um container (o job do CI), precisamos: -1. **Privileged mode**: Acesso ao kernel para criar containers -2. **Docker daemon**: Serviço Docker rodando no job - -Alternativas (mais seguras, mas mais complexas): -- **Kaniko**: Build sem Docker daemon -- **Buildah**: Build rootless - -### Requisitos para Docker-in-Docker - -#### 1. Pod Security (Kubernetes 1.25+) - -Kubernetes 1.25+ aplica Pod Security Admission por padrão. O namespace `gitlab` -precisa permitir pods privilegiados: - -```bash -kubectl label namespace gitlab \ - pod-security.kubernetes.io/enforce=privileged \ - pod-security.kubernetes.io/warn=privileged \ - --overwrite -``` - -> **Nota**: O `setup.sh` já configura isso automaticamente. - -#### 2. Helper Image para ARM64 - -Em clusters com nodes ARM64 (como Hetzner CAX), o runner precisa usar -o helper image correto. Configure em `gitlab-runner-values.yaml`: - -```toml -# Dentro de runners.config -[[runners]] - [runners.kubernetes] - helper_image = "gitlab/gitlab-runner-helper:arm64-latest" -``` - -Sem isso, você verá erros como: -``` -no match for platform in manifest: not found -``` - ## Troubleshooting ### ArgoCD não sincroniza @@ -264,65 +215,6 @@ kubectl describe application node-bugado -n argocd kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller ``` -### Runner não aparece no GitLab - -```bash -# Verificar pod do runner -kubectl get pods -n gitlab -l app=gitlab-runner - -# Ver logs -kubectl logs -n gitlab -l app=gitlab-runner - -# Verificar registration token -kubectl get secret gitlab-gitlab-runner-secret -n gitlab -o yaml -``` - -### Jobs CI falham - -```bash -# Ver pods dos jobs -kubectl get pods -n gitlab - -# Ver logs de um job específico -kubectl logs -n gitlab runner-xxxxx-project-xxx-concurrent-xxx -``` - -### Erro "violates PodSecurity" - -``` -violates PodSecurity "baseline:latest": privileged -(containers must not set securityContext.privileged=true) -``` - -**Solução**: Configure o namespace para permitir pods privilegiados: -```bash -kubectl label namespace gitlab \ - pod-security.kubernetes.io/enforce=privileged \ - --overwrite -``` - -### Erro "no match for platform in manifest" - -``` -image pull failed: no match for platform in manifest: not found -``` - -**Causa**: O runner está tentando usar imagem x86_64 em node ARM64. - -**Solução**: Configure o helper image ARM64 no `gitlab-runner-values.yaml`: -```toml -[[runners]] - [runners.kubernetes] - helper_image = "gitlab/gitlab-runner-helper:arm64-latest" -``` - -Depois faça upgrade do runner: -```bash -helm upgrade gitlab-runner gitlab/gitlab-runner \ - -n gitlab --reuse-values \ - -f gitlab-runner-values.yaml -``` - ### Erro SSH ao conectar repositório ```bash @@ -330,7 +222,7 @@ helm upgrade gitlab-runner gitlab/gitlab-runner \ kubectl get configmap argocd-ssh-known-hosts-cm -n argocd -o yaml # Adicionar manualmente -ssh-keyscan git.kube.quest | kubectl create configmap argocd-ssh-known-hosts-cm \ +ssh-keyscan gitea.kube.quest | kubectl create configmap argocd-ssh-known-hosts-cm \ --from-file=ssh_known_hosts=/dev/stdin -n argocd --dry-run=client -o yaml | kubectl apply -f - ``` @@ -343,10 +235,6 @@ kubectl get pods -n argocd argocd app list argocd app sync node-bugado -# GitLab Runner -kubectl get pods -n gitlab -l app=gitlab-runner -kubectl logs -n gitlab -l app=gitlab-runner -f - # Ver todos os recursos do demo kubectl get all -n demo @@ -360,12 +248,10 @@ argocd app diff node-bugado ## Lições do Workshop 1. **GitOps**: Git como fonte única de verdade para estado do cluster -2. **Separação CI/CD**: GitLab CI = build, ArgoCD = deploy -3. **Runner Kubernetes**: Jobs como pods efêmeros e escaláveis -4. **Docker-in-Docker**: Build de imagens em containers privilegiados -5. **Auditoria**: Histórico de deploys = histórico Git -6. **Self-Heal**: ArgoCD corrige drift automaticamente -7. **Segurança**: Deploy Keys com permissão mínima +2. **Separação CI/CD**: Gitea Actions = build, ArgoCD = deploy +3. **Auditoria**: Histórico de deploys = histórico Git +4. **Self-Heal**: ArgoCD corrige drift automaticamente +5. **Segurança**: Deploy Keys com permissão mínima ## Cleanup @@ -373,25 +259,17 @@ argocd app diff node-bugado ./cleanup.sh ``` -Remove ArgoCD e GitLab Runner. Não remove GitLab ou infraestrutura base. +Remove ArgoCD. Não remove Gitea ou infraestrutura base. ## Custos | Recurso | Custo/mês | |---------|-----------| -| ArgoCD + Runner (~1.2Gi) | Usa workers existentes | +| ArgoCD (~832Mi) | Usa workers existentes | | **Total Adicional** | ~$0 | -## Próximos Passos - -- **Aula 12**: eStargz + Lazy Pulling - - Converter imagens para formato eStargz - - Demonstrar startup mais rápido com lazy pulling - - Medir diferença de performance - ## Referências - [ArgoCD Docs](https://argo-cd.readthedocs.io/en/stable/) - [ArgoCD Helm Chart](https://github.com/argoproj/argo-helm) -- [GitLab Runner Kubernetes Executor](https://docs.gitlab.com/runner/executors/kubernetes.html) -- [GitOps with GitLab + ArgoCD](https://medium.com/@andrew.kaczynski/gitops-in-kubernetes-argo-cd-and-gitlab-ci-cd-5828c8eb34d6) +- [Gitea Actions](https://docs.gitea.com/usage/actions/overview) diff --git a/aula-11/cleanup.sh b/aula-11/cleanup.sh index bcb1df3..987fa3f 100755 --- a/aula-11/cleanup.sh +++ b/aula-11/cleanup.sh @@ -3,8 +3,8 @@ # Aula 11 - Cleanup # ============================================================================= # -# Remove ArgoCD e GitLab Runner instalados pelo setup.sh. -# NÃO remove a infraestrutura base (GitLab, NGINX Ingress, etc). +# Remove ArgoCD instalado pelo setup.sh. +# NÃO remove a infraestrutura base (Gitea, NGINX Ingress, etc). # # ============================================================================= @@ -23,7 +23,7 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1"; } echo "" echo "==========================================" -echo " Removendo ArgoCD e GitLab Runner" +echo " Removendo ArgoCD" echo "==========================================" echo "" @@ -44,18 +44,9 @@ fi log_info "Removendo namespace argocd..." kubectl delete namespace argocd --timeout=60s 2>/dev/null || true -# Remover GitLab Runner -log_info "Removendo GitLab Runner..." -if helm status gitlab-runner -n gitlab &> /dev/null; then - helm uninstall gitlab-runner -n gitlab --wait - log_success "GitLab Runner removido" -else - log_warn "GitLab Runner não estava instalado" -fi - # Limpar secrets residuais log_info "Limpando secrets residuais..." -kubectl delete secret argocd-ssh-known-hosts-cm -n argocd 2>/dev/null || true +kubectl delete configmap argocd-ssh-known-hosts-cm -n argocd 2>/dev/null || true # Remover arquivo .env local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -67,6 +58,6 @@ fi echo "" log_success "Cleanup concluído!" echo "" -echo "Nota: GitLab, NGINX Ingress e infraestrutura base foram mantidos." -echo "Para remover o GitLab, execute: ../aula-10/cleanup.sh" +echo "Nota: Gitea, NGINX Ingress e infraestrutura base foram mantidos." +echo "Para remover o Gitea, execute: ../aula-10/cleanup.sh" echo "" diff --git a/aula-11/gitlab-runner-values.yaml b/aula-11/gitlab-runner-values.yaml index 809e2d0..197d536 100644 --- a/aula-11/gitlab-runner-values.yaml +++ b/aula-11/gitlab-runner-values.yaml @@ -61,10 +61,11 @@ runners: helper_image = "gitlab/gitlab-runner-helper:arm64-latest" # Recursos para pods de job (aumentados para builds Docker) - cpu_request = "100m" - cpu_limit = "1000m" - memory_request = "256Mi" - memory_limit = "1Gi" + # CAX31 tem 8 vCPU e 16GB - aproveitar para builds rápidos + cpu_request = "500m" + cpu_limit = "4000m" + memory_request = "1Gi" + memory_limit = "8Gi" # Timeout para pods poll_timeout = 600 @@ -72,6 +73,17 @@ runners: # Pull policy pull_policy = ["if-not-present"] + # Node selector para usar o build-pool (CAX31) + [runners.kubernetes.node_selector] + "node-pool" = "build" + + # Toleration para o taint do build-pool + [[runners.kubernetes.node_tolerations]] + key = "dedicated" + operator = "Equal" + value = "builds" + effect = "NoSchedule" + # Volume para Docker certs (DinD) [[runners.kubernetes.volumes.empty_dir]] name = "docker-certs" diff --git a/aula-11/node-bugado/.gitea/workflows/ci.yml b/aula-11/node-bugado/.gitea/workflows/ci.yml new file mode 100644 index 0000000..3d98cf7 --- /dev/null +++ b/aula-11/node-bugado/.gitea/workflows/ci.yml @@ -0,0 +1,77 @@ +# ============================================================================= +# Gitea Actions Workflow - node-bugado +# ============================================================================= +# +# Pipeline GitOps: +# 1. Build: Constrói imagem Docker e faz push para Gitea Registry +# 2. Deploy: Atualiza manifests no repo GitOps (ArgoCD faz sync) +# +# Secrets necessários (Repository → Settings → Actions → Secrets): +# - REGISTRY_TOKEN: Token para push no Gitea Container Registry +# - DEPLOY_KEY: Chave SSH privada para push no repo GitOps +# - GITOPS_REPO: URL SSH do repo GitOps (ex: git@gitea.kube.quest:user/gitops-demo.git) +# +# ============================================================================= + +name: Build and Deploy + +on: + push: + branches: [main] + +env: + REGISTRY: gitea.kube.quest + IMAGE_NAME: ${{ gitea.repository }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Login to Gitea Registry + run: | + echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ env.REGISTRY }} \ + -u ${{ gitea.actor }} --password-stdin + + - name: Build and push + run: | + echo "Building ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}" + docker build \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ + . + docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - name: Setup SSH + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan gitea.kube.quest >> ~/.ssh/known_hosts 2>/dev/null || true + + - name: Update GitOps repo + run: | + git config --global user.email "ci@gitea.kube.quest" + git config --global user.name "Gitea CI" + + git clone ${{ secrets.GITOPS_REPO }} gitops + cd gitops + + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8) + + if [ -f apps/node-bugado/deployment.yaml ]; then + sed -i "s|image:.*node-bugado.*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}|g" apps/node-bugado/deployment.yaml + git add apps/node-bugado/deployment.yaml + git commit -m "deploy: node-bugado ${SHORT_SHA} [skip ci]" + git push + echo "GitOps repo updated" + else + echo "WARNING: apps/node-bugado/deployment.yaml not found" + exit 1 + fi diff --git a/aula-11/setup.sh b/aula-11/setup.sh index 90eb15e..93f0d37 100755 --- a/aula-11/setup.sh +++ b/aula-11/setup.sh @@ -1,16 +1,15 @@ #!/bin/bash # ============================================================================= -# Aula 11 - ArgoCD + GitLab Runner (GitOps) +# Aula 11 - ArgoCD (GitOps) # ============================================================================= # # Este script instala: -# 1. GitLab Runner (executor kubernetes) para CI -# 2. ArgoCD para CD declarativo (GitOps) -# 3. Integração com GitLab self-hosted (aula-10) +# 1. ArgoCD para CD declarativo (GitOps) +# 2. Integração SSH com Gitea (aula-10) # # Pré-requisitos: # - Cluster Kubernetes (aula-08) -# - GitLab instalado (aula-10) +# - Gitea instalado (aula-10) # - NGINX Ingress Controller # - kubectl e helm instalados # @@ -23,16 +22,13 @@ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' -CYAN='\033[0;36m' -NC='\033[0m' # No Color +NC='\033[0m' -# Funções de log log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } -# Diretório do script SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${SCRIPT_DIR}/.env" @@ -42,80 +38,51 @@ ENV_FILE="${SCRIPT_DIR}/.env" 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 helm if ! command -v helm &> /dev/null; then log_error "helm não encontrado. Instale com: brew install helm" 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" log_info "Exemplo: export KUBECONFIG=\$(pwd)/../aula-08/kubeconfig" exit 1 fi -# Verificar se GitLab está instalado -if ! kubectl get namespace gitlab &> /dev/null; then - log_error "Namespace 'gitlab' não encontrado" - log_info "Execute primeiro a aula-10 para instalar o GitLab" +# 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 -# Verificar NGINX Ingress if ! kubectl get ingressclass nginx &> /dev/null; then log_error "NGINX Ingress Controller não encontrado" - log_info "Execute o script de instalação do NGINX Ingress na aula-08" exit 1 fi log_success "Pré-requisitos verificados" # ============================================================================= -# CONFIGURAR POD SECURITY PARA DOCKER-IN-DOCKER -# ============================================================================= -# -# Docker-in-Docker requer pods privilegiados. Kubernetes 1.25+ aplica -# Pod Security Admission por padrão, bloqueando containers privilegiados -# no modo "baseline". Precisamos configurar o namespace gitlab para -# permitir pods privilegiados. -# -log_info "Configurando PodSecurity para Docker-in-Docker..." -kubectl label namespace gitlab \ - pod-security.kubernetes.io/enforce=privileged \ - pod-security.kubernetes.io/warn=privileged \ - pod-security.kubernetes.io/audit=privileged \ - --overwrite -log_success "PodSecurity configurado para permitir Docker-in-Docker" - -# ============================================================================= -# CARREGAR CONFIGURAÇÃO EXISTENTE +# CARREGAR CONFIGURAÇÃO # ============================================================================= -# 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 "$GITLAB_HOST" ]]; then +# Herdar da aula-10 +if [[ -z "$GITEA_HOST" ]]; then AULA10_ENV="${SCRIPT_DIR}/../aula-10/.env" if [[ -f "$AULA10_ENV" ]]; then log_info "Herdando configuração da aula-10..." source "$AULA10_ENV" - GITLAB_HOST="${GITLAB_HOST:-}" - DOMAIN="${DOMAIN:-}" - USE_CLOUDFLARE="${USE_CLOUDFLARE:-false}" - USE_LETSENCRYPT="${USE_LETSENCRYPT:-false}" - LETSENCRYPT_EMAIL="${LETSENCRYPT_EMAIL:-}" fi fi @@ -125,19 +92,19 @@ fi echo "" echo "==========================================" -echo " Configuração do ArgoCD + GitLab CI" +echo " Configuração do ArgoCD (GitOps)" echo "==========================================" echo "" -# GitLab Host -if [[ -z "$GITLAB_HOST" ]]; then - read -p "Hostname do GitLab (ex: git.kube.quest): " GITLAB_HOST +# Gitea Host +if [[ -z "$GITEA_HOST" ]]; then + read -p "Hostname do Gitea (ex: gitea.kube.quest): " GITEA_HOST fi -log_info "GitLab: https://${GITLAB_HOST}" +log_info "Gitea: https://${GITEA_HOST}" # Extrair domínio base if [[ -z "$DOMAIN" ]]; then - DOMAIN=$(echo "$GITLAB_HOST" | sed 's/^[^.]*\.//') + DOMAIN=$(echo "$GITEA_HOST" | sed 's/^[^.]*\.//') fi # ArgoCD Host @@ -148,7 +115,7 @@ if [[ -z "$ARGOCD_HOST" ]]; then fi log_info "ArgoCD: https://${ARGOCD_HOST}" -# TLS (herdar da aula-10 ou perguntar) +# TLS if [[ "$USE_CLOUDFLARE" != "true" && "$USE_LETSENCRYPT" != "true" ]]; then echo "" echo "Configuração de TLS:" @@ -158,21 +125,14 @@ if [[ "$USE_CLOUDFLARE" != "true" && "$USE_LETSENCRYPT" != "true" ]]; then read -p "Escolha [1-3]: " TLS_CHOICE case $TLS_CHOICE in - 1) - USE_CLOUDFLARE=true - USE_LETSENCRYPT=false - ;; + 1) USE_CLOUDFLARE=true; USE_LETSENCRYPT=false ;; 2) - USE_CLOUDFLARE=false - USE_LETSENCRYPT=true + USE_CLOUDFLARE=false; USE_LETSENCRYPT=true if [[ -z "$LETSENCRYPT_EMAIL" ]]; then read -p "Email para Let's Encrypt: " LETSENCRYPT_EMAIL fi ;; - *) - USE_CLOUDFLARE=false - USE_LETSENCRYPT=false - ;; + *) USE_CLOUDFLARE=false; USE_LETSENCRYPT=false ;; esac fi @@ -180,7 +140,7 @@ fi cat > "$ENV_FILE" << EOF # Configuração gerada pelo setup.sh # $(date) -GITLAB_HOST=${GITLAB_HOST} +GITEA_HOST=${GITEA_HOST} ARGOCD_HOST=${ARGOCD_HOST} DOMAIN=${DOMAIN} USE_CLOUDFLARE=${USE_CLOUDFLARE} @@ -190,65 +150,6 @@ EOF log_success "Configuração salva em ${ENV_FILE}" -# ============================================================================= -# INSTALAR GITLAB RUNNER -# ============================================================================= - -echo "" -log_info "=== Instalando GitLab Runner ===" - -# Adicionar repositório Helm -helm repo add gitlab https://charts.gitlab.io 2>/dev/null || true -helm repo update - -# Verificar se já está instalado -if helm status gitlab-runner -n gitlab &> /dev/null; then - log_warn "GitLab Runner já instalado, fazendo upgrade..." - HELM_CMD="upgrade" -else - HELM_CMD="install" -fi - -# Obter runner registration token -log_info "Obtendo token de registro do GitLab Runner..." - -# Tentar obter o token do secret -RUNNER_TOKEN="" -if kubectl get secret gitlab-gitlab-runner-secret -n gitlab &> /dev/null; then - RUNNER_TOKEN=$(kubectl get secret gitlab-gitlab-runner-secret -n gitlab -o jsonpath='{.data.runner-registration-token}' 2>/dev/null | base64 -d 2>/dev/null || echo "") -fi - -if [[ -z "$RUNNER_TOKEN" ]]; then - log_warn "Token de registro não encontrado automaticamente" - echo "" - echo "Para obter o token:" - echo " 1. Acesse https://${GITLAB_HOST}/admin/runners" - echo " 2. Clique em 'New instance runner'" - echo " 3. Copie o registration token" - echo "" - read -p "Cole o registration token: " RUNNER_TOKEN -fi - -if [[ -z "$RUNNER_TOKEN" ]]; then - log_error "Token de registro é obrigatório" - exit 1 -fi - -# Instalar GitLab Runner -log_info "Instalando GitLab Runner via Helm..." -helm ${HELM_CMD} gitlab-runner gitlab/gitlab-runner \ - --namespace gitlab \ - -f "${SCRIPT_DIR}/gitlab-runner-values.yaml" \ - --set gitlabUrl="https://${GITLAB_HOST}" \ - --set runnerRegistrationToken="${RUNNER_TOKEN}" \ - --wait --timeout 5m - -log_success "GitLab Runner instalado" - -# Verificar pods -log_info "Verificando pods do GitLab Runner..." -kubectl get pods -n gitlab -l app=gitlab-runner - # ============================================================================= # INSTALAR CERT-MANAGER (se Let's Encrypt) # ============================================================================= @@ -273,7 +174,6 @@ if [[ "$USE_LETSENCRYPT" == "true" ]]; then log_success "cert-manager já instalado" fi - # Criar ClusterIssuer se não existir if ! kubectl get clusterissuer letsencrypt-prod &> /dev/null; then log_info "Criando ClusterIssuer letsencrypt-prod..." kubectl apply -f - << EOF @@ -303,14 +203,11 @@ fi echo "" log_info "=== Instalando ArgoCD ===" -# Adicionar repositório Helm helm repo add argo https://argoproj.github.io/argo-helm 2>/dev/null || true helm repo update -# Criar namespace kubectl create namespace argocd 2>/dev/null || true -# Verificar se já está instalado if helm status argocd -n argocd &> /dev/null; then log_warn "ArgoCD já instalado, fazendo upgrade..." HELM_CMD="upgrade" @@ -318,7 +215,7 @@ else HELM_CMD="install" fi -# Construir argumentos Helm +# Argumentos Helm HELM_ARGS="" HELM_ARGS="$HELM_ARGS --set global.domain=${ARGOCD_HOST}" HELM_ARGS="$HELM_ARGS --set server.ingress.hosts[0]=${ARGOCD_HOST}" @@ -327,12 +224,8 @@ if [[ "$USE_LETSENCRYPT" == "true" ]]; then HELM_ARGS="$HELM_ARGS --set server.ingress.tls[0].secretName=argocd-server-tls" HELM_ARGS="$HELM_ARGS --set server.ingress.tls[0].hosts[0]=${ARGOCD_HOST}" HELM_ARGS="$HELM_ARGS --set 'server.ingress.annotations.cert-manager\.io/cluster-issuer=letsencrypt-prod'" -elif [[ "$USE_CLOUDFLARE" == "true" ]]; then - # CloudFlare faz TLS na borda - HELM_ARGS="$HELM_ARGS --set server.ingress.tls=" fi -# Instalar ArgoCD log_info "Instalando ArgoCD via Helm..." eval helm ${HELM_CMD} argocd argo/argo-cd \ --namespace argocd \ @@ -349,7 +242,6 @@ log_success "ArgoCD instalado" echo "" log_info "=== Credenciais do ArgoCD ===" -# Aguardar secret ser criado log_info "Aguardando secret de credenciais..." for i in {1..30}; do if kubectl get secret argocd-initial-admin-secret -n argocd &> /dev/null; then @@ -360,33 +252,17 @@ done ARGOCD_PASSWORD=$(kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || echo "") -if [[ -z "$ARGOCD_PASSWORD" ]]; then - log_warn "Não foi possível obter a senha inicial" - log_info "Tente: kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 -d" -else - echo "" - echo "==========================================" - echo " ArgoCD Credenciais" - echo "==========================================" - echo " URL: https://${ARGOCD_HOST}" - echo " Username: admin" - echo " Password: ${ARGOCD_PASSWORD}" - echo "==========================================" -fi - # ============================================================================= -# CONFIGURAR INTEGRAÇÃO GITLAB +# CONFIGURAR INTEGRAÇÃO GITEA # ============================================================================= echo "" -log_info "=== Configurando Integração GitLab ===" +log_info "=== Configurando Integração Gitea ===" -# Obter host key do GitLab -log_info "Obtendo SSH host key do GitLab..." -SSH_HOST_KEY=$(ssh-keyscan -t ed25519 ${GITLAB_HOST} 2>/dev/null || ssh-keyscan ${GITLAB_HOST} 2>/dev/null || echo "") +log_info "Obtendo SSH host key do Gitea..." +SSH_HOST_KEY=$(ssh-keyscan -t ed25519 ${GITEA_HOST} 2>/dev/null || ssh-keyscan ${GITEA_HOST} 2>/dev/null || echo "") if [[ -n "$SSH_HOST_KEY" ]]; then - # Criar ConfigMap com known hosts kubectl create configmap argocd-ssh-known-hosts-cm \ --from-literal=ssh_known_hosts="${SSH_HOST_KEY}" \ -n argocd \ @@ -398,7 +274,7 @@ else fi # ============================================================================= -# INSTRUÇÕES FINAIS +# RESUMO FINAL # ============================================================================= echo "" @@ -415,47 +291,12 @@ else echo " Password: kubectl get secret argocd-initial-admin-secret -n argocd -o jsonpath='{.data.password}' | base64 -d" fi echo "" -echo "GitLab Runner:" -echo " Namespace: gitlab" -echo " Verificar: kubectl get pods -n gitlab -l app=gitlab-runner" -echo " Status no GitLab: https://${GITLAB_HOST}/admin/runners" -echo "" echo "Próximos passos:" echo "" -# Obter IP do LoadBalancer -LB_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \ - -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") - -# Extrair nome do host -HOST_NAME=$(echo "$ARGOCD_HOST" | cut -d. -f1) - echo "1. Configure DNS:" +echo " Adicione registro A para ${ARGOCD_HOST} apontando para o LoadBalancer" echo "" -if [[ "$USE_CLOUDFLARE" == "true" ]]; then - echo " No painel do CloudFlare (https://dash.cloudflare.com):" - echo "" - echo -e " ${YELLOW}Tipo:${NC} A" - echo -e " ${YELLOW}Nome:${NC} ${HOST_NAME}" - echo -e " ${YELLOW}Conteúdo:${NC} ${GREEN}${LB_IP}${NC}" - echo -e " ${YELLOW}Proxy:${NC} ✓ (ícone laranja)" - echo "" - echo -e " ${GREEN}O CloudFlare cuida do TLS automaticamente!${NC}" -else - echo " No seu provedor DNS:" - echo "" - echo -e " ${YELLOW}Tipo:${NC} A" - echo -e " ${YELLOW}Nome:${NC} ${HOST_NAME}" - echo -e " ${YELLOW}Valor:${NC} ${GREEN}${LB_IP}${NC}" - if [[ "$USE_LETSENCRYPT" == "true" ]]; then - echo "" - echo -e " ${GREEN}Let's Encrypt irá emitir certificados automaticamente!${NC}" - echo -e " Aguarde ~2 minutos após configurar o DNS." - echo "" - read -rp " Pressione ENTER após configurar o DNS e aguardar a propagação... " - fi -fi -echo "" -echo "2. Crie um repositório GitOps no GitLab:" +echo "2. Crie um repositório GitOps no Gitea:" echo " - Nome: gitops-demo" echo " - Estrutura: apps/node-bugado/{deployment,service,configmap}.yaml" echo "" @@ -463,42 +304,21 @@ echo "3. Configure repositório no ArgoCD:" echo " a) Gere uma deploy key:" echo " ssh-keygen -t ed25519 -f argocd-deploy-key -N ''" echo "" -echo " b) Adicione a chave pública no GitLab:" -echo " Settings → Repository → Deploy Keys" +echo " b) Adicione a chave pública no Gitea:" +echo " Repositório → Settings → Deploy Keys" echo "" echo " c) Conecte o repositório no ArgoCD:" echo " - Acesse https://${ARGOCD_HOST}" echo " - Settings → Repositories → Connect Repo" echo " - Method: SSH" -echo " - URL: git@${GITLAB_HOST}:/gitops-demo.git" +echo " - URL: git@${GITEA_HOST}:/gitops-demo.git" echo " - SSH private key: (conteúdo de argocd-deploy-key)" echo "" -echo "4. Crie uma Application no ArgoCD:" -echo " kubectl apply -f - << 'EOF'" -echo " apiVersion: argoproj.io/v1alpha1" -echo " kind: Application" -echo " metadata:" -echo " name: node-bugado" -echo " namespace: argocd" -echo " spec:" -echo " project: default" -echo " source:" -echo " repoURL: git@${GITLAB_HOST}:/gitops-demo.git" -echo " targetRevision: HEAD" -echo " path: apps/node-bugado" -echo " destination:" -echo " server: https://kubernetes.default.svc" -echo " namespace: demo" -echo " syncPolicy:" -echo " automated:" -echo " prune: true" -echo " selfHeal: true" -echo " syncOptions:" -echo " - CreateNamespace=true" -echo " EOF" -echo "" echo "Comandos úteis:" echo " kubectl get pods -n argocd" echo " kubectl get applications -n argocd" -echo " kubectl logs -n gitlab -l app=gitlab-runner -f" echo "" + +# Status dos pods +log_info "Status dos pods:" +kubectl get pods -n argocd diff --git a/aula-13/README.md b/aula-13/README.md new file mode 100644 index 0000000..afd8c7b --- /dev/null +++ b/aula-13/README.md @@ -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 +``` diff --git a/aula-13/benchmarks/benchmark-clean.sh b/aula-13/benchmarks/benchmark-clean.sh new file mode 100755 index 0000000..4a0e967 --- /dev/null +++ b/aula-13/benchmarks/benchmark-clean.sh @@ -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 <>> Tempo total: ${TIME1}s <<<" + +# Teste 2: Imagem eStargz +echo "" +echo "========================================================================" +echo "[5/6] TESTE 2: Imagem eStargz (lazy pulling) - PULL REAL" +echo "========================================================================" +echo "Iniciando em $(date)" +T2_START=$(date +%s) + +cat <>> Tempo total: ${TIME2}s <<<" + +# Resultados +echo "" +echo "========================================================================" +echo "[6/6] RESULTADOS" +echo "========================================================================" +echo "" + +# Status dos pods +echo "Status dos Pods:" +kubectl get pods -n $NAMESPACE -o wide +echo "" + +# Eventos completos +echo "Todos os Eventos (ordenados por tempo):" +kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' \ + -o custom-columns='TIMESTAMP:.lastTimestamp,REASON:.reason,POD:.involvedObject.name,MESSAGE:.message' +echo "" + +# Verificar se houve pull real +echo "Análise de Pull:" +TRAD_PULL=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=pg-traditional,reason=Pulled -o jsonpath='{.items[0].message}' 2>/dev/null) +ESTARGZ_PULL=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=pg-estargz,reason=Pulled -o jsonpath='{.items[0].message}' 2>/dev/null) + +echo " Tradicional: $TRAD_PULL" +echo " eStargz: $ESTARGZ_PULL" +echo "" + +# Tabela de resultados +echo "┌─────────────────────────────────────────────────────────────────┐" +echo "│ RESULTADOS DO BENCHMARK (PULL REAL) │" +echo "├───────────────────┬─────────────────┬─────────────────────────────┤" +echo "│ Métrica │ Tradicional │ eStargz │" +echo "├───────────────────┼─────────────────┼─────────────────────────────┤" +printf "│ Tempo até Ready │ %12ss │ %12ss │\n" "$TIME1" "$TIME2" +echo "├───────────────────┼─────────────────┼─────────────────────────────┤" + +if [ "$TIME1" -gt "$TIME2" ]; then + DIFF=$((TIME1 - TIME2)) + PERCENT=$(( (DIFF * 100) / TIME1 )) + echo "│ Diferença │ baseline │ -${DIFF}s (${PERCENT}% mais rápido) │" +elif [ "$TIME2" -gt "$TIME1" ]; then + DIFF=$((TIME2 - TIME1)) + PERCENT=$(( (DIFF * 100) / TIME2 )) + echo "│ Diferença │ +${DIFF}s mais rápido │ baseline │" +else + echo "│ Diferença │ igual │ igual │" +fi + +echo "└───────────────────┴─────────────────┴─────────────────────────────┘" + +echo "" +echo "Nota: Este benchmark usou imagePullPolicy: Always no node '$TARGET_NODE'" +echo " que não tinha as imagens em cache, forçando pull real." +echo "" +echo "O benefício do eStargz (lazy pulling) é mais significativo em:" +echo " - Imagens maiores (1GB+)" +echo " - Scale-out events (novos nodes)" +echo " - Cold starts" +echo "" +echo "Para limpar: kubectl delete namespace $NAMESPACE" diff --git a/aula-13/benchmarks/benchmark-postgresql.sh b/aula-13/benchmarks/benchmark-postgresql.sh new file mode 100755 index 0000000..43bf263 --- /dev/null +++ b/aula-13/benchmarks/benchmark-postgresql.sh @@ -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" diff --git a/aula-13/benchmarks/benchmark-pull-only.sh b/aula-13/benchmarks/benchmark-pull-only.sh new file mode 100755 index 0000000..9fdfd74 --- /dev/null +++ b/aula-13/benchmarks/benchmark-pull-only.sh @@ -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 </dev/null) + if [ -n "$PULLED" ]; then + echo "RESULTADO: $PULLED" + break + fi + PULLING=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=test-traditional,reason=Pulling -o jsonpath='{.items[0].message}' 2>/dev/null) + if [ -n "$PULLING" ]; then + echo -n "." + fi + sleep 1 +done + +echo "" +echo "========================================================================" +echo "TESTE 2: Pull de Imagem eStargz (lazy pulling)" +echo "========================================================================" + +cat </dev/null) + if [ -n "$PULLED" ]; then + echo "RESULTADO: $PULLED" + break + fi + PULLING=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=test-estargz,reason=Pulling -o jsonpath='{.items[0].message}' 2>/dev/null) + if [ -n "$PULLING" ]; then + echo -n "." + fi + sleep 1 +done + +echo "" +echo "========================================================================" +echo "RESUMO" +echo "========================================================================" +echo "" +echo "Todos os eventos de pull:" +kubectl get events -n $NAMESPACE --sort-by='.lastTimestamp' \ + -o custom-columns='TIME:.lastTimestamp,REASON:.reason,POD:.involvedObject.name,MESSAGE:.message' \ + | grep -E "Pull|pull" + +echo "" +echo "Status dos pods:" +kubectl get pods -n $NAMESPACE -o wide + +echo "" +echo "Para limpar: kubectl delete namespace $NAMESPACE" diff --git a/aula-13/benchmarks/benchmark-toolbox.sh b/aula-13/benchmarks/benchmark-toolbox.sh new file mode 100755 index 0000000..d12bd24 --- /dev/null +++ b/aula-13/benchmarks/benchmark-toolbox.sh @@ -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 </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 "$@" diff --git a/aula-13/cleanup.sh b/aula-13/cleanup.sh new file mode 100755 index 0000000..f34ddb3 --- /dev/null +++ b/aula-13/cleanup.sh @@ -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 "" diff --git a/aula-13/images/devops-toolbox/.gitlab-ci.yml b/aula-13/images/devops-toolbox/.gitlab-ci.yml new file mode 100644 index 0000000..1bcdcbb --- /dev/null +++ b/aula-13/images/devops-toolbox/.gitlab-ci.yml @@ -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 diff --git a/aula-13/images/devops-toolbox/Dockerfile b/aula-13/images/devops-toolbox/Dockerfile new file mode 100644 index 0000000..0b78c3b --- /dev/null +++ b/aula-13/images/devops-toolbox/Dockerfile @@ -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"] diff --git a/aula-13/images/devops-toolbox/entrypoint.sh b/aula-13/images/devops-toolbox/entrypoint.sh new file mode 100644 index 0000000..cb957bc --- /dev/null +++ b/aula-13/images/devops-toolbox/entrypoint.sh @@ -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 [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 diff --git a/aula-13/images/large-test/.gitlab-ci.yml b/aula-13/images/large-test/.gitlab-ci.yml new file mode 100644 index 0000000..f516f86 --- /dev/null +++ b/aula-13/images/large-test/.gitlab-ci.yml @@ -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 diff --git a/aula-13/images/large-test/Dockerfile b/aula-13/images/large-test/Dockerfile new file mode 100644 index 0000000..d82b8a1 --- /dev/null +++ b/aula-13/images/large-test/Dockerfile @@ -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"] diff --git a/aula-13/images/large-test/entrypoint.sh b/aula-13/images/large-test/entrypoint.sh new file mode 100644 index 0000000..63f5a50 --- /dev/null +++ b/aula-13/images/large-test/entrypoint.sh @@ -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 diff --git a/aula-13/images/postgresql/Dockerfile b/aula-13/images/postgresql/Dockerfile new file mode 100644 index 0000000..cdf1b89 --- /dev/null +++ b/aula-13/images/postgresql/Dockerfile @@ -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"] diff --git a/aula-13/images/postgresql/postgresql.conf b/aula-13/images/postgresql/postgresql.conf new file mode 100644 index 0000000..48f613d --- /dev/null +++ b/aula-13/images/postgresql/postgresql.conf @@ -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 diff --git a/aula-13/k8s/postgresql/configmap.yaml b/aula-13/k8s/postgresql/configmap.yaml new file mode 100644 index 0000000..04c3428 --- /dev/null +++ b/aula-13/k8s/postgresql/configmap.yaml @@ -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" diff --git a/aula-13/k8s/postgresql/deployment.yaml b/aula-13/k8s/postgresql/deployment.yaml new file mode 100644 index 0000000..111e8af --- /dev/null +++ b/aula-13/k8s/postgresql/deployment.yaml @@ -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 diff --git a/aula-13/k8s/postgresql/pvc.yaml b/aula-13/k8s/postgresql/pvc.yaml new file mode 100644 index 0000000..bce2c4b --- /dev/null +++ b/aula-13/k8s/postgresql/pvc.yaml @@ -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 diff --git a/aula-13/k8s/postgresql/secret.yaml b/aula-13/k8s/postgresql/secret.yaml new file mode 100644 index 0000000..8edb5ee --- /dev/null +++ b/aula-13/k8s/postgresql/secret.yaml @@ -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 diff --git a/aula-13/k8s/postgresql/service.yaml b/aula-13/k8s/postgresql/service.yaml new file mode 100644 index 0000000..f24f375 --- /dev/null +++ b/aula-13/k8s/postgresql/service.yaml @@ -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 diff --git a/aula-13/k8s/prepull-daemonset.yaml b/aula-13/k8s/prepull-daemonset.yaml new file mode 100644 index 0000000..b1a639e --- /dev/null +++ b/aula-13/k8s/prepull-daemonset.yaml @@ -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= \ +# --docker-password= +# ============================================================================= diff --git a/aula-13/pipelines/postgresql/.gitlab-ci.yml b/aula-13/pipelines/postgresql/.gitlab-ci.yml new file mode 100644 index 0000000..5561727 --- /dev/null +++ b/aula-13/pipelines/postgresql/.gitlab-ci.yml @@ -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 diff --git a/aula-13/pipelines/postgresql/ci.yml b/aula-13/pipelines/postgresql/ci.yml new file mode 100644 index 0000000..d7a725a --- /dev/null +++ b/aula-13/pipelines/postgresql/ci.yml @@ -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)" diff --git a/aula-13/setup.sh b/aula-13/setup.sh new file mode 100755 index 0000000..326ad79 --- /dev/null +++ b/aula-13/setup.sh @@ -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 logs stargz-snapshotter" +echo ""