# Aula 10 - Plataforma de Desenvolvimento (Gitea) 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 │ │ └───────┬───────┘ └──────┬──────┘ │ │ │ │ ▼ ▼ │ ┌──────────────────────────────────────────┐ │ Namespace: gitea │ │ │ │ ┌─────────────────────────────────┐ │ │ │ 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 1. **Cluster Talos na Hetzner** (aula-08) com: - Hetzner CSI Driver (StorageClass: hcloud-volumes) - NGINX Ingress Controller com LoadBalancer 2. **kubectl** instalado 3. **Helm** 3.x instalado ## Instalação ```bash cd aula-10 export KUBECONFIG=$(pwd)/../aula-08/kubeconfig chmod +x setup.sh ./setup.sh ``` O script é interativo e pergunta: 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) ### 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 | Cenário | TLS Provider | Configuração | |---------|--------------|--------------| | **CloudFlare (proxy)** | CloudFlare Edge | Sem cert-manager, TLS na borda | | **Outro DNS + Let's Encrypt** | cert-manager | HTTP-01 challenge automático | | **Outro DNS + HTTP** | Nenhum | Apenas HTTP (dev/workshop) | ## Componentes e Recursos | 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** | ## Funcionalidades ### 1. Container Registry (OCI) O Gitea inclui um Container Registry OCI integrado. Sem Harbor, sem MinIO, sem componentes extras. ```bash # Login no registry docker login gitea.kube.quest # 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 ### Web UI ``` URL: https://gitea.kube.quest Usuário: gitea_admin Senha: (exibida no final do setup.sh) ``` ### Container Registry ```bash docker login gitea.kube.quest # Username: gitea_admin # Password: (mesma senha do admin) ``` ### 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 gitea # Ver logs do Gitea kubectl logs -n gitea -l app.kubernetes.io/name=gitea -f # Reiniciar Gitea kubectl rollout restart deployment -n gitea gitea # Ver certificados (se Let's Encrypt) kubectl get certificate -n gitea # Desinstalar ./cleanup.sh ``` ## Troubleshooting ### Pods não iniciam ```bash kubectl get events -n gitea --sort-by='.lastTimestamp' kubectl get pvc -n gitea ``` ### SSH não conecta ```bash # Verificar TCP passthrough kubectl get configmap tcp-services -n ingress-nginx -o yaml ``` ### Registry não funciona ```bash curl -v https://gitea.kube.quest/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 | Recurso | Custo/mês | |---------|-----------| | Worker (1x CAX11) | ~$4.59 | | Volumes (~26Gi) | ~$1.26 | | LoadBalancer (compartilhado) | ~$1.80 (1/3) | | **Total** | **~$7.65** | ## Storage: Disco Local vs S3 (Object Storage) Por padrão, o Gitea armazena tudo em **disco local** (PVC). Isso é simples e funciona pro workshop. Mas em produção com muitas imagens Docker, o PVC de 10Gi enche rápido. O Gitea suporta **S3-compatible storage** (MinIO, AWS S3, Hetzner Object Storage, etc.) como backend para **tudo**: container images, LFS, avatars, attachments, actions artifacts. ### O que usamos (default) ``` Gitea Pod → PVC 10Gi (hcloud-volumes) ├── /data/git/repositories (código) ├── /data/gitea/packages (container images) ├── /data/gitea/lfs (large files) └── /data/gitea/avatars (fotos) ``` Simples, sem dependências externas. Suficiente pra clusters pequenos. ### Quando migrar pra S3 - PVC passando de 80% (o pvc-autoresizer da aula-12 ajuda, mas tem limite) - Muitas imagens Docker grandes (container registry crescendo) - Precisa de storage "ilimitado" sem gerenciar volumes ### Como configurar S3 (Hetzner Object Storage) 1. Criar bucket no [Hetzner Cloud Console](https://console.hetzner.cloud) → Object Storage 2. Gerar Access Key + Secret Key 3. Adicionar no `gitea-values.yaml`: ```yaml gitea: config: storage: STORAGE_TYPE: minio MINIO_ENDPOINT: nbg1.your-objectstorage.com MINIO_ACCESS_KEY_ID: "sua-access-key" MINIO_SECRET_ACCESS_KEY: "sua-secret-key" MINIO_BUCKET: gitea MINIO_LOCATION: nbg1 MINIO_USE_SSL: "true" SERVE_DIRECT: "true" ``` 4. `helm upgrade` pra aplicar ### SERVE_DIRECT: como funciona (e por que é seguro) Com `SERVE_DIRECT: true`, o Gitea **não** faz proxy dos downloads. Em vez disso, gera **URLs pré-assinadas** com expiração: ``` docker pull gitea.kube.quest/org/app-privada:v1 │ ├─ 1. Client autentica no Gitea (token/password) │ ├─ 2. Gitea verifica permissão (repo privado? user tem acesso?) │ → Se não tem acesso: 403 Forbidden. Para aqui. │ ├─ 3. Gitea gera URL S3 pré-assinada (expira em ~10 minutos) │ https://s3.example.com/gitea/packages/sha256:abc... │ ?X-Amz-Signature=xxx&X-Amz-Expires=600 │ └─ 4. Client baixa direto do S3 via URL assinada → URL expira, não pode ser reutilizada → Repo privado permanece privado ``` Sem `SERVE_DIRECT` (default): ``` Client → Gitea (proxy) → S3 → Gitea (proxy) → Client ^^^^^^^^^^^^ ^^^^^^^^^^^^ CPU + RAM + bandwidth passam pelo Gitea ``` **Repos privados continuam privados** — a URL assinada só é gerada após autenticação e verificação de permissão. É o mesmo mecanismo usado por AWS S3, GitHub Packages e Google Cloud Storage. Único cenário onde **não** usar `SERVE_DIRECT`: se o bucket S3 está em rede privada e os clients não têm acesso direto à rede do S3. ### Ou só pra packages (container images) Se quiser manter repos git em disco local mas imagens no S3: ```yaml gitea: config: storage.packages: STORAGE_TYPE: minio MINIO_ENDPOINT: nbg1.your-objectstorage.com MINIO_ACCESS_KEY_ID: "sua-access-key" MINIO_SECRET_ACCESS_KEY: "sua-secret-key" MINIO_BUCKET: gitea MINIO_USE_SSL: "true" MINIO_BASE_PATH: "packages/" SERVE_DIRECT: "true" ``` ### Custo S3 vs PVC | Storage | Custo | Limite | |---------|-------|--------| | PVC (Hetzner Volume) | ~$0.048/GB/mês | 10TB max | | Hetzner Object Storage | ~€0.006/GB/mês (~8x mais barato) | Ilimitado | **Para o workshop usamos PVC** — zero configuração extra, zero secrets de S3. ## Lições do Workshop 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 - [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)