Aula 08 - Cluster Kubernetes HA: - Setup interativo com OpenTofu para Talos na Hetzner - CCM, CSI Driver, Cluster Autoscaler, Metrics Server - NGINX Ingress com LoadBalancer (HTTP/HTTPS/SSH) Aula 09 - n8n na Hetzner: - Deploy via Helm com PostgreSQL e Redis - Suporte multi-tenant com add-client.sh - Integração com Hetzner CSI para volumes persistentes Aula 10 - GitLab na Hetzner: - Setup agnóstico: CloudFlare (trusted proxies) ou Let's Encrypt - Anti-affinity para distribuir webservice/sidekiq em nós diferentes - Container Registry e SSH via TCP passthrough - Documentação do erro 422 e solução com trustedCIDRsForXForwardedFor Melhorias gerais: - READMEs atualizados com arquitetura e troubleshooting - Scripts cleanup.sh para todas as aulas - CLAUDE.md atualizado com contexto do projeto
Aula 10 - GitLab via Helm (Cluster Hetzner Cloud)
Deploy do GitLab em Kubernetes usando Helm chart oficial, com Container Registry, SSH e TLS configurável.
Arquitetura
Hetzner LoadBalancer
│
┌───────────────────┼───────────────────┐
│ │ │
:80/:443 :22 │
│ │ │
▼ ▼ │
┌───────────────┐ ┌─────────────┐ │
│ NGINX Ingress │ │ TCP Service │ │
└───────┬───────┘ └──────┬──────┘ │
│ │ │
▼ ▼ │
┌─────────────────────────────────────────┐ │
│ GitLab (namespace: gitlab) │ │
│ ┌──────────┐ ┌────────┐ ┌─────────┐ │ │
│ │Webservice│ │Sidekiq │ │ Shell │◄─┼─────┘
│ │ (Rails) │ │ (Jobs) │ │ (SSH) │ │
│ └────┬─────┘ └───┬────┘ └─────────┘ │
│ │ │ │
│ ┌────▼────────────▼────┐ │
│ │ Gitaly │ │
│ │ (Git Storage) │ │
│ └──────────────────────┘ │
│ │
│ ┌──────────┐ ┌───────┐ ┌──────────┐ │
│ │PostgreSQL│ │ Redis │ │ MinIO │ │
│ │ (10Gi) │ │(10Gi) │ │ (10Gi) │ │
│ └──────────┘ └───────┘ └──────────┘ │
└─────────────────────────────────────────┘
Pré-requisitos
- Cluster Talos na Hetzner (aula-08) com:
- Hetzner CSI Driver (StorageClass: hcloud-volumes)
- NGINX Ingress Controller com LoadBalancer
- kubectl instalado
- Helm 3.x instalado
Contexto do Cluster
Esta aula usa o cluster Kubernetes provisionado na aula-08. O kubeconfig foi gerado automaticamente pelo OpenTofu.
# Verificar se o cluster está acessível
export KUBECONFIG=$(pwd)/../aula-08/kubeconfig
kubectl cluster-info
Instalação
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:
- Hostname do GitLab (ex:
git.kube.quest) - FQDN completo - Usa CloudFlare? (com proxy/CDN) - pode herdar da aula-09
- Ativar Let's Encrypt? (se não usar CloudFlare)
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) |
CloudFlare e Trusted Proxies (Erro 422)
Quando você usa CloudFlare com proxy ativo (ícone laranja), o tráfego passa pelos servidores do CloudFlare antes de chegar ao GitLab:
Usuário → CloudFlare (TLS) → LoadBalancer → NGINX → GitLab
↓
Adiciona headers:
X-Forwarded-For: IP-do-usuario
X-Forwarded-Proto: https
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"
Solução: O setup.sh configura automaticamente os IPs do CloudFlare como proxies confiáveis:
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 |
| MinIO | 128Mi | 256Mi | 10Gi |
| Shell | 64Mi | 128Mi | - |
| Toolbox | 256Mi | 512Mi | - |
| Total | ~5Gi | ~8Gi | 40Gi |
Pod Anti-Affinity (Distribuicao Inteligente)
O GitLab configura antiAffinity para garantir que webservice e sidekiq rodem em nos diferentes:
gitlab:
webservice:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: sidekiq
topologyKey: kubernetes.io/hostname
Por que isso importa?
Sem antiAffinity, webservice (~2.5Gi) + sidekiq (~2Gi) = 4.5Gi no mesmo no CAX11 (4GB):
- Resultado: OOMKilled constantes, servico instavel
Com antiAffinity:
- Kubernetes detecta que nao pode agendar no mesmo no
- Pod fica Pending
- Cluster Autoscaler cria um novo no automaticamente
- Pods distribuidos = servico estavel
Licao do Workshop
"Kubernetes nao e so rodar containers - e orquestracao inteligente. Quando configuramos antiAffinity, o scheduler entendeu que precisava de mais recursos e o autoscaler provisionou automaticamente."
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:
Arquivo de Configuração
O setup.sh gera um arquivo .env (herda configuração de TLS da aula-09):
# 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=
Acesso
Web UI
URL: https://git.{domain}
Usuário: root
Senha: (ver abaixo)
Obter senha do root
kubectl get secret gitlab-gitlab-initial-root-password \
-n gitlab \
-o jsonpath='{.data.password}' | base64 -d; echo
SSH
# 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
Container Registry
# Login no registry
docker login registry.kube.quest
# Push de imagem
docker tag minha-app:v1 registry.kube.quest/grupo/projeto:v1
docker push registry.kube.quest/grupo/projeto:v1
Comandos Úteis
# Ver todos os pods
kubectl get pods -n gitlab
# Ver logs do webservice
kubectl logs -n gitlab -l app=webservice -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
# Ver certificados (se Let's Encrypt)
kubectl get certificate -n gitlab
# Desinstalar (apenas GitLab)
./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
# 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=<token>
Troubleshooting
Pods não iniciam
# 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
SSH não conecta
# Verificar se porta 22 está no ConfigMap
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
# Verificar pod do registry
kubectl get pods -n gitlab -l app=registry
kubectl logs -n gitlab -l app=registry
# Testar internamente
kubectl run test --rm -it --image=busybox -- wget -qO- http://gitlab-registry:5000/v2/
Custos
| Recurso | Custo/mês |
|---|---|
| Workers (2x CAX11 - antiAffinity) | ~$9.18 |
| Volumes (4x 10Gi = 40Gi) | ~$1.94 |
| LoadBalancer (compartilhado) | ~$1.80 (1/3) |
| Total GitLab | ~$12.92 |
Nota: O antiAffinity requer 2 workers minimos (webservice e sidekiq em nos separados). O Cluster Autoscaler provisiona automaticamente quando necessario.