Files
workshop/aula-10

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:

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

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.

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

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

# 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

docker login gitea.kube.quest
# Username: gitea_admin
# Password: (mesma senha do admin)

Verificar Runner

# 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

# 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

kubectl get events -n gitea --sort-by='.lastTimestamp'
kubectl get pvc -n gitea

SSH não conecta

# Verificar TCP passthrough
kubectl get configmap tcp-services -n ingress-nginx -o yaml

Registry não funciona

curl -v https://gitea.kube.quest/v2/

Runner não executa workflows

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

# 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 → Object Storage
  2. Gerar Access Key + Secret Key
  3. Adicionar no gitea-values.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"
  1. 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:

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