diff --git a/.gitignore b/.gitignore index 4de7722..15ee82c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ **/talosconfig **/*.pem **/*.key +**/.env # OS .DS_Store diff --git a/CLAUDE.md b/CLAUDE.md index 6e18c2d..8397569 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,9 +13,11 @@ This is a workshop repository for teaching Docker and Kubernetes concepts, speci - **aula-03/**: Kubernetes lesson - high availability with replicas and readiness probes - **aula-04/**: Kubernetes lesson - NGINX Ingress with Keep Request (Lua) for zero-downtime - **aula-05/**: Kubernetes lesson - KEDA + Victoria Metrics for metrics-based auto-scaling -- **aula-06/**: Kubernetes lesson - n8n deployment via Helm with Queue Mode (workers, webhooks, PostgreSQL, Redis) +- **aula-06/**: Kubernetes lesson - n8n deployment via Helm (LOCAL environment - Docker Desktop, minikube, kind) - **aula-07/**: Talos Linux - creating custom Talos image for Hetzner Cloud -- **aula-08/**: OpenTofu - provisioning HA Talos Kubernetes cluster on Hetzner Cloud +- **aula-08/**: OpenTofu - provisioning HA Talos Kubernetes cluster on Hetzner Cloud with CCM and LoadBalancer +- **aula-09/**: Kubernetes lesson - n8n deployment via Helm (Hetzner Cloud with CSI Driver and multi-tenant support) +- **aula-10/**: Kubernetes lesson - GitLab deployment via Helm with Container Registry and SSH ## Running the Examples @@ -63,12 +65,12 @@ cd aula-05 ``` Installs Victoria Metrics (metrics collection), KEDA (event-driven autoscaling), and NGINX Ingress. The ScaledObject monitors metrics like unavailable pods and restart counts, automatically scaling the deployment from 5 to 30 replicas based on demand. -### Aula 06 (Kubernetes - n8n via Helm) +### Aula 06 (Kubernetes - n8n via Helm - LOCAL) ```bash cd aula-06 ./setup.sh ``` -Deploys n8n workflow automation platform via Helm chart with Queue Mode architecture: main node, workers (2-5 replicas with HPA), webhooks (1-3 replicas with HPA), PostgreSQL, and Redis. Access via http://n8n.localhost (requires NGINX Ingress). +Deploys n8n workflow automation platform via Helm chart in a LOCAL Kubernetes cluster (Docker Desktop, minikube, kind). Queue Mode architecture with main node, workers (2-5 replicas with HPA), webhooks (1-3 replicas with HPA), PostgreSQL, and Redis. Access via http://n8n.localhost (requires NGINX Ingress). ### Aula 07 (Talos Linux - Custom Image) Follow the instructions in `aula-07/README.md` to create a custom Talos Linux image on Hetzner Cloud using Talos Factory. This is a prerequisite for Aula 08. @@ -98,8 +100,47 @@ Optional - Enable cluster autoscaling: ``` This installs the Kubernetes Cluster Autoscaler configured for Hetzner Cloud, automatically scaling workers from 1 to 5 based on pending pods. +Optional - Install Hetzner Cloud Controller Manager and NGINX Ingress with LoadBalancer: +```bash +./install-ccm.sh +./install-nginx-ingress.sh +``` +This enables automatic LoadBalancer provisioning and exposes HTTP/HTTPS/SSH via a single Hetzner LoadBalancer (~$5/month). + To destroy the infrastructure: `./cleanup.sh` +### Aula 09 (Kubernetes - n8n via Helm - Hetzner Cloud) +```bash +cd aula-09 +export KUBECONFIG=/path/to/aula-08/kubeconfig +./setup.sh +``` +Deploys n8n workflow automation platform via Helm chart on Hetzner Cloud. Installs Hetzner CSI Driver for persistent volumes (10Gi minimum). Includes multi-tenant support with `add-client.sh` script for provisioning clients in separate namespaces. + +Prerequisites: +- Completed Aula 08 (Talos cluster on Hetzner) +- Hetzner Cloud API token + +### Aula 10 (Kubernetes - GitLab via Helm) +```bash +cd aula-10 +./setup.sh +``` +Deploys GitLab via official Helm chart with: +- Web UI at git.kube.quest +- Container Registry at registry.git.kube.quest +- SSH access via port 22 (TCP passthrough through NGINX) +- PostgreSQL, Redis, and MinIO for storage +- Resource requests of ~4GB to occupy one dedicated CAX11 worker + +Prerequisites: +- Completed Aula 08 (Talos cluster) +- Hetzner CSI Driver (Aula 09) +- Hetzner CCM and NGINX Ingress with LoadBalancer (Aula 08) +- DNS configured pointing to LoadBalancer IP + +To remove: `./cleanup.sh` + ## App Behavior The Node.js app (`app.js`) is intentionally designed to: diff --git a/aula-01/README.md b/aula-01/README.md new file mode 100644 index 0000000..2fdcac1 --- /dev/null +++ b/aula-01/README.md @@ -0,0 +1,122 @@ +# Aula 01 - O Problema: Apps que Travam mas Nao Morrem + +## Objetivo + +Demonstrar uma limitacao comum em ambientes de producao: aplicacoes que param de responder mas continuam com o processo rodando. O Docker nao consegue detectar esse tipo de falha. + +## O Problema + +Imagine uma aplicacao que: +- Tem um memory leak que causa travamento +- Entra em deadlock +- Perde conexao com o banco e fica em loop infinito + +O processo continua vivo, mas a aplicacao nao responde. O Docker ve o processo rodando e nao faz nada. + +## A Aplicacao "Bugada" + +Nossa app Node.js simula esse comportamento: + +```javascript +// Apos MAX_REQUESTS, para de responder mas continua rodando +if (requestCount > MAX_REQUESTS) { + console.log('App travado apos 3 requests'); + return; // Nao responde, mas processo vivo +} +``` + +## Arquivos + +``` +aula-01/ +├── docker-compose.yml # Configuracao do container +├── app.js # Aplicacao Node.js "bugada" +└── .env # MAX_REQUESTS=3 +``` + +## Comandos + +```bash +# Iniciar a aplicacao +docker compose up + +# Parar a aplicacao +docker compose down +``` + +## Teste Pratico + +1. Inicie a aplicacao: + ```bash + docker compose up + ``` + +2. Faca 3 requests (funcionam normalmente): + ```bash + curl localhost:3000 # Req -> 1/3 + curl localhost:3000 # Req -> 2/3 + curl localhost:3000 # Req -> 3/3 + ``` + +3. Faca mais um request (vai travar): + ```bash + curl localhost:3000 # Fica esperando para sempre... + ``` + +4. Observe os logs - a app diz que travou: + ``` + App travado apos 3 requests + ``` + +5. O container continua rodando: + ```bash + docker ps # Container esta "Up", mas nao responde + ``` + +## A Limitacao do `restart: always` + +No `docker-compose.yml` temos: + +```yaml +restart: always +``` + +Isso reinicia o container apenas quando o **processo morre**. Como nossa app trava mas o processo Node.js continua rodando, o Docker nao faz nada. + +| Situacao | Processo | Docker reinicia? | +|----------|:--------:|:----------------:| +| App crasha (exit 1) | Morto | Sim | +| App trava (nosso caso) | Vivo | Nao | + +## Healthcheck do Docker: Detecta mas Nao Resolve + +Adicionamos um healthcheck no `docker-compose.yml`: + +```yaml +healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 2s + timeout: 2s + retries: 2 + start_period: 5s +``` + +Apos a app travar, veja o status mudar para `unhealthy`: + +```bash +docker ps +# STATUS: Up 1 minute (unhealthy) +``` + +**Porem, o Docker apenas DETECTA o problema - nao reinicia o container!** + +O healthcheck serve para: +- Monitoramento e alertas +- Orquestradores externos tomarem acao +- Load balancers removerem o container do pool + +Mas sozinho, nao resolve o problema. + +## Proximo Passo + +Na **Aula 02**, vamos resolver esse problema usando Kubernetes com **Liveness Probes** - verificacoes periodicas que detectam quando a aplicacao parou de responder **E reiniciam o container automaticamente**. diff --git a/aula-01/docker-compose.yml b/aula-01/docker-compose.yml index fa586ec..3f9688a 100644 --- a/aula-01/docker-compose.yml +++ b/aula-01/docker-compose.yml @@ -9,3 +9,9 @@ services: ports: - "3000:3000" restart: always + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"] + interval: 2s + timeout: 2s + retries: 2 + start_period: 5s diff --git a/aula-02/README.md b/aula-02/README.md new file mode 100644 index 0000000..832f3b3 --- /dev/null +++ b/aula-02/README.md @@ -0,0 +1,107 @@ +# Aula 02 - Do Docker Compose ao Kubernetes + +## Objetivo + +Migrar a aplicação da Aula 01 para Kubernetes, entendendo como os conceitos se traduzem entre as duas tecnologias. + +## Comparação: Docker Compose vs Kubernetes + +| Docker Compose | Kubernetes | Descrição | +|----------------|------------|-----------| +| `docker-compose.yml` | `deployment.yaml` | Define como rodar o container | +| `restart: always` | `livenessProbe` | Recuperação de falhas | +| `volumes:` (bind mount) | `ConfigMap` + `volumeMounts` | Injetar arquivos no container | +| `environment:` | `env:` + `configMapKeyRef` | Variáveis de ambiente | +| `ports:` | `Service` | Expor a aplicação | + +## Mapeamento de Arquivos + +``` +aula-01/ aula-02/ +├── docker-compose.yml → ├── deployment.yaml +├── app.js → ├── configmap.yaml (contém o app.js) + └── service.yaml +``` + +Na Aula 01, tínhamos 1 arquivo YAML. No Kubernetes, separamos em 3 arquivos com responsabilidades distintas: + +- **configmap.yaml**: Configurações e código da aplicação +- **deployment.yaml**: Como rodar e monitorar o container +- **service.yaml**: Como expor a aplicação na rede + +## Comandos + +```bash +# Criar todos os recursos +kubectl apply -f . + +# Remover todos os recursos +kubectl delete -f . +``` + +## O Problema do `restart: always` + +Na Aula 01, usamos `restart: always` no Docker Compose: + +```yaml +services: + node-app: + restart: always # Reinicia quando o processo morre +``` + +**Problema**: Se a aplicação travar mas o processo continuar rodando, o Docker não reinicia o container. É exatamente o que acontece com nossa app "bugada" - ela para de responder mas o processo Node.js continua vivo. + +## A Solução: Liveness Probe + +O Kubernetes resolve isso com **Liveness Probes** - verificações periódicas de saúde: + +```yaml +livenessProbe: + httpGet: + path: /health # Endpoint a ser testado + port: 3000 + initialDelaySeconds: 5 # Espera inicial antes de testar + periodSeconds: 2 # Intervalo entre testes + failureThreshold: 2 # Falhas consecutivas para reiniciar +``` + +**Como funciona**: +1. Kubernetes faz GET em `/health` a cada 2 segundos +2. Se falhar 2 vezes seguidas, o container é reiniciado +3. Diferente do `restart: always`, detecta processos "vivos mas travados" + +## Teste Prático + +1. Aplique os recursos: + ```bash + kubectl apply -f . + ``` + +2. Acompanhe os pods: + ```bash + kubectl get pods -w + ``` + +3. Em outro terminal, faça requests até travar: + ```bash + curl localhost:3000 + curl localhost:3000 + curl localhost:3000 + curl localhost:3000 # Este vai travar + ``` + +4. Observe o pod ser reiniciado automaticamente (RESTARTS aumenta) + +5. Remova tudo: + ```bash + kubectl delete -f . + ``` + +## Conclusão + +| Abordagem | Detecta processo morto | Detecta processo travado | +|-----------|:---------------------:|:-----------------------:| +| `restart: always` | Sim | Nao | +| `livenessProbe` | Sim | Sim | + +O Liveness Probe é a forma do Kubernetes garantir que sua aplicação está realmente funcionando, não apenas rodando. diff --git a/aula-03/cleanup.sh b/aula-03/cleanup.sh new file mode 100755 index 0000000..b701034 --- /dev/null +++ b/aula-03/cleanup.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# ============================================================================= +# Cleanup da Aula 03 - Remove aplicação node-bugado (HA) +# ============================================================================= + +set -e + +# Cores para output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } +log_success() { echo -e "${GREEN}[OK]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "" +echo "============================================" +echo " Cleanup - Aula 03" +echo "============================================" +echo "" + +log_info "Removendo recursos..." +kubectl delete -f "$SCRIPT_DIR/service.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/deployment.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/configmap.yaml" 2>/dev/null || true + +log_success "Cleanup concluído!" +echo "" diff --git a/aula-04/cleanup.sh b/aula-04/cleanup.sh new file mode 100755 index 0000000..6d2dc0b --- /dev/null +++ b/aula-04/cleanup.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# ============================================================================= +# Cleanup da Aula 04 - Remove NGINX Ingress e aplicação +# ============================================================================= + +set -e + +# Cores para output +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)" + +echo "" +echo "============================================" +echo " Cleanup - Aula 04" +echo "============================================" +echo "" + +# Remover recursos da aplicação +log_info "Removendo aplicação node-bugado..." +kubectl delete -f "$SCRIPT_DIR/ingress-nginx.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/service.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/deployment.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/configmap.yaml" 2>/dev/null || true +log_success "Aplicação removida" + +# Remover NGINX Ingress +log_info "Removendo NGINX Ingress Controller..." +helm uninstall nginx-ingress -n ingress-nginx 2>/dev/null || true +kubectl delete namespace ingress-nginx 2>/dev/null || true +log_success "NGINX Ingress removido" + +echo "" +log_success "Cleanup concluído!" +echo "" diff --git a/aula-05/cleanup.sh b/aula-05/cleanup.sh new file mode 100755 index 0000000..6b1fc9a --- /dev/null +++ b/aula-05/cleanup.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# ============================================================================= +# Cleanup da Aula 05 - Remove KEDA, Victoria Metrics e aplicação +# ============================================================================= + +set -e + +# Cores para output +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)" + +echo "" +echo "============================================" +echo " Cleanup - Aula 05" +echo "============================================" +echo "" + +# Remover recursos da aplicação +log_info "Removendo aplicação node-bugado..." +kubectl delete -f "$SCRIPT_DIR/scaledobject.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/ingress-nginx.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/service.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/deployment.yaml" 2>/dev/null || true +kubectl delete -f "$SCRIPT_DIR/configmap.yaml" 2>/dev/null || true +log_success "Aplicação removida" + +# Remover NGINX Ingress +log_info "Removendo NGINX Ingress Controller..." +helm uninstall nginx-ingress -n ingress-nginx 2>/dev/null || true +kubectl delete namespace ingress-nginx 2>/dev/null || true +log_success "NGINX Ingress removido" + +# Remover KEDA +log_info "Removendo KEDA..." +helm uninstall keda -n keda 2>/dev/null || true +kubectl delete namespace keda 2>/dev/null || true +log_success "KEDA removido" + +# Remover Victoria Metrics +log_info "Removendo Victoria Metrics..." +helm uninstall vm -n monitoring 2>/dev/null || true +kubectl delete namespace monitoring 2>/dev/null || true +log_success "Victoria Metrics removido" + +echo "" +log_success "Cleanup concluído!" +echo "" diff --git a/aula-06/README.md b/aula-06/README.md new file mode 100644 index 0000000..2492fbf --- /dev/null +++ b/aula-06/README.md @@ -0,0 +1,107 @@ +# Aula 06 - n8n via Helm (Ambiente LOCAL) + +Deploy do n8n workflow automation em cluster Kubernetes local usando Helm. + +## Arquitetura + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Cluster Local │ +│ (Docker Desktop / minikube / kind / k3d) │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ NGINX Ingress │ │ +│ │ http://n8n.localhost │ │ +│ └────────────────────────┬────────────────────────────┘ │ +│ │ │ +│ ┌────────────────────────┼────────────────────────────┐ │ +│ │ Namespace: n8n │ │ +│ │ │ │ │ +│ │ ┌────────────────────┼────────────────────┐ │ │ +│ │ │ ▼ │ │ │ +│ │ │ ┌──────────┐ │ │ │ +│ │ │ │ Main │ │ │ │ +│ │ │ │ (n8n) │ │ │ │ +│ │ │ └────┬─────┘ │ │ │ +│ │ │ │ │ │ │ +│ │ │ ┌─────────────┼─────────────┐ │ │ │ +│ │ │ ▼ ▼ ▼ │ │ │ +│ │ │ ┌───────┐ ┌──────────┐ ┌────────┐ │ │ │ +│ │ │ │Workers│ │ Webhooks │ │ MCP │ │ │ │ +│ │ │ │ (2-5) │ │ (1-3) │ │Webhook │ │ │ │ +│ │ │ └───────┘ └──────────┘ └────────┘ │ │ │ +│ │ │ │ │ │ +│ │ │ Queue Mode │ │ │ +│ │ └────────────────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ ┌───────────────┼───────────────┐ │ │ +│ │ ▼ ▼ │ │ +│ │ ┌──────────┐ ┌──────────┐ │ │ +│ │ │PostgreSQL│ │ Redis │ │ │ +│ │ │ (1Gi) │ │ (1Gi) │ │ │ +│ │ └──────────┘ └──────────┘ │ │ +│ └─────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Pré-requisitos + +- Cluster Kubernetes local: + - Docker Desktop com Kubernetes habilitado + - minikube (`minikube start`) + - kind (`kind create cluster`) + - k3d (`k3d cluster create`) +- kubectl configurado +- Helm 3.x instalado + +## Instalação + +```bash +cd aula-06 +chmod +x setup.sh +./setup.sh +``` + +O script instala automaticamente: +1. NGINX Ingress Controller +2. n8n com todos os componentes + +## Acesso + +**URL:** http://n8n.localhost + +Se `n8n.localhost` não resolver, adicione ao `/etc/hosts`: +``` +127.0.0.1 n8n.localhost +``` + +## Componentes + +| Componente | Réplicas | Recursos | +|------------|----------|----------| +| Main (n8n) | 1 | 256Mi-1Gi RAM | +| Workers | 2-5 (HPA) | 256Mi-512Mi RAM | +| Webhooks | 1-3 (HPA) | 128Mi-256Mi RAM | +| PostgreSQL | 1 | 1Gi volume | +| Redis | 1 | 1Gi volume | + +## Comandos Úteis + +```bash +# Ver pods +kubectl get pods -n n8n + +# Ver logs do n8n +kubectl logs -f -n n8n deployment/n8n + +# Ver autoscaling +kubectl get hpa -n n8n + +# Desinstalar +helm uninstall n8n -n n8n +kubectl delete ns n8n +``` + +## Para Cluster Hetzner Cloud + +Se você quer fazer deploy em um cluster Hetzner Cloud com volumes persistentes e LoadBalancer, veja a **aula-09**. diff --git a/aula-06/cleanup.sh b/aula-06/cleanup.sh new file mode 100755 index 0000000..8c13170 --- /dev/null +++ b/aula-06/cleanup.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# ============================================================================= +# Cleanup da Aula 06 - Remove n8n (ambiente LOCAL) +# ============================================================================= + +set -e + +# Cores para output +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"; } + +echo "" +echo "============================================" +echo " Cleanup - Aula 06 (n8n LOCAL)" +echo "============================================" +echo "" + +# Remover n8n +log_info "Removendo n8n..." +helm uninstall n8n -n n8n 2>/dev/null || true +log_success "Helm release removida" + +# Remover PVCs +log_info "Removendo PVCs..." +kubectl delete pvc --all -n n8n 2>/dev/null || true +log_success "PVCs removidos" + +# Remover namespace +log_info "Removendo namespace n8n..." +kubectl delete namespace n8n 2>/dev/null || true +log_success "Namespace removido" + +# Opcional: remover NGINX Ingress +read -p "Remover NGINX Ingress também? (s/N): " remove_ingress +if [[ "$remove_ingress" =~ ^[Ss]$ ]]; then + log_info "Removendo NGINX Ingress..." + helm uninstall nginx-ingress -n ingress-nginx 2>/dev/null || true + kubectl delete namespace ingress-nginx 2>/dev/null || true + log_success "NGINX Ingress removido" +fi + +echo "" +log_success "Cleanup concluído!" +echo "" diff --git a/aula-06/custom-values.yaml b/aula-06/custom-values.yaml index 62efce9..ce0553d 100644 --- a/aula-06/custom-values.yaml +++ b/aula-06/custom-values.yaml @@ -1,10 +1,12 @@ # ============================================================================= -# n8n Helm Chart - Custom Values +# n8n Helm Chart - Custom Values (Ambiente LOCAL) # ============================================================================= -# Aula 06 - Deploy n8n via Helm +# Aula 06 - Deploy n8n via Helm em cluster local # # Chart: community-charts/n8n # Docs: https://community-charts.github.io/docs/charts/n8n/configuration +# +# Para cluster Hetzner Cloud, veja aula-09/ # ============================================================================= # ----------------------------------------------------------------------------- @@ -35,7 +37,7 @@ postgresql: primary: persistence: enabled: true - size: 5Gi + size: 1Gi # Ambiente local - sem mínimo # ----------------------------------------------------------------------------- # Redis (necessário para Queue Mode) @@ -46,6 +48,10 @@ redis: auth: enabled: true password: "n8n-redis-workshop-2025" + master: + persistence: + enabled: true + size: 1Gi # Ambiente local - sem mínimo # ----------------------------------------------------------------------------- # Ingress NGINX @@ -58,7 +64,7 @@ ingress: nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" hosts: - - host: n8n.kube.quest + - host: n8n.localhost paths: - path: / pathType: Prefix @@ -71,7 +77,7 @@ main: N8N_SECURE_COOKIE: "false" # Permite HTTP sem HTTPS (apenas para dev/workshop) persistence: enabled: true - size: 2Gi + size: 1Gi # Ambiente local - sem mínimo mountPath: "/home/node/.n8n" resources: requests: @@ -121,7 +127,7 @@ worker: webhook: mode: queue count: 1 - url: "https://n8n.kube.quest" + url: "http://n8n.localhost" extraEnvVars: N8N_SECURE_COOKIE: "false" resources: diff --git a/aula-06/setup.sh b/aula-06/setup.sh index 62d3409..9c0f9c4 100755 --- a/aula-06/setup.sh +++ b/aula-06/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================================= -# Setup da Aula 06 - n8n via Helm +# Setup da Aula 06 - n8n via Helm (Ambiente LOCAL) # ============================================================================= # # Este script instala e configura: @@ -8,7 +8,7 @@ # 2. n8n com PostgreSQL, Redis, Workers e Webhooks # # Pré-requisitos: -# - Kubernetes cluster rodando (k3s, minikube, kind, etc) +# - Kubernetes cluster LOCAL rodando (Docker Desktop, minikube, kind, k3d) # - kubectl configurado # - Helm 3.x instalado # @@ -52,6 +52,10 @@ log_success "kubectl encontrado" # 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 seu cluster local está rodando:" + log_info " - Docker Desktop: Habilite Kubernetes nas configurações" + log_info " - minikube: minikube start" + log_info " - kind: kind create cluster" exit 1 fi log_success "Conectado ao cluster Kubernetes" @@ -67,68 +71,7 @@ log_success "Helm $(helm version --short) encontrado" echo "" # ============================================================================= -# 1. INSTALAR HETZNER CSI DRIVER (para provisionar volumes) -# ============================================================================= - -log_info "=== Configurando Hetzner CSI Driver ===" - -# Verificar se secret já existe (evita pedir token novamente) -if kubectl get secret hcloud -n kube-system &> /dev/null; then - log_success "Secret hcloud já existe em kube-system" -else - # Pedir token via input interativo - echo "" - log_info "Token da Hetzner Cloud necessário para provisionar volumes." - log_info "Crie um token em: https://console.hetzner.cloud/projects/*/security/tokens" - echo "" - log_info "Cole o token e pressione ENTER:" - - # Desabilita echo, lê linha completa, reabilita echo - stty -echo - IFS= read -r HCLOUD_TOKEN - stty echo - echo "" - - if [ -z "$HCLOUD_TOKEN" ]; then - log_error "Token não pode ser vazio." - exit 1 - fi - - log_info "Criando secret hcloud em kube-system..." - kubectl create secret generic hcloud \ - --namespace=kube-system \ - --from-literal=token="$HCLOUD_TOKEN" - log_success "Secret hcloud criado" -fi - -# Instalar Hetzner CSI Driver via Helm (se não instalado) -if helm status hcloud-csi -n kube-system &> /dev/null; then - log_success "Hetzner CSI Driver já está instalado" -else - log_info "Instalando Hetzner CSI Driver..." - helm repo add hcloud https://charts.hetzner.cloud 2>/dev/null || true - helm repo update hcloud - - helm install hcloud-csi hcloud/hcloud-csi \ - --namespace kube-system \ - --wait \ - --timeout 5m - log_success "Hetzner CSI Driver instalado" -fi - -# Verificar StorageClass -log_info "Verificando StorageClass..." -if kubectl get storageclass hcloud-volumes &> /dev/null; then - log_success "StorageClass hcloud-volumes disponível" -else - log_error "StorageClass hcloud-volumes não encontrado" - exit 1 -fi - -echo "" - -# ============================================================================= -# 2. INSTALAR NGINX INGRESS (se não existir) +# 1. INSTALAR NGINX INGRESS (se não existir) # ============================================================================= log_info "=== Verificando NGINX Ingress ===" @@ -152,7 +95,7 @@ fi echo "" # ============================================================================= -# 3. CRIAR NAMESPACE E APLICAR SECRETS +# 2. CRIAR NAMESPACE E APLICAR SECRETS # ============================================================================= log_info "=== Configurando namespace n8n ===" @@ -169,7 +112,7 @@ fi echo "" # ============================================================================= -# 4. INSTALAR n8n VIA HELM +# 3. INSTALAR n8n VIA HELM # ============================================================================= log_info "=== Instalando n8n ===" @@ -201,7 +144,7 @@ fi echo "" # ============================================================================= -# 5. AGUARDAR PODS FICAREM PRONTOS +# 4. AGUARDAR PODS FICAREM PRONTOS # ============================================================================= log_info "=== Aguardando pods ficarem prontos ===" @@ -248,7 +191,6 @@ echo -e "${GREEN} Setup Completo!${NC}" echo "==============================================" echo "" echo "Componentes instalados:" -echo " - Hetzner CSI Driver (StorageClass: hcloud-volumes)" echo " - NGINX Ingress Controller" echo " - n8n (namespace: n8n)" echo " - Main node" @@ -279,15 +221,6 @@ echo "" echo " # Desinstalar" echo " helm uninstall n8n -n n8n" echo "" -echo " # Fazer upgrade do helm chart" -echo " helm upgrade --reuse-values --values custom-values.yaml n8n community-charts/n8n --namespace n8n" -echo "" -echo " # Verificar historico de releases" -echo " helm history n8n -n n8n" -echo "" -echo " # Fazer rollback do historico de releases" -echo " helm rollback n8n " -echo "" echo "==============================================" echo "" diff --git a/aula-08/README.md b/aula-08/README.md new file mode 100644 index 0000000..834234d --- /dev/null +++ b/aula-08/README.md @@ -0,0 +1,208 @@ +# Aula 08 - Cluster Kubernetes HA na Hetzner Cloud + +## Objetivo + +Provisionar um cluster Kubernetes Talos de producao na Hetzner Cloud usando OpenTofu, com opcoes de Alta Disponibilidade (HA) e LoadBalancer. + +## Arquitetura + +### Modo HA com LoadBalancer (Recomendado) + +``` + Internet + │ + ▼ + ┌───────────────────────┐ + │ Hetzner Load │ + │ Balancer (LB11) │ + │ IP: 1.2.3.4 │ + └───────────────────────┘ + │ + ┌───────────────┼───────────────┐ + │ │ │ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ CP-0 │ │ CP-1 │ │ CP-2 │ + │ CAX11 │ │ CAX11 │ │ CAX11 │ + │10.0.1.10│ │10.0.1.11│ │10.0.1.12│ + └─────────┘ └─────────┘ └─────────┘ + │ │ │ + └───────────────┼───────────────┘ + │ + ▼ + ┌──────────┐ + │ Worker-0 │ + │ CAX11 │ + │10.0.1.20 │ + └──────────┘ +``` + +### Servicos do LoadBalancer + +O LoadBalancer centraliza todo o trafego externo: + +| Porta | Destino | Uso | +|:-----:|---------|-----| +| 6443 | Control Planes | Kubernetes API | +| 50000 | Control Planes | Talos API | +| 80 | Workers | HTTP (NGINX Ingress) | +| 443 | Workers | HTTPS (NGINX Ingress) | +| 22 | Workers | SSH (GitLab) | + +### Roteamento L7 (por dominio) + +O LoadBalancer faz apenas roteamento L4 (por porta). O roteamento por dominio e feito pelo NGINX Ingress: + +``` +LB :443 → NGINX Ingress → n8n.kube.quest → n8n pods + → git.kube.quest → gitlab pods + → argocd.kube.quest → argocd pods + → registry.git... → registry pods +``` + +## Custos Estimados + +Precos baseados em [Hetzner Cloud](https://www.hetzner.com/cloud/) (NBG1 - Nuremberg): + +| Configuracao | CPs | LB | Custo/mes | +|--------------|:---:|:--:|----------:| +| Single | 1 | Floating IP | ~$13 | +| HA | 3 | Floating IP | ~$22 | +| HA + LB | 3 | LB11 | ~$24 | +| HA + LB + Autoscaler (max 5 workers) | 3 | LB11 | ~$43 | + +Recursos: CAX11 $4.59, LB11 $5.99, Floating IP $3.29 + +## Pre-requisitos + +```bash +# macOS +brew install opentofu +brew install siderolabs/tap/talosctl +brew install kubectl +brew install helm + +# Criar imagem Talos (aula-07) +# Obter ID: hcloud image list --type snapshot +``` + +## Comandos + +```bash +# Provisionar cluster (interativo) +./setup.sh + +# Destruir infraestrutura +./cleanup.sh +``` + +## Fluxo do Setup + +``` +1. Verifica pre-requisitos +2. Coleta credenciais: + - Token Hetzner Cloud + - Chave SSH + - ID da imagem Talos +3. Pergunta configuracao: + - Cluster HA? (S/n) + - LoadBalancer? (S/n) [se HA] +4. Cria terraform.tfvars +5. Executa tofu init/plan/apply +6. Aguarda cluster ficar pronto +7. Instala CCM (Cloud Controller Manager) +8. Instala Cluster Autoscaler +9. Instala Hetzner CSI Driver +10. Instala Metrics Server +11. Instala NGINX Ingress Controller +``` + +## Componentes Instalados + +| Componente | Funcao | +|------------|--------| +| Hetzner CCM | Remove taints dos workers, provisiona LoadBalancers | +| Cluster Autoscaler | Escala workers de 1 a 5 automaticamente | +| Hetzner CSI Driver | Provisiona volumes persistentes (StorageClass: hcloud-volumes) | +| Metrics Server | Habilita kubectl top e HPA baseado em CPU/memoria | +| NGINX Ingress | Expoe servicos HTTP/HTTPS/SSH via LoadBalancer | + +## Apos o Setup + +### Configurar kubectl + +```bash +export KUBECONFIG=$PWD/kubeconfig +kubectl get nodes +``` + +### Configurar talosctl + +```bash +export TALOSCONFIG=$PWD/talosconfig +talosctl -n health +``` + +### Ver logs do Autoscaler + +```bash +kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler -f +``` + +### Ver IP do LoadBalancer + +```bash +kubectl get svc -n ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}' +``` + +### Ver StorageClass + +```bash +kubectl get storageclass +# Esperado: hcloud-volumes (default) +``` + +### Ver uso de recursos + +```bash +kubectl top nodes # Uso de CPU/memoria por no +kubectl top pods -A # Uso de CPU/memoria por pod +``` + +### Testar Autoscaler + +```bash +# Criar pods pending (vai escalar workers) +kubectl create deployment test --image=nginx --replicas=20 + +# Acompanhar +kubectl get nodes -w + +# Limpar +kubectl delete deployment test +``` + +## Proximos Passos + +1. **Configurar DNS** - Apontar dominio para o IP do LoadBalancer +2. **Deploy n8n** (aula-09) - Workflow automation com PostgreSQL e Redis +3. **Deploy GitLab** (aula-10) - Git + Container Registry + SSH + +## Arquivos + +``` +aula-08/ +├── main.tf # Recursos principais (servers, LB, Talos) +├── variables.tf # Variaveis de entrada +├── outputs.tf # Outputs do cluster +├── versions.tf # Versoes dos providers +├── setup.sh # Script de setup interativo +├── cleanup.sh # Script de destruicao +├── cluster-autoscaler.yaml # Manifesto do autoscaler +├── install-nginx-ingress.sh # Instala NGINX Ingress com LB +├── install-metrics-server.sh # Instala Metrics Server (kubectl top, HPA) +├── nginx-ingress-values.yaml # Configuracao do NGINX Ingress +└── talos-patches/ # Patches de configuracao Talos + ├── control-plane.yaml + └── worker.yaml +``` diff --git a/aula-08/cleanup.sh b/aula-08/cleanup.sh index 977e3bd..a27cc20 100755 --- a/aula-08/cleanup.sh +++ b/aula-08/cleanup.sh @@ -20,7 +20,7 @@ cd "$SCRIPT_DIR" 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"; } +log_error() { echo -e "${RED}[ERRO]${NC} $1"; } echo "" echo "============================================" @@ -57,6 +57,9 @@ fi log_warn "ATENÇÃO: Esta operação irá DESTRUIR todos os recursos!" echo "" echo "Recursos que serão removidos:" +echo " - NGINX Ingress Controller" +echo " - Hetzner CSI Driver" +echo " - LoadBalancer (Hetzner LB)" echo " - 3x Control Plane nodes" echo " - Workers (incluindo os criados pelo autoscaler)" echo " - Rede privada" @@ -99,6 +102,76 @@ if [ -f "terraform.tfvars" ]; then fi fi +# ========================================================================== +# Remover LoadBalancer criado pelo CCM (não gerenciado pelo OpenTofu) +# ========================================================================== +# O LoadBalancer é criado pelo Hetzner CCM quando o NGINX Ingress é instalado. +# Ele está conectado à subnet e impede a destruição da rede se não for removido. + +log_info "Verificando LoadBalancer do Hetzner CCM..." + +# Tentar via kubectl primeiro (se cluster ainda acessível) +if [ -f "kubeconfig" ]; then + export KUBECONFIG="$SCRIPT_DIR/kubeconfig" + if kubectl cluster-info &>/dev/null; then + if kubectl get svc -n ingress-nginx nginx-ingress-ingress-nginx-controller &>/dev/null; then + log_info "Removendo Service LoadBalancer via kubectl..." + kubectl delete svc nginx-ingress-ingress-nginx-controller -n ingress-nginx --timeout=60s 2>/dev/null || true + sleep 10 # Aguardar CCM processar a remoção + fi + fi +fi + +# Fallback: deletar diretamente via hcloud CLI +if [ -f "terraform.tfvars" ]; then + HCLOUD_TOKEN=$(grep 'hcloud_token' terraform.tfvars | cut -d'"' -f2) + if [ -n "$HCLOUD_TOKEN" ]; then + LB_NAME="k8s-ingress" # Nome definido no nginx-ingress-values.yaml + if HCLOUD_TOKEN="$HCLOUD_TOKEN" hcloud load-balancer describe "$LB_NAME" &>/dev/null; then + log_info "Removendo LoadBalancer '$LB_NAME' via hcloud CLI..." + HCLOUD_TOKEN="$HCLOUD_TOKEN" hcloud load-balancer delete "$LB_NAME" --quiet 2>/dev/null || true + log_success "LoadBalancer removido" + sleep 5 # Aguardar Hetzner processar + else + log_info "LoadBalancer '$LB_NAME' não encontrado (OK)" + fi + fi +fi + +# ========================================================================== +# Remover NGINX Ingress Controller +# ========================================================================== +# Instalado pelo setup.sh para expor serviços HTTP/HTTPS/SSH + +if [ -f "kubeconfig" ]; then + export KUBECONFIG="$SCRIPT_DIR/kubeconfig" + if kubectl cluster-info &>/dev/null; then + if helm status ingress-nginx -n ingress-nginx &>/dev/null; then + log_info "Removendo NGINX Ingress Controller..." + helm uninstall ingress-nginx -n ingress-nginx --wait 2>/dev/null || true + kubectl delete namespace ingress-nginx --wait=false 2>/dev/null || true + log_success "NGINX Ingress removido" + sleep 5 # Aguardar processamento + fi + fi +fi + +# ========================================================================== +# Remover Hetzner CSI Driver +# ========================================================================== +# Instalado pelo setup.sh para provisionar volumes persistentes + +if [ -f "kubeconfig" ]; then + export KUBECONFIG="$SCRIPT_DIR/kubeconfig" + if kubectl cluster-info &>/dev/null; then + if helm status hcloud-csi -n kube-system &>/dev/null; then + log_info "Removendo Hetzner CSI Driver..." + helm uninstall hcloud-csi -n kube-system --wait 2>/dev/null || true + log_success "Hetzner CSI Driver removido" + fi + fi +fi + echo "" log_info "Destruindo infraestrutura via OpenTofu..." echo "" diff --git a/aula-08/cluster-autoscaler.yaml b/aula-08/cluster-autoscaler.yaml index bb81910..a37c6df 100644 --- a/aula-08/cluster-autoscaler.yaml +++ b/aula-08/cluster-autoscaler.yaml @@ -106,17 +106,17 @@ spec: app: cluster-autoscaler spec: serviceAccountName: cluster-autoscaler - # Use host network to access external APIs (Hetzner) - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - # Workaround: Talos DNS proxy doesn't forward to upstream correctly - hostAliases: - - ip: "213.239.246.73" - hostnames: - - "api.hetzner.cloud" containers: - name: cluster-autoscaler image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.31.0 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + runAsNonRoot: true + runAsUser: 65532 + seccompProfile: + type: RuntimeDefault command: - ./cluster-autoscaler - --cloud-provider=hetzner diff --git a/aula-08/install-autoscaler.sh b/aula-08/install-autoscaler.sh deleted file mode 100755 index 33172ed..0000000 --- a/aula-08/install-autoscaler.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/bash - -############################################################ -# Instala o Cluster Autoscaler no cluster Talos -# Requer: cluster provisionado via setup.sh -############################################################ - -set -e - -# Cores -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -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"; } - -echo "" -echo "============================================" -echo " Instalando Cluster Autoscaler" -echo "============================================" -echo "" - -# Verificar pré-requisitos -if [ ! -f "kubeconfig" ]; then - log_error "kubeconfig não encontrado! Execute setup.sh primeiro." - exit 1 -fi - -if [ ! -f "terraform.tfvars" ]; then - log_error "terraform.tfvars não encontrado!" - exit 1 -fi - -export KUBECONFIG="$SCRIPT_DIR/kubeconfig" - -# Verificar conexão com cluster -log_info "Verificando conexão com o cluster..." -if ! kubectl get nodes &>/dev/null; then - log_error "Não foi possível conectar ao cluster!" - exit 1 -fi -log_success "Conectado ao cluster" - -# Obter valores do OpenTofu -log_info "Obtendo configurações do OpenTofu..." - -WORKER_CONFIG_BASE64=$(tofu output -raw autoscaler_worker_config 2>/dev/null) -TALOS_IMAGE_ID=$(tofu output -raw autoscaler_image_id 2>/dev/null) -CLUSTER_NAME=$(tofu output -raw cluster_name 2>/dev/null) -NETWORK_ID=$(tofu output -raw network_id 2>/dev/null) -FIREWALL_ID=$(tofu output -raw firewall_id 2>/dev/null) -SSH_KEY_NAME=$(tofu output -raw ssh_key_name 2>/dev/null) - -# Obter token do terraform.tfvars -HCLOUD_TOKEN=$(grep 'hcloud_token' terraform.tfvars | cut -d'"' -f2) - -if [ -z "$WORKER_CONFIG_BASE64" ] || [ -z "$HCLOUD_TOKEN" ]; then - log_error "Não foi possível obter as configurações necessárias!" - exit 1 -fi - -log_success "Configurações obtidas" -echo " - Cluster: $CLUSTER_NAME" -echo " - Image ID: $TALOS_IMAGE_ID" -echo " - Network ID: $NETWORK_ID" -echo " - SSH Key: $SSH_KEY_NAME" -echo "" - -# Criar namespace com política privileged (necessário para hostNetwork) -log_info "Criando namespace cluster-autoscaler..." -kubectl create namespace cluster-autoscaler --dry-run=client -o yaml | kubectl apply -f - -kubectl label namespace cluster-autoscaler pod-security.kubernetes.io/enforce=privileged --overwrite - -# Criar secret com credenciais -log_info "Criando secret com credenciais..." -kubectl create secret generic hcloud-autoscaler \ - --namespace cluster-autoscaler \ - --from-literal=token="$HCLOUD_TOKEN" \ - --from-literal=cloud-init="$WORKER_CONFIG_BASE64" \ - --dry-run=client -o yaml | kubectl apply -f - - -log_success "Secret criado" - -# Aplicar RBAC e Deployment -log_info "Aplicando manifesto do cluster-autoscaler..." - -# Substituir variáveis no template e aplicar -cat cluster-autoscaler.yaml | \ - sed "s|\${TALOS_IMAGE_ID}|$TALOS_IMAGE_ID|g" | \ - sed "s|\${NETWORK_NAME}|$CLUSTER_NAME-network|g" | \ - sed "s|\${FIREWALL_NAME}|$CLUSTER_NAME-firewall|g" | \ - sed "s|\${SSH_KEY_NAME}|$SSH_KEY_NAME|g" | \ - kubectl apply -f - - -log_success "Cluster Autoscaler instalado!" - -# Aguardar pod ficar pronto -log_info "Aguardando pod do autoscaler..." -kubectl wait --for=condition=ready pod \ - -l app=cluster-autoscaler \ - -n cluster-autoscaler \ - --timeout=120s - -echo "" -log_success "Cluster Autoscaler pronto!" - -echo "" -echo "============================================" -echo " Configuração do Autoscaler" -echo "============================================" -echo "" -echo " Pool: worker-pool" -echo " Tipo: CAX11 (ARM64)" -echo " Região: nbg1 (Nuremberg)" -echo " Min nodes: 1" -echo " Max nodes: 5" -echo "" -echo " Scale down após: 5 minutos" -echo " Utilização mínima: 50%" -echo "" -echo "Comandos úteis:" -echo "" -echo " # Ver logs do autoscaler" -echo " kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler -f" -echo "" -echo " # Ver status dos nodes" -echo " kubectl get nodes" -echo "" -echo " # Testar scale up (criar pods pending)" -echo " kubectl create deployment test --image=nginx --replicas=10" -echo "" diff --git a/aula-08/install-metrics-server.sh b/aula-08/install-metrics-server.sh new file mode 100755 index 0000000..75ff9e7 --- /dev/null +++ b/aula-08/install-metrics-server.sh @@ -0,0 +1,121 @@ +#!/bin/bash +# ============================================================================= +# Instala o Metrics Server para suporte a HPA e kubectl top +# ============================================================================= +# +# O Metrics Server é necessário para: +# - Horizontal Pod Autoscaler (HPA) baseado em CPU/memória +# - kubectl top pods/nodes +# - Decisões de scheduling baseadas em métricas +# +# Pré-requisitos: +# - Cluster Kubernetes rodando +# - Helm 3.x instalado +# +# Uso: +# ./install-metrics-server.sh +# +# ============================================================================= + +set -e + +# Cores para output +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"; } +log_error() { echo -e "${RED}[ERRO]${NC} $1"; } + +# ============================================================================= +# VERIFICAÇÕES +# ============================================================================= + +log_info "Verificando pré-requisitos..." + +if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado" + exit 1 +fi + +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado" + exit 1 +fi + +# Verificar conexão com o cluster +if ! kubectl cluster-info &> /dev/null; then + log_error "Não foi possível conectar ao cluster" + log_error "Verifique se KUBECONFIG está configurado corretamente" + exit 1 +fi + +log_success "Pré-requisitos OK" + +# ============================================================================= +# INSTALAR METRICS SERVER +# ============================================================================= + +log_info "Adicionando repositório Helm..." +helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/ 2>/dev/null || true +helm repo update + +log_info "Instalando Metrics Server..." + +# Nota: --kubelet-insecure-tls é necessário para Talos Linux +# pois os certificados do kubelet não são assinados pela CA do cluster +helm upgrade --install metrics-server metrics-server/metrics-server \ + -n kube-system \ + --set 'args[0]=--kubelet-insecure-tls' \ + --wait \ + --timeout 2m + +log_success "Metrics Server instalado!" + +# ============================================================================= +# VERIFICAR INSTALAÇÃO +# ============================================================================= + +log_info "Aguardando Metrics Server ficar pronto..." + +for i in {1..30}; do + if kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" &> /dev/null; then + break + fi + echo -n "." + sleep 2 +done + +echo "" + +# Testar se as métricas estão disponíveis +if kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" &> /dev/null; then + log_success "Metrics API disponível!" + echo "" + log_info "Testando kubectl top nodes..." + kubectl top nodes 2>/dev/null || log_warn "Métricas ainda sendo coletadas, aguarde alguns segundos..." +else + log_warn "Metrics API ainda não disponível" + log_warn "Aguarde alguns segundos e execute: kubectl top nodes" +fi + +# ============================================================================= +# RESUMO +# ============================================================================= + +echo "" +echo "==============================================" +echo -e "${GREEN} Metrics Server Instalado!${NC}" +echo "==============================================" +echo "" +echo "Agora você pode usar:" +echo " kubectl top nodes # Ver uso de recursos dos nós" +echo " kubectl top pods # Ver uso de recursos dos pods" +echo "" +echo "HPAs agora podem escalar baseado em CPU/memória!" +echo "" +echo "==============================================" diff --git a/aula-08/install-nginx-ingress.sh b/aula-08/install-nginx-ingress.sh new file mode 100755 index 0000000..83e65b0 --- /dev/null +++ b/aula-08/install-nginx-ingress.sh @@ -0,0 +1,139 @@ +#!/bin/bash +# ============================================================================= +# Instala/Atualiza o NGINX Ingress Controller com LoadBalancer Hetzner +# ============================================================================= +# +# Este script configura o NGINX Ingress para: +# - Usar LoadBalancer da Hetzner (requer CCM instalado) +# - Suportar TCP passthrough para SSH do GitLab +# - Comunicação via rede privada +# +# Pré-requisitos: +# - Cluster Kubernetes rodando +# - Hetzner CCM instalado (./install-ccm.sh) +# - Helm 3.x instalado +# +# Uso: +# ./install-nginx-ingress.sh +# +# ============================================================================= + +set -e + +# Cores para output +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"; } +log_error() { echo -e "${RED}[ERRO]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ============================================================================= +# VERIFICAÇÕES +# ============================================================================= + +log_info "Verificando pré-requisitos..." + +if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado" + exit 1 +fi + +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado" + exit 1 +fi + +# Verificar se CCM está instalado +if ! kubectl get deployment hccm-hcloud-cloud-controller-manager -n kube-system &> /dev/null; then + log_warn "Hetzner CCM não parece estar instalado" + log_warn "Execute ./install-ccm.sh primeiro para LoadBalancer automático" + read -p "Continuar mesmo assim? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi +fi + +log_success "Pré-requisitos OK" + +# ============================================================================= +# INSTALAR NGINX INGRESS +# ============================================================================= + +log_info "Adicionando repositório Helm..." +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 2>/dev/null || true +helm repo update + +log_info "Instalando/Atualizando NGINX Ingress Controller..." + +helm upgrade --install nginx-ingress ingress-nginx/ingress-nginx \ + -n ingress-nginx \ + --create-namespace \ + -f "$SCRIPT_DIR/nginx-ingress-values.yaml" \ + --wait \ + --timeout 5m + +log_success "NGINX Ingress instalado!" + +# ============================================================================= +# AGUARDAR LOADBALANCER +# ============================================================================= + +log_info "Aguardando LoadBalancer receber IP externo..." +echo "(pode levar 1-2 minutos)" + +for i in {1..60}; do + EXTERNAL_IP=$(kubectl get svc nginx-ingress-ingress-nginx-controller \ + -n ingress-nginx \ + -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") + + if [ -n "$EXTERNAL_IP" ] && [ "$EXTERNAL_IP" != "" ]; then + break + fi + + echo -n "." + sleep 2 +done + +echo "" + +if [ -n "$EXTERNAL_IP" ] && [ "$EXTERNAL_IP" != "" ]; then + log_success "LoadBalancer IP: $EXTERNAL_IP" +else + log_warn "LoadBalancer ainda não tem IP. Verifique com:" + echo " kubectl get svc -n ingress-nginx" +fi + +# ============================================================================= +# RESUMO +# ============================================================================= + +echo "" +echo "==============================================" +echo -e "${GREEN} NGINX Ingress Configurado!${NC}" +echo "==============================================" +echo "" +echo "LoadBalancer IP: ${EXTERNAL_IP:-}" +echo "" +echo "Portas expostas:" +echo " - 80 (HTTP)" +echo " - 443 (HTTPS)" +echo " - 22 (SSH - para GitLab)" +echo "" +echo "Próximos passos:" +echo " 1. Configure DNS apontando para o IP acima:" +echo " - n8n.kube.quest → $EXTERNAL_IP" +echo " - git.kube.quest → $EXTERNAL_IP" +echo " - registry.git.kube.quest → $EXTERNAL_IP" +echo "" +echo " 2. Instale o GitLab:" +echo " cd ../aula-09 && ./setup.sh" +echo "" +echo "==============================================" diff --git a/aula-08/main.tf b/aula-08/main.tf index ed141e5..23a35ac 100644 --- a/aula-08/main.tf +++ b/aula-08/main.tf @@ -32,7 +32,12 @@ resource "random_string" "cluster_id" { } locals { - cluster_name = "talos-${random_string.cluster_id.result}" + cluster_name = "talos-${random_string.cluster_id.result}" + control_plane_count = var.enable_ha ? 3 : 1 + + # Endpoint: LoadBalancer IP if enabled, otherwise Floating IP + cluster_endpoint_ip = var.enable_loadbalancer ? hcloud_load_balancer.cluster[0].ipv4 : hcloud_floating_ip.control_plane[0].ip_address + common_labels = { cluster = local.cluster_name environment = var.environment @@ -191,7 +196,7 @@ resource "hcloud_placement_group" "cluster" { ############################################################ resource "hcloud_server" "control_plane" { - count = 3 + count = local.control_plane_count name = "${local.cluster_name}-cp-${count.index}" server_type = "cax11" image = data.hcloud_image.talos.id @@ -218,14 +223,15 @@ resource "hcloud_server" "control_plane" { } resource "hcloud_server_network" "control_plane" { - count = 3 + count = local.control_plane_count server_id = hcloud_server.control_plane[count.index].id network_id = hcloud_network.cluster.id ip = "10.0.1.${10 + count.index}" } -# Floating IP for stable control plane access +# Floating IP for stable control plane access (only if LoadBalancer is disabled) resource "hcloud_floating_ip" "control_plane" { + count = var.enable_loadbalancer ? 0 : 1 type = "ipv4" name = "${local.cluster_name}-cp-ip" home_location = "nbg1" @@ -233,10 +239,139 @@ resource "hcloud_floating_ip" "control_plane" { } resource "hcloud_floating_ip_assignment" "control_plane" { - floating_ip_id = hcloud_floating_ip.control_plane.id + count = var.enable_loadbalancer ? 0 : 1 + floating_ip_id = hcloud_floating_ip.control_plane[0].id server_id = hcloud_server.control_plane[0].id } +############################################################ +# LOAD BALANCER (for HA access to control plane and ingress) +############################################################ + +resource "hcloud_load_balancer" "cluster" { + count = var.enable_loadbalancer ? 1 : 0 + name = "${local.cluster_name}-lb" + load_balancer_type = "lb11" + location = "nbg1" + labels = local.common_labels +} + +resource "hcloud_load_balancer_network" "cluster" { + count = var.enable_loadbalancer ? 1 : 0 + load_balancer_id = hcloud_load_balancer.cluster[0].id + network_id = hcloud_network.cluster.id + ip = "10.0.1.2" + + depends_on = [hcloud_network_subnet.cluster] +} + +# Kubernetes API (6443) -> Control Planes +resource "hcloud_load_balancer_service" "kubernetes_api" { + count = var.enable_loadbalancer ? 1 : 0 + load_balancer_id = hcloud_load_balancer.cluster[0].id + protocol = "tcp" + listen_port = 6443 + destination_port = 6443 + + health_check { + protocol = "tcp" + port = 6443 + interval = 10 + timeout = 5 + retries = 3 + } +} + +# Talos API (50000) -> Control Planes +resource "hcloud_load_balancer_service" "talos_api" { + count = var.enable_loadbalancer ? 1 : 0 + load_balancer_id = hcloud_load_balancer.cluster[0].id + protocol = "tcp" + listen_port = 50000 + destination_port = 50000 + + health_check { + protocol = "tcp" + port = 50000 + interval = 10 + timeout = 5 + retries = 3 + } +} + +# HTTP (80) -> Workers (NGINX Ingress) +resource "hcloud_load_balancer_service" "http" { + count = var.enable_loadbalancer ? 1 : 0 + load_balancer_id = hcloud_load_balancer.cluster[0].id + protocol = "tcp" + listen_port = 80 + destination_port = 80 + + health_check { + protocol = "tcp" + port = 80 + interval = 10 + timeout = 5 + retries = 3 + } +} + +# HTTPS (443) -> Workers (NGINX Ingress) +resource "hcloud_load_balancer_service" "https" { + count = var.enable_loadbalancer ? 1 : 0 + load_balancer_id = hcloud_load_balancer.cluster[0].id + protocol = "tcp" + listen_port = 443 + destination_port = 443 + + health_check { + protocol = "tcp" + port = 443 + interval = 10 + timeout = 5 + retries = 3 + } +} + +# SSH (22) -> Workers (GitLab SSH) +resource "hcloud_load_balancer_service" "ssh" { + count = var.enable_loadbalancer ? 1 : 0 + load_balancer_id = hcloud_load_balancer.cluster[0].id + protocol = "tcp" + listen_port = 22 + destination_port = 22 + + health_check { + protocol = "tcp" + port = 22 + interval = 10 + timeout = 5 + retries = 3 + } +} + +# LB Targets: Control Planes (for 6443 and 50000) +resource "hcloud_load_balancer_target" "control_plane" { + count = var.enable_loadbalancer ? local.control_plane_count : 0 + type = "server" + load_balancer_id = hcloud_load_balancer.cluster[0].id + server_id = hcloud_server.control_plane[count.index].id + use_private_ip = true + + depends_on = [hcloud_load_balancer_network.cluster] +} + +# LB Targets: Workers (for 80, 443, and 22) +resource "hcloud_load_balancer_target" "worker" { + count = var.enable_loadbalancer ? 1 : 0 + type = "server" + load_balancer_id = hcloud_load_balancer.cluster[0].id + server_id = hcloud_server.worker[count.index].id + use_private_ip = true + + depends_on = [hcloud_load_balancer_network.cluster] +} + ############################################################ # WORKER NODE (Single CAX11) ############################################################ @@ -288,15 +423,15 @@ resource "talos_machine_secrets" "this" { data "talos_client_configuration" "this" { cluster_name = local.cluster_name client_configuration = talos_machine_secrets.this.client_configuration - endpoints = [hcloud_floating_ip.control_plane.ip_address] + endpoints = [local.cluster_endpoint_ip] } # Control plane configuration data "talos_machine_configuration" "control_plane" { - count = 3 + count = local.control_plane_count cluster_name = local.cluster_name machine_type = "controlplane" - cluster_endpoint = "https://${hcloud_floating_ip.control_plane.ip_address}:6443" + cluster_endpoint = "https://${local.cluster_endpoint_ip}:6443" machine_secrets = talos_machine_secrets.this.machine_secrets talos_version = var.talos_version @@ -304,15 +439,16 @@ data "talos_machine_configuration" "control_plane" { templatefile("${path.module}/talos-patches/control-plane.yaml", { cluster_name = local.cluster_name node_name = hcloud_server.control_plane[count.index].name - is_ha = true + is_ha = var.enable_ha is_first_cp = count.index == 0 - etcd_peers = [for i in range(3) : "10.0.1.${10 + i}"] - floating_ip = hcloud_floating_ip.control_plane.ip_address + etcd_peers = [for i in range(local.control_plane_count) : "10.0.1.${10 + i}"] + floating_ip = local.cluster_endpoint_ip }) ] depends_on = [ hcloud_server.control_plane, + hcloud_load_balancer.cluster, hcloud_floating_ip_assignment.control_plane ] } @@ -322,7 +458,7 @@ data "talos_machine_configuration" "worker" { count = 1 cluster_name = local.cluster_name machine_type = "worker" - cluster_endpoint = "https://${hcloud_floating_ip.control_plane.ip_address}:6443" + cluster_endpoint = "https://${local.cluster_endpoint_ip}:6443" machine_secrets = talos_machine_secrets.this.machine_secrets talos_version = var.talos_version @@ -335,6 +471,7 @@ data "talos_machine_configuration" "worker" { depends_on = [ hcloud_server.worker, + hcloud_load_balancer.cluster, hcloud_floating_ip_assignment.control_plane ] } @@ -344,7 +481,7 @@ data "talos_machine_configuration" "worker" { ############################################################ resource "talos_machine_configuration_apply" "control_plane" { - count = 3 + count = local.control_plane_count client_configuration = talos_machine_secrets.this.client_configuration machine_configuration_input = data.talos_machine_configuration.control_plane[count.index].machine_configuration endpoint = hcloud_server.control_plane[count.index].ipv4_address @@ -400,11 +537,11 @@ resource "talos_cluster_kubeconfig" "this" { ############################################################ resource "local_sensitive_file" "kubeconfig" { - # Replace the internal hostname with the floating IP for external access + # Replace the internal hostname with the LB/Floating IP for external access content = replace( talos_cluster_kubeconfig.this.kubeconfig_raw, "https://${local.cluster_name}.local:6443", - "https://${hcloud_floating_ip.control_plane.ip_address}:6443" + "https://${local.cluster_endpoint_ip}:6443" ) filename = "${path.root}/kubeconfig" } diff --git a/aula-08/nginx-ingress-values.yaml b/aula-08/nginx-ingress-values.yaml new file mode 100644 index 0000000..a674bd4 --- /dev/null +++ b/aula-08/nginx-ingress-values.yaml @@ -0,0 +1,88 @@ +# ============================================================================= +# NGINX Ingress Controller - Configuração para Hetzner Cloud +# ============================================================================= +# +# Este values configura o NGINX Ingress com: +# - LoadBalancer da Hetzner (provisionado automaticamente pelo CCM) +# - Suporte a TCP para SSH do GitLab (porta 22) +# - Uso de rede privada para comunicação com os nodes +# +# Uso: +# helm upgrade --install nginx-ingress ingress-nginx/ingress-nginx \ +# -n ingress-nginx --create-namespace \ +# -f nginx-ingress-values.yaml +# +# ============================================================================= + +controller: + # Configuração do Service LoadBalancer + service: + type: LoadBalancer + + # Annotations específicas para Hetzner Cloud + annotations: + # Nome do LoadBalancer no painel Hetzner + load-balancer.hetzner.cloud/name: "k8s-ingress" + + # Localização do LoadBalancer (mesmo datacenter do cluster) + load-balancer.hetzner.cloud/location: "nbg1" + + # Usar rede privada para comunicação com nodes + # Mais seguro e sem custo de tráfego + load-balancer.hetzner.cloud/use-private-ip: "true" + + # Tipo do LoadBalancer (lb11 é o menor/mais barato) + load-balancer.hetzner.cloud/type: "lb11" + + # Health check + load-balancer.hetzner.cloud/health-check-interval: "5s" + load-balancer.hetzner.cloud/health-check-timeout: "3s" + load-balancer.hetzner.cloud/health-check-retries: "3" + + # ========================================================================== + # TCP Services - Para SSH do GitLab + # ========================================================================== + # Mapeia porta externa -> namespace/service:porta + # O GitLab Shell roda no namespace gitlab, service gitlab-gitlab-shell + tcp: + 22: "gitlab/gitlab-gitlab-shell:22" + + # Configuração do controller + config: + # Habilitar proxy protocol se necessário + # use-proxy-protocol: "true" + + # Timeouts + proxy-connect-timeout: "10" + proxy-read-timeout: "120" + proxy-send-timeout: "120" + + # Body size para uploads grandes (GitLab, n8n) + proxy-body-size: "0" + + # Keepalive + keep-alive: "75" + keep-alive-requests: "1000" + + # Recursos do controller + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + + # Métricas para monitoramento + metrics: + enabled: true + serviceMonitor: + enabled: false # Habilitar se usar Prometheus Operator + + # Admission webhook + admissionWebhooks: + enabled: true + +# Default backend (opcional) +defaultBackend: + enabled: false diff --git a/aula-08/outputs.tf b/aula-08/outputs.tf index 736647d..26ee0a0 100644 --- a/aula-08/outputs.tf +++ b/aula-08/outputs.tf @@ -26,8 +26,13 @@ output "network_cidr" { # Control Plane Information output "control_plane_ip" { - description = "Public IP address of the control plane" - value = hcloud_floating_ip.control_plane.ip_address + description = "Public IP address of the control plane (LB or Floating IP)" + value = local.cluster_endpoint_ip +} + +output "load_balancer_ip" { + description = "Public IP of the Load Balancer (if enabled)" + value = var.enable_loadbalancer ? hcloud_load_balancer.cluster[0].ipv4 : null } output "control_plane_private_ips" { @@ -70,22 +75,23 @@ output "talosconfig_path" { # API Endpoints output "kubernetes_api_endpoint" { description = "Kubernetes API server endpoint" - value = "https://${hcloud_floating_ip.control_plane.ip_address}:6443" + value = "https://${local.cluster_endpoint_ip}:6443" } output "talos_api_endpoint" { description = "Talos API endpoint for management" - value = "https://${hcloud_floating_ip.control_plane.ip_address}:50000" + value = "https://${local.cluster_endpoint_ip}:50000" } # Cost Information output "estimated_monthly_cost" { - description = "Estimated monthly cost for the infrastructure (EUR)" + description = "Estimated monthly cost for the infrastructure (USD)" value = { - control_plane = 3 * 3.79 # 3x CAX11 - worker = 1 * 3.79 # 1x CAX11 - floating_ip = 3.00 # Floating IPv4 - total = (4 * 3.79) + 3.00 # ~€18.16 + control_plane = local.control_plane_count * 4.59 + worker = 1 * 4.59 + load_balancer = var.enable_loadbalancer ? 5.99 : 0 + floating_ip = var.enable_loadbalancer ? 0 : 3.29 + total = (local.control_plane_count + 1) * 4.59 + (var.enable_loadbalancer ? 5.99 : 3.29) } } @@ -104,16 +110,15 @@ output "connection_instructions" { 2. Configure talosctl: export TALOSCONFIG=${local_sensitive_file.talosconfig.filename} - talosctl --nodes ${hcloud_floating_ip.control_plane.ip_address} health + talosctl --nodes ${local.cluster_endpoint_ip} health 3. Access Kubernetes API: - ${"https://${hcloud_floating_ip.control_plane.ip_address}:6443"} + https://${local.cluster_endpoint_ip}:6443 4. Nodes: - Control Plane: 3x CAX11 (ARM64) + Control Plane: ${local.control_plane_count}x CAX11 (ARM64) Workers: 1x CAX11 (ARM64) - - 5. Total Monthly Cost: ~€18/month + ${var.enable_loadbalancer ? "Load Balancer: LB11" : "Floating IP: IPv4"} ==================================== EOT diff --git a/aula-08/setup.sh b/aula-08/setup.sh index 3c46f1d..676eef7 100755 --- a/aula-08/setup.sh +++ b/aula-08/setup.sh @@ -32,7 +32,7 @@ log_warn() { } log_error() { - echo -e "${RED}[ERROR]${NC} $1" + echo -e "${RED}[ERRO]${NC} $1" } ############################################################ @@ -84,6 +84,18 @@ if ! command -v kubectl &> /dev/null; then fi log_success "kubectl $(kubectl version --client -o yaml 2>/dev/null | grep gitVersion | awk '{print $2}' || echo 'instalado')" +# Verificar Helm +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado!" + echo "" + echo "Instale o Helm:" + echo " brew install helm # macOS" + echo " snap install helm --classic # Linux" + echo "" + exit 1 +fi +log_success "Helm $(helm version --short 2>/dev/null | head -1)" + # Verificar hcloud CLI (opcional, mas útil) if command -v hcloud &> /dev/null; then log_success "hcloud CLI instalado" @@ -170,6 +182,52 @@ if [ "$SKIP_CREDENTIALS" != "true" ]; then log_success "Image ID: $TALOS_IMAGE_ID" echo "" + # Configuração do Cluster + echo "============================================" + echo " Configuração do Cluster" + echo "============================================" + echo "" + + # Cluster HA? + echo "4. Modo de Alta Disponibilidade (HA)" + echo "" + echo " HA = 3 Control Planes (tolerância a falhas)" + echo " Single = 1 Control Plane (menor custo)" + echo "" + read -p " Cluster HA? (S/n): " enable_ha + if [[ "$enable_ha" =~ ^[Nn]$ ]]; then + ENABLE_HA="false" + ENABLE_LB="false" + log_info "Modo Single: 1 Control Plane" + else + ENABLE_HA="true" + log_success "Modo HA: 3 Control Planes" + echo "" + + # LoadBalancer? + echo "5. LoadBalancer para o Control Plane" + echo "" + echo " Com LB: HA real (qualquer CP pode cair)" + echo " Sem LB: Floating IP (se CP-0 cair, cluster inacessível)" + echo "" + echo " O LoadBalancer também serve para:" + echo " - HTTP/HTTPS (NGINX Ingress)" + echo " - SSH (GitLab)" + echo " - Talos API" + echo "" + echo " Custo adicional: ~\$6/mes" + echo "" + read -p " Usar LoadBalancer? (S/n): " enable_lb + if [[ "$enable_lb" =~ ^[Nn]$ ]]; then + ENABLE_LB="false" + log_info "Usando Floating IP (sem HA real do CP)" + else + ENABLE_LB="true" + log_success "LoadBalancer habilitado" + fi + fi + echo "" + # Criar terraform.tfvars log_info "Criando terraform.tfvars..." cat > terraform.tfvars << EOF @@ -180,8 +238,9 @@ hcloud_token = "$HCLOUD_TOKEN" ssh_public_key = "$SSH_PUBLIC_KEY" talos_image_id = $TALOS_IMAGE_ID -environment = "workshop" -enable_monitoring = true +environment = "prod" +enable_ha = $ENABLE_HA +enable_loadbalancer = $ENABLE_LB EOF log_success "terraform.tfvars criado" fi @@ -219,16 +278,39 @@ echo "" log_success "Plano criado!" echo "" -# Mostrar resumo +# Mostrar resumo baseado na configuração echo "============================================" echo " Recursos a serem criados:" echo "============================================" echo "" -echo " - 4x CAX11 (3 CP + 1 Worker) = 4 x €3.79 = €15.16" -echo " - 1x Floating IPv4 = €3.00" -echo " - Rede/Firewall/Placement = Grátis" + +# Ler configuração do tfvars +ENABLE_HA_CONFIG=$(grep 'enable_ha' terraform.tfvars 2>/dev/null | grep -o 'true\|false' || echo "true") +ENABLE_LB_CONFIG=$(grep 'enable_loadbalancer' terraform.tfvars 2>/dev/null | grep -o 'true\|false' || echo "true") + +if [ "$ENABLE_HA_CONFIG" = "true" ]; then + CP_COUNT=3 + echo " - 3x CAX11 Control Planes = 3 x \$4.59 = \$13.77" +else + CP_COUNT=1 + echo " - 1x CAX11 Control Plane = 1 x \$4.59 = \$4.59" +fi + +echo " - 1x CAX11 Worker = 1 x \$4.59 = \$4.59" + +if [ "$ENABLE_LB_CONFIG" = "true" ]; then + echo " - 1x Load Balancer LB11 = \$5.99" + echo " - Rede/Firewall/Placement = Gratis" + LB_COST=5.99 +else + echo " - 1x Floating IPv4 = \$3.29" + echo " - Rede/Firewall/Placement = Gratis" + LB_COST=3.29 +fi + +TOTAL_COST=$(echo "scale=2; ($CP_COUNT + 1) * 4.59 + $LB_COST" | bc) echo "" -echo " Custo estimado: ~€18.16/mês (sem VAT)" +echo " Custo estimado: ~\$${TOTAL_COST}/mes" echo "" ############################################################ @@ -312,6 +394,203 @@ fi echo "" +############################################################ +# INSTALAÇÃO DO CCM (Cloud Controller Manager) +############################################################ + +echo "============================================" +echo " Instalando Hetzner Cloud Controller Manager" +echo "============================================" +echo "" + +# Obter token do terraform.tfvars +HCLOUD_TOKEN=$(grep 'hcloud_token' terraform.tfvars | cut -d'"' -f2) +NETWORK_ID=$(tofu output -raw network_id 2>/dev/null || echo "") + +if [ -z "$HCLOUD_TOKEN" ]; then + log_error "Não foi possível obter HCLOUD_TOKEN!" + exit 1 +fi + +# Criar secret para o CCM +log_info "Criando secret hcloud..." +SECRET_DATA="--from-literal=token=$HCLOUD_TOKEN" +if [ -n "$NETWORK_ID" ]; then + SECRET_DATA="$SECRET_DATA --from-literal=network=$NETWORK_ID" +fi + +kubectl create secret generic hcloud \ + $SECRET_DATA \ + -n kube-system \ + --dry-run=client -o yaml | kubectl apply -f - + +log_success "Secret criado" + +# Instalar CCM via Helm +log_info "Instalando CCM via Helm..." +helm repo add hcloud https://charts.hetzner.cloud 2>/dev/null || true +helm repo update hcloud + +HELM_ARGS="--set networking.enabled=true" +HELM_ARGS="$HELM_ARGS --set networking.clusterCIDR=10.244.0.0/16" +if [ -n "$NETWORK_ID" ]; then + HELM_ARGS="$HELM_ARGS --set networking.network.id=$NETWORK_ID" +fi + +helm upgrade --install hccm hcloud/hcloud-cloud-controller-manager \ + -n kube-system \ + $HELM_ARGS \ + --wait + +log_success "CCM instalado!" + +# Aguardar taint ser removido dos workers +log_info "Aguardando CCM inicializar workers..." +for i in {1..30}; do + if ! kubectl get nodes -o jsonpath='{.items[*].spec.taints[*].key}' 2>/dev/null | grep -q "node.cloudprovider.kubernetes.io/uninitialized"; then + log_success "Workers inicializados!" + break + fi + echo -n "." + sleep 5 +done +echo "" + +############################################################ +# INSTALAÇÃO DO CLUSTER AUTOSCALER +############################################################ + +echo "" +echo "============================================" +echo " Instalando Cluster Autoscaler" +echo "============================================" +echo "" + +# Obter configurações do OpenTofu +log_info "Obtendo configurações do OpenTofu..." + +WORKER_CONFIG_BASE64=$(tofu output -raw autoscaler_worker_config 2>/dev/null) +TALOS_IMAGE_ID=$(tofu output -raw autoscaler_image_id 2>/dev/null) +CLUSTER_NAME=$(tofu output -raw cluster_name 2>/dev/null) +FIREWALL_ID=$(tofu output -raw firewall_id 2>/dev/null) +SSH_KEY_NAME=$(tofu output -raw ssh_key_name 2>/dev/null) + +if [ -z "$WORKER_CONFIG_BASE64" ]; then + log_error "Não foi possível obter configuração do worker!" + exit 1 +fi + +log_success "Configurações obtidas" +echo " - Cluster: $CLUSTER_NAME" +echo " - Image ID: $TALOS_IMAGE_ID" +echo " - Network ID: $NETWORK_ID" +echo "" + +# Criar namespace +log_info "Criando namespace cluster-autoscaler..." +kubectl create namespace cluster-autoscaler --dry-run=client -o yaml | kubectl apply -f - +kubectl label namespace cluster-autoscaler pod-security.kubernetes.io/enforce=privileged --overwrite + +# Criar secret +log_info "Criando secret do autoscaler..." +kubectl create secret generic hcloud-autoscaler \ + --namespace cluster-autoscaler \ + --from-literal=token="$HCLOUD_TOKEN" \ + --from-literal=cloud-init="$WORKER_CONFIG_BASE64" \ + --dry-run=client -o yaml | kubectl apply -f - + +log_success "Secret criado" + +# Aplicar manifesto +log_info "Aplicando manifesto do cluster-autoscaler..." +cat cluster-autoscaler.yaml | \ + sed "s|\${TALOS_IMAGE_ID}|$TALOS_IMAGE_ID|g" | \ + sed "s|\${NETWORK_NAME}|$CLUSTER_NAME-network|g" | \ + sed "s|\${FIREWALL_NAME}|$CLUSTER_NAME-firewall|g" | \ + sed "s|\${SSH_KEY_NAME}|$SSH_KEY_NAME|g" | \ + kubectl apply -f - + +# Aguardar pod ficar pronto +log_info "Aguardando pod do autoscaler..." +kubectl wait --for=condition=ready pod \ + -l app=cluster-autoscaler \ + -n cluster-autoscaler \ + --timeout=120s + +log_success "Cluster Autoscaler instalado!" + +echo "" + +############################################################ +# INSTALAÇÃO DO HETZNER CSI DRIVER +############################################################ + +echo "============================================" +echo " Instalando Hetzner CSI Driver" +echo "============================================" +echo "" + +log_info "Instalando CSI Driver via Helm..." + +helm upgrade --install hcloud-csi hcloud/hcloud-csi \ + -n kube-system \ + --wait \ + --timeout 5m + +log_success "Hetzner CSI Driver instalado!" + +# Verificar StorageClass +log_info "Verificando StorageClass..." +kubectl get storageclass hcloud-volumes + +echo "" + +############################################################ +# INSTALAÇÃO DO NGINX INGRESS CONTROLLER +############################################################ + +echo "============================================" +echo " Instalando NGINX Ingress Controller" +echo "============================================" +echo "" + +# Detectar localização do cluster para o LoadBalancer +CLUSTER_LOCATION=$(kubectl get nodes -o jsonpath='{.items[0].metadata.labels.topology\.kubernetes\.io/zone}' 2>/dev/null | cut -d'-' -f1) +if [ -z "$CLUSTER_LOCATION" ]; then + CLUSTER_LOCATION="nbg1" # Default para Nuremberg +fi +log_info "Localização do cluster: $CLUSTER_LOCATION" + +log_info "Instalando NGINX Ingress via Helm..." +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 2>/dev/null || true +helm repo update ingress-nginx + +helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ + --namespace ingress-nginx \ + --create-namespace \ + --set controller.allowSnippetAnnotations=true \ + --set controller.config.annotations-risk-level=Critical \ + --set controller.admissionWebhooks.enabled=false \ + --set "controller.service.annotations.load-balancer\.hetzner\.cloud/location=${CLUSTER_LOCATION}" \ + --set "controller.service.annotations.load-balancer\.hetzner\.cloud/use-private-ip=true" \ + --wait --timeout 5m + +log_success "NGINX Ingress Controller instalado!" + +# Aguardar LoadBalancer obter IP +log_info "Aguardando LoadBalancer obter IP externo..." +for i in {1..30}; do + LB_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \ + -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null) + if [ -n "$LB_IP" ]; then + log_success "LoadBalancer IP: $LB_IP" + break + fi + echo -n "." + sleep 5 +done +echo "" + ############################################################ # RESUMO FINAL ############################################################ @@ -327,6 +606,19 @@ tofu output -raw kubernetes_api_endpoint 2>/dev/null && echo "" || true tofu output -raw talos_api_endpoint 2>/dev/null && echo "" || true echo "" +echo "Componentes instalados:" +echo " - Hetzner Cloud Controller Manager (CCM)" +echo " - Cluster Autoscaler (1-5 workers)" +echo " - Hetzner CSI Driver (StorageClass: hcloud-volumes)" +echo " - NGINX Ingress Controller + LoadBalancer" +echo "" + +# Mostrar 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 "pendente") +echo "LoadBalancer IP: $LB_IP" +echo "" + echo "Arquivos gerados:" echo " - kubeconfig : Configuração do kubectl" echo " - talosconfig : Configuração do talosctl" @@ -338,24 +630,12 @@ echo " # Usar kubectl com este cluster" echo " export KUBECONFIG=$SCRIPT_DIR/kubeconfig" echo " kubectl get nodes" echo "" -echo " # Usar talosctl com este cluster" -echo " export TALOSCONFIG=$SCRIPT_DIR/talosconfig" -echo " talosctl -n health" -echo "" -echo " # Ver outputs do OpenTofu" -echo " tofu output" +echo " # Ver logs do autoscaler" +echo " kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler -f" echo "" echo " # Destruir infraestrutura (CUIDADO!)" echo " ./cleanup.sh" echo "" log_success "Setup concluído!" - -echo "" -echo "============================================" -echo " Próximo passo (opcional)" -echo "============================================" -echo "" -echo " Para habilitar autoscaling de 1-5 workers:" -echo " ./install-autoscaler.sh" echo "" diff --git a/aula-08/talos-patches/control-plane.yaml b/aula-08/talos-patches/control-plane.yaml index e4cad9e..375cfce 100644 --- a/aula-08/talos-patches/control-plane.yaml +++ b/aula-08/talos-patches/control-plane.yaml @@ -26,6 +26,7 @@ machine: # Kubelet configuration kubelet: extraArgs: + cloud-provider: external max-pods: "110" kube-reserved: "cpu=200m,memory=300Mi" system-reserved: "cpu=200m,memory=200Mi" diff --git a/aula-08/talos-patches/worker.yaml b/aula-08/talos-patches/worker.yaml index 74a6648..d690abc 100644 --- a/aula-08/talos-patches/worker.yaml +++ b/aula-08/talos-patches/worker.yaml @@ -16,6 +16,7 @@ machine: # Kubelet configuration kubelet: extraArgs: + cloud-provider: external max-pods: "110" kube-reserved: "cpu=100m,memory=200Mi" system-reserved: "cpu=100m,memory=100Mi" diff --git a/aula-08/variables.tf b/aula-08/variables.tf index 726f6b2..64c9b16 100644 --- a/aula-08/variables.tf +++ b/aula-08/variables.tf @@ -2,28 +2,56 @@ # Variables for Hetzner Talos Kubernetes Cluster ############################################################ -# Authentication +# ========================================================== +# AUTENTICAÇÃO +# ========================================================== + variable "hcloud_token" { type = string description = "Hetzner Cloud API token" sensitive = true } -# Cluster Configuration +# ========================================================== +# CONFIGURAÇÃO DO CLUSTER +# ========================================================== + +variable "enable_ha" { + type = bool + description = "Enable HA mode with 3 control plane nodes" + default = true +} + +variable "enable_loadbalancer" { + type = bool + description = "Enable Hetzner Load Balancer for HA access to control plane and ingress" + default = true +} + variable "environment" { type = string description = "Environment name (prod, staging, dev)" default = "prod" + + validation { + condition = contains(["prod", "staging", "dev"], var.environment) + error_message = "Environment deve ser: prod, staging ou dev." + } } +# ========================================================== +# SSH +# ========================================================== -# SSH Configuration variable "ssh_public_key" { type = string description = "Public SSH key for emergency access to nodes" } -# Talos Configuration +# ========================================================== +# TALOS +# ========================================================== + variable "talos_image_id" { type = number description = "ID da imagem Talos customizada na Hetzner (criada na aula-07). Obtenha com: hcloud image list --type snapshot" @@ -32,30 +60,18 @@ variable "talos_image_id" { variable "talos_version" { type = string description = "Talos version to use" - default = "v1.11.2" # Match the official image version + default = "v1.11.2" + + validation { + condition = can(regex("^v[0-9]+\\.[0-9]+\\.[0-9]+$", var.talos_version)) + error_message = "talos_version deve seguir o formato semântico: v1.2.3" + } } -# Monitoring Configuration -variable "enable_monitoring" { - type = bool - description = "Enable Victoria Metrics monitoring stack" - default = true -} +# ========================================================== +# LABELS CUSTOMIZADAS +# ========================================================== -# Auto-scaling Configuration -variable "scale_up_threshold" { - type = number - description = "CPU percentage to trigger scale up" - default = 70 -} - -variable "scale_down_threshold" { - type = number - description = "CPU percentage to trigger scale down" - default = 30 -} - -# Tags for resource management variable "custom_labels" { type = map(string) description = "Custom labels to add to all resources" diff --git a/aula-09/README.md b/aula-09/README.md new file mode 100644 index 0000000..dc80d96 --- /dev/null +++ b/aula-09/README.md @@ -0,0 +1,233 @@ +# Aula 09 - n8n via Helm (Cluster Hetzner Cloud) + +Deploy do n8n workflow automation em cluster Kubernetes na Hetzner Cloud com volumes persistentes, LoadBalancer e TLS configurável. + +## Arquitetura + +``` + Internet + │ + ▼ + ┌─────────────────┐ + │ LoadBalancer │ + │ (Hetzner LB) │ + └────────┬────────┘ + │ + ▼ + ┌────────────────────────┐ + │ NGINX Ingress │ + │ n8n.{domain}:443 │ + └───────────┬────────────┘ + │ +┌─────────────────────────┼─────────────────────────┐ +│ Namespace: n8n │ +│ │ │ +│ ┌────────────────────┼────────────────────┐ │ +│ │ ▼ │ │ +│ │ ┌──────────┐ │ │ +│ │ │ Main │ │ │ +│ │ │ (n8n) │ │ │ +│ │ └────┬─────┘ │ │ +│ │ │ │ │ +│ │ ┌─────────────┼─────────────┐ │ │ +│ │ ▼ ▼ ▼ │ │ +│ │ ┌───────┐ ┌──────────┐ ┌────────┐ │ │ +│ │ │Workers│ │ Webhooks │ │ MCP │ │ │ +│ │ │ (2-5) │ │ (1-3) │ │Webhook │ │ │ +│ │ └───────┘ └──────────┘ └────────┘ │ │ +│ │ │ │ +│ │ Queue Mode │ │ +│ └────────────────────────────────────────┘ │ +│ │ │ +│ ┌───────────────┼───────────────┐ │ +│ ▼ ▼ │ +│ ┌──────────┐ ┌──────────┐ │ +│ │PostgreSQL│ │ Redis │ │ +│ │ (10Gi) │ │ (10Gi) │ │ +│ │ hcloud │ │ hcloud │ │ +│ └──────────┘ └──────────┘ │ +└───────────────────────────────────────────────────┘ + Hetzner Cloud +``` + +## 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 + +## Contexto do Cluster + +Esta aula usa o cluster Kubernetes provisionado na **aula-08**. O `kubeconfig` foi gerado automaticamente pelo OpenTofu e está em `aula-08/kubeconfig`. + +```bash +# Verificar se o cluster está acessível +export KUBECONFIG=$(pwd)/../aula-08/kubeconfig +kubectl cluster-info + +# Esperado: +# Kubernetes control plane is running at https://:6443 +``` + +## Instalação + +```bash +cd aula-09 + +# 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 n8n** (ex: `n8n.kube.quest`) - FQDN completo +2. **Usa CloudFlare?** (com proxy/CDN) +3. **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) | + +### Exemplo de Instalação + +``` +═══════════════════════════════════════════════════ + n8n no Kubernetes (Hetzner Cloud) +═══════════════════════════════════════════════════ + +Digite o hostname do n8n (ex: n8n.kube.quest): n8n.kube.quest + +Você usa CloudFlare para DNS? + 1) Sim (com proxy/CDN ativado - ícone laranja) + 2) Não +Escolha [1/2]: 1 + +[INFO] CloudFlare irá gerenciar TLS automaticamente na edge +[OK] Configuração salva em .env + +...instalação... + +═══════════════════════════════════════════════════ + Configure o DNS +═══════════════════════════════════════════════════ + +No painel do CloudFlare (https://dash.cloudflare.com): + + Tipo: A + Nome: n8n + Conteúdo: 123.45.67.89 + Proxy: ✓ (ícone laranja) + +O CloudFlare cuida do TLS automaticamente! + +Acesse: https://n8n.kube.quest +``` + +## 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) +- **n8n** com todos os componentes + +| Componente | Réplicas | Recursos | Volume | +|------------|----------|----------|--------| +| Main (n8n) | 1 | 256Mi-1Gi RAM | 10Gi | +| Workers | 2-5 (HPA) | 256Mi-512Mi RAM | - | +| Webhooks | 1-3 (HPA) | 128Mi-256Mi RAM | - | +| PostgreSQL | 1 | - | 10Gi | +| Redis | 1 | - | 10Gi | + +**Total volumes:** 30Gi (~$1.45/mês) + +## Multi-Tenant: Adicionar Clientes + +Para provisionar n8n para múltiplos clientes em namespaces separados: + +```bash +# Adicionar novo cliente +./add-client.sh acme + +# Isso cria: +# - Namespace: acme-n8n +# - n8n em: https://acme-n8n.{domain} +# - Grupo GitLab: /acme/ (se GitLab instalado - aula-10) +``` + +O script `add-client.sh` herda a configuração do `.env` gerado pelo `setup.sh`. + +### Padrão de Domínio + +Os clientes usam o padrão `{cliente}-n8n.{domain}`: +- `acme-n8n.kube.quest` +- `empresa-n8n.kube.quest` + +Se você configurou um wildcard DNS (`*.kube.quest`), os novos clientes funcionam automaticamente. + +## Arquivo de Configuração + +O `setup.sh` gera um arquivo `.env` com as configurações: + +```bash +# aula-09/.env (gerado automaticamente) +N8N_HOST=n8n.kube.quest +DOMAIN=kube.quest +USE_CLOUDFLARE=true +USE_LETSENCRYPT=false +LETSENCRYPT_EMAIL= +``` + +Este arquivo é usado pelo `add-client.sh` e em re-execuções do `setup.sh`. + +## Comandos Úteis + +```bash +# Ver pods +kubectl get pods -n n8n + +# Ver logs do n8n +kubectl logs -f -n n8n deployment/n8n + +# Ver autoscaling +kubectl get hpa -n n8n + +# Ver volumes +kubectl get pvc -n n8n + +# Ver certificado (se Let's Encrypt) +kubectl get certificate -n n8n + +# Desinstalar (apenas n8n) +./cleanup.sh + +# Ou manualmente: +helm uninstall n8n -n n8n +kubectl delete pvc --all -n n8n +kubectl delete ns n8n +``` + +**Nota:** O `cleanup.sh` remove apenas n8n, clientes e cert-manager. A infraestrutura (CSI Driver, NGINX Ingress, LoadBalancer) é mantida pois pertence à aula-08. + +## Custos Estimados + +| Recurso | Custo/mês | +|---------|-----------| +| Volumes (3x 10Gi) | ~$1.45 | +| Worker (compartilhado) | ~$0.80 | +| **Total por cliente** | ~$2.25 | + +## Para Ambiente Local + +Se você quer testar em um cluster local (Docker Desktop, minikube), veja a **aula-06**. diff --git a/aula-09/add-client.sh b/aula-09/add-client.sh new file mode 100755 index 0000000..f0823b3 --- /dev/null +++ b/aula-09/add-client.sh @@ -0,0 +1,299 @@ +#!/bin/bash +# ============================================================================= +# Script para Adicionar Novo Cliente +# ============================================================================= +# +# Este script provisiona um novo cliente com: +# - n8n em namespace separado (cliente-n8n) +# - Grupo no GitLab compartilhado (opcional) +# +# Uso: +# ./add-client.sh +# +# Exemplo: +# ./add-client.sh acme +# -> Cria namespace acme-n8n com n8n +# -> Cria grupo /acme no GitLab (se disponível) +# +# Pré-requisitos: +# - ./setup.sh já executado (gera .env com configurações) +# - kubectl configurado +# - Helm instalado +# +# ============================================================================= + +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}[ERRO]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CLIENT=$1 + +# ============================================================================= +# CARREGAR CONFIGURAÇÃO +# ============================================================================= + +if [[ ! -f "$SCRIPT_DIR/.env" ]]; then + log_error "Arquivo .env não encontrado!" + log_error "Execute ./setup.sh primeiro para configurar o ambiente." + exit 1 +fi + +source "$SCRIPT_DIR/.env" + +# Suporte ao novo formato (N8N_HOST) e legado (DOMAIN) +if [[ -n "$N8N_HOST" && -z "$DOMAIN" ]]; then + # Extrair DOMAIN do N8N_HOST (ex: n8n.kube.quest → kube.quest) + DOMAIN=$(echo "$N8N_HOST" | sed 's/^[^.]*\.//') +fi + +if [[ -z "$DOMAIN" ]]; then + log_error "Variável DOMAIN ou N8N_HOST não definida no .env" + log_error "Execute ./setup.sh novamente." + exit 1 +fi + +# Carregar configuração do GitLab se disponível +GITLAB_HOST="" +if [[ -f "$SCRIPT_DIR/../aula-10/.env" ]]; then + source "$SCRIPT_DIR/../aula-10/.env" +fi + +# ============================================================================= +# VALIDAÇÕES +# ============================================================================= + +if [ -z "$CLIENT" ]; then + echo "Uso: $0 " + echo "" + echo "Exemplo:" + echo " $0 acme" + echo "" + echo "Configuração atual (de .env):" + echo " Domínio: ${DOMAIN}" + echo " CloudFlare: ${USE_CLOUDFLARE}" + echo " Let's Encrypt: ${USE_LETSENCRYPT}" + echo "" + echo "Isso vai criar:" + echo " - Namespace: acme-n8n" + PROTOCOL="https" + [[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]] && PROTOCOL="http" + echo " - n8n em: ${PROTOCOL}://acme-n8n.${DOMAIN}" + if [[ -n "$GITLAB_HOST" ]]; then + echo " - GitLab grupo: https://${GITLAB_HOST}/acme/ (se configurado)" + fi + exit 1 +fi + +# Validar nome do cliente (só letras minúsculas, números e hífens) +if ! [[ "$CLIENT" =~ ^[a-z0-9-]+$ ]]; then + log_error "Nome do cliente inválido: $CLIENT" + log_error "Use apenas letras minúsculas, números e hífens" + exit 1 +fi + +log_info "Verificando pré-requisitos..." + +if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado" + exit 1 +fi + +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado" + exit 1 +fi + +if ! kubectl cluster-info &> /dev/null; then + log_error "Não foi possível conectar ao cluster" + exit 1 +fi + +log_success "Pré-requisitos OK" + +echo "" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo -e "${CYAN} Adicionando Cliente: $CLIENT${NC}" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo "" + +# ============================================================================= +# 1. CRIAR NAMESPACE PARA N8N +# ============================================================================= + +log_info "Criando namespace ${CLIENT}-n8n..." + +kubectl create namespace ${CLIENT}-n8n --dry-run=client -o yaml | kubectl apply -f - + +log_success "Namespace criado" + +# ============================================================================= +# 2. INSTALAR N8N +# ============================================================================= + +log_info "Instalando n8n para ${CLIENT}..." + +# Verificar se base-n8n-values.yaml existe +if [ ! -f "$SCRIPT_DIR/base-n8n-values.yaml" ]; then + log_error "Arquivo base-n8n-values.yaml não encontrado em $SCRIPT_DIR" + exit 1 +fi + +# Adicionar repo se necessário +helm repo add community-charts https://community-charts.github.io/helm-charts 2>/dev/null || true +helm repo update + +# Construir argumentos do Helm dinamicamente +HELM_ARGS="" + +# Configurar host do ingress +HELM_ARGS="$HELM_ARGS --set ingress.enabled=true" +HELM_ARGS="$HELM_ARGS --set ingress.className=nginx" +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].host=${CLIENT}-n8n.${DOMAIN}" +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].path=/" +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].pathType=Prefix" + +# Configurar webhook URL +if [[ "$USE_CLOUDFLARE" == "true" || "$USE_LETSENCRYPT" == "true" ]]; then + HELM_ARGS="$HELM_ARGS --set webhook.url=https://${CLIENT}-n8n.${DOMAIN}" +else + HELM_ARGS="$HELM_ARGS --set webhook.url=http://${CLIENT}-n8n.${DOMAIN}" +fi + +# Configurar TLS (se Let's Encrypt) +if [[ "$USE_LETSENCRYPT" == "true" ]]; then + HELM_ARGS="$HELM_ARGS --set ingress.annotations.cert-manager\\.io/cluster-issuer=letsencrypt" + HELM_ARGS="$HELM_ARGS --set ingress.tls[0].hosts[0]=${CLIENT}-n8n.${DOMAIN}" + HELM_ARGS="$HELM_ARGS --set ingress.tls[0].secretName=${CLIENT}-n8n-tls" +fi + +# Configurar N8N_SECURE_COOKIE (se HTTPS) +if [[ "$USE_CLOUDFLARE" == "true" || "$USE_LETSENCRYPT" == "true" ]]; then + HELM_ARGS="$HELM_ARGS --set main.extraEnvVars.N8N_SECURE_COOKIE=true" + HELM_ARGS="$HELM_ARGS --set worker.extraEnvVars.N8N_SECURE_COOKIE=true" + HELM_ARGS="$HELM_ARGS --set webhook.extraEnvVars.N8N_SECURE_COOKIE=true" +fi + +# Instalar n8n +eval helm upgrade --install ${CLIENT}-n8n community-charts/n8n \ + --namespace ${CLIENT}-n8n \ + -f "$SCRIPT_DIR/base-n8n-values.yaml" \ + $HELM_ARGS \ + --wait \ + --timeout 5m + +log_success "n8n instalado" + +# ============================================================================= +# 3. CRIAR GRUPO NO GITLAB (opcional) +# ============================================================================= + +log_info "Verificando GitLab..." + +# Obter token do GitLab +if [ -z "$GITLAB_TOKEN" ]; then + GITLAB_TOKEN=$(kubectl get secret gitlab-gitlab-initial-root-password \ + -n gitlab \ + -o jsonpath='{.data.password}' 2>/dev/null | base64 -d || echo "") +fi + +if [ -z "$GITLAB_TOKEN" ]; then + log_warn "GitLab não encontrado ou não configurado" + log_warn "Pule esta etapa ou crie o grupo manualmente" +else + log_info "Criando grupo no GitLab..." + # Usar GITLAB_HOST se disponível (da aula-10/.env) + if [[ -n "$GITLAB_HOST" ]]; then + GITLAB_URL="https://${GITLAB_HOST}" + else + GITLAB_URL="https://git.${DOMAIN}" + fi + + # Criar grupo via API + RESULT=$(curl -s --request POST "${GITLAB_URL}/api/v4/groups" \ + --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \ + --form "name=${CLIENT}" \ + --form "path=${CLIENT}" \ + --form "visibility=private" \ + --write-out "%{http_code}" \ + --output /tmp/gitlab-group-result.json 2>/dev/null || echo "000") + + if [ "$RESULT" == "201" ]; then + log_success "Grupo criado no GitLab" + elif [ "$RESULT" == "400" ]; then + log_warn "Grupo já existe ou erro de validação" + else + log_warn "Não foi possível criar grupo (HTTP $RESULT)" + log_warn "Crie manualmente em ${GITLAB_URL}/admin/groups/new" + fi +fi + +# ============================================================================= +# RESUMO +# ============================================================================= + +# Obter IP do LoadBalancer +LB_IP=$(kubectl get svc ingress-nginx-controller \ + -n ingress-nginx \ + -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") + +# Determinar protocolo +PROTOCOL="https" +[[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]] && PROTOCOL="http" + +echo "" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo -e "${GREEN} Cliente $CLIENT Provisionado!${NC}" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo "" +echo "Serviços:" +echo -e " n8n: ${GREEN}${PROTOCOL}://${CLIENT}-n8n.${DOMAIN}${NC}" +if [ -n "$GITLAB_TOKEN" ]; then + if [[ -n "$GITLAB_HOST" ]]; then + echo -e " GitLab: ${GREEN}https://${GITLAB_HOST}/${CLIENT}/${NC}" + else + echo -e " GitLab: ${GREEN}https://git.${DOMAIN}/${CLIENT}/${NC}" + fi +fi +echo "" + +if [[ "$USE_CLOUDFLARE" == "true" ]]; then + echo "Configure no CloudFlare:" + echo -e " ${YELLOW}Tipo:${NC} A" + echo -e " ${YELLOW}Nome:${NC} ${CLIENT}-n8n" + echo -e " ${YELLOW}Conteúdo:${NC} ${GREEN}${LB_IP}${NC}" + echo -e " ${YELLOW}Proxy:${NC} ✓ (ícone laranja)" + echo "" + echo "(Ou use o wildcard *.${DOMAIN} se já configurado)" +else + echo "Configure o DNS:" + echo -e " ${YELLOW}Tipo:${NC} A" + echo -e " ${YELLOW}Nome:${NC} ${CLIENT}-n8n" + echo -e " ${YELLOW}Valor:${NC} ${GREEN}${LB_IP}${NC}" + echo "" + echo "(Ou use o wildcard *.${DOMAIN} se já configurado)" +fi + +if [ -n "$GITLAB_TOKEN" ]; then + echo "" + echo "Git clone:" + if [[ -n "$GITLAB_HOST" ]]; then + echo " git clone git@${GITLAB_HOST}:${CLIENT}/meu-projeto.git" + else + echo " git clone git@git.${DOMAIN}:${CLIENT}/meu-projeto.git" + fi +fi + +echo "" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" diff --git a/aula-09/base-n8n-values.yaml b/aula-09/base-n8n-values.yaml new file mode 100644 index 0000000..bb1fd4b --- /dev/null +++ b/aula-09/base-n8n-values.yaml @@ -0,0 +1,140 @@ +# ============================================================================= +# Base Values para n8n de Clientes +# ============================================================================= +# +# Este arquivo contém configurações base para instalações de n8n por cliente. +# O script add-client.sh usa este arquivo como template. +# +# Personalizações por cliente são feitas via --set no helm install. +# +# ============================================================================= + +# Imagem +image: + repository: n8nio/n8n + tag: "2.0.3" + pullPolicy: IfNotPresent + +# Chave de criptografia (será sobrescrita por cliente se necessário) +# IMPORTANTE: Em produção, use uma chave única por cliente +encryptionKey: "workshop-n8n-encryption-key-32ch" + +# ============================================================================= +# BANCO DE DADOS +# ============================================================================= + +db: + type: postgresdb + +postgresql: + enabled: true + auth: + database: n8n + username: n8n + # Em produção, use secrets únicos por cliente + password: "n8n-postgres-workshop-2025" + primary: + persistence: + enabled: true + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + storageClass: hcloud-volumes + +# ============================================================================= +# REDIS +# ============================================================================= + +redis: + enabled: true + architecture: standalone + auth: + enabled: true + password: "n8n-redis-workshop-2025" + master: + persistence: + enabled: true + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + storageClass: hcloud-volumes + +# ============================================================================= +# INGRESS +# ============================================================================= +# Host é configurado dinamicamente pelo add-client.sh + +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + +# ============================================================================= +# MAIN NODE +# ============================================================================= +# N8N_SECURE_COOKIE é configurado dinamicamente pelo add-client.sh via --set + +main: + persistence: + enabled: true + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + storageClass: hcloud-volumes + mountPath: "/home/node/.n8n" + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + +# ============================================================================= +# WORKERS (Queue Mode) +# ============================================================================= + +worker: + mode: queue + count: 1 + concurrency: 10 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + +# ============================================================================= +# WEBHOOKS +# ============================================================================= + +webhook: + mode: queue + count: 1 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + autoscaling: + enabled: false # Manter simples para clientes menores + +# ============================================================================= +# SEGURANÇA +# ============================================================================= + +securityContext: + runAsNonRoot: true + runAsUser: 1000 diff --git a/aula-09/cleanup.sh b/aula-09/cleanup.sh new file mode 100755 index 0000000..156b727 --- /dev/null +++ b/aula-09/cleanup.sh @@ -0,0 +1,185 @@ +#!/bin/bash +# ============================================================================= +# Cleanup da Aula 09 - Remove n8n e clientes +# ============================================================================= +# +# Este script remove: +# - n8n principal (namespace n8n) +# - Todos os clientes (namespaces *-n8n) +# - cert-manager (se instalado por esta aula) +# - Arquivo .env +# +# NÃO remove (gerenciados pela aula-08): +# - NGINX Ingress Controller +# - Hetzner CSI Driver +# - LoadBalancer +# +# ============================================================================= + +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}[ERRO]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "" +echo -e "${CYAN}============================================${NC}" +echo -e "${CYAN} Cleanup - Aula 09 (n8n)${NC}" +echo -e "${CYAN}============================================${NC}" +echo "" + +log_warn "ATENÇÃO: Isso vai remover os recursos da aula 09:" +echo " - n8n principal e todos os clientes" +echo " - Volumes persistentes (PostgreSQL, Redis, n8n)" +echo " - cert-manager (se instalado)" +echo " - Arquivo .env" +echo "" +echo "Mantido (aula-08):" +echo " - NGINX Ingress Controller" +echo " - Hetzner CSI Driver" +echo " - LoadBalancer" +echo "" +read -p "Continuar? (digite 'sim' para confirmar): " confirm + +if [ "$confirm" != "sim" ]; then + log_info "Operação cancelada" + exit 0 +fi + +echo "" + +# ============================================================================= +# 1. REMOVER n8n PRINCIPAL +# ============================================================================= + +log_info "=== Removendo n8n principal ===" + +if helm status n8n -n n8n &> /dev/null; then + log_info "Removendo Helm release n8n..." + helm uninstall n8n -n n8n --wait 2>/dev/null || true + log_success "n8n removido" +else + log_info "n8n não está instalado" +fi + +# Remover PVCs +if kubectl get pvc -n n8n &> /dev/null 2>&1; then + log_info "Removendo PVCs do namespace n8n..." + kubectl delete pvc --all -n n8n --wait=false 2>/dev/null || true +fi + +# Remover namespace +if kubectl get namespace n8n &> /dev/null; then + log_info "Removendo namespace n8n..." + kubectl delete namespace n8n --wait=false 2>/dev/null || true + log_success "Namespace n8n removido" +fi + +# ============================================================================= +# 2. REMOVER CLIENTES (namespaces *-n8n) +# ============================================================================= + +log_info "=== Removendo clientes ===" + +CLIENT_NAMESPACES=$(kubectl get namespaces -o name 2>/dev/null | grep -E ".*-n8n$" | sed 's/namespace\///' || true) + +if [ -n "$CLIENT_NAMESPACES" ]; then + for ns in $CLIENT_NAMESPACES; do + log_info "Removendo cliente: $ns" + + # Remover helm release + RELEASE_NAME=$(echo "$ns" | sed 's/-n8n$//') + helm uninstall "${RELEASE_NAME}-n8n" -n "$ns" --wait 2>/dev/null || true + + # Remover PVCs + kubectl delete pvc --all -n "$ns" --wait=false 2>/dev/null || true + + # Remover namespace + kubectl delete namespace "$ns" --wait=false 2>/dev/null || true + + log_success "Cliente $ns removido" + done +else + log_info "Nenhum cliente encontrado" +fi + +# ============================================================================= +# 3. REMOVER CERT-MANAGER (se instalado) +# ============================================================================= + +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 \ + challenges.acme.cert-manager.io \ + clusterissuers.cert-manager.io \ + issuers.cert-manager.io \ + 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" +else + log_info "cert-manager não está instalado" +fi + +# ============================================================================= +# 4. REMOVER ARQUIVO .env +# ============================================================================= + +log_info "=== Removendo arquivo de configuração ===" + +if [ -f "$SCRIPT_DIR/.env" ]; then + rm -f "$SCRIPT_DIR/.env" + log_success "Arquivo .env removido" +else + log_info "Arquivo .env não existe" +fi + +# ============================================================================= +# RESUMO +# ============================================================================= + +echo "" +echo -e "${CYAN}============================================${NC}" +echo -e "${GREEN} Cleanup Concluído!${NC}" +echo -e "${CYAN}============================================${NC}" +echo "" +echo "Removido:" +echo " - n8n principal e clientes" +echo " - cert-manager (se existia)" +echo " - Arquivo .env" +echo "" +echo "Mantido (aula-08):" +echo " - NGINX Ingress Controller" +echo " - Hetzner CSI Driver" +echo " - LoadBalancer" +echo " - Cluster Kubernetes" +echo "" +log_warn "Volumes Hetzner podem demorar alguns minutos para serem" +log_warn "completamente removidos. Verifique no painel da Hetzner." +echo "" diff --git a/aula-09/custom-values.yaml b/aula-09/custom-values.yaml new file mode 100644 index 0000000..152f366 --- /dev/null +++ b/aula-09/custom-values.yaml @@ -0,0 +1,148 @@ +# ============================================================================= +# n8n Helm Chart - Custom Values +# ============================================================================= +# Aula 06 - Deploy n8n via Helm +# +# Chart: community-charts/n8n +# Docs: https://community-charts.github.io/docs/charts/n8n/configuration +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Imagem +# ----------------------------------------------------------------------------- +image: + repository: n8nio/n8n + tag: "2.0.3" + pullPolicy: IfNotPresent + +# ----------------------------------------------------------------------------- +# Chave de Criptografia +# ----------------------------------------------------------------------------- +encryptionKey: "workshop-n8n-encryption-key-32ch" + +# ----------------------------------------------------------------------------- +# Banco de Dados PostgreSQL +# ----------------------------------------------------------------------------- +db: + type: postgresdb + +postgresql: + enabled: true + auth: + database: n8n + username: n8n + password: "n8n-postgres-workshop-2025" + primary: + persistence: + enabled: true + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + +# ----------------------------------------------------------------------------- +# Redis (necessário para Queue Mode) +# ----------------------------------------------------------------------------- +redis: + enabled: true + architecture: standalone + auth: + enabled: true + password: "n8n-redis-workshop-2025" + master: + persistence: + enabled: true + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + +# ----------------------------------------------------------------------------- +# Ingress NGINX +# ----------------------------------------------------------------------------- +# Host e TLS são configurados dinamicamente pelo setup.sh via --set +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "50m" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + +# ----------------------------------------------------------------------------- +# Main Node +# ----------------------------------------------------------------------------- +# N8N_SECURE_COOKIE é configurado dinamicamente pelo setup.sh via --set +main: + persistence: + enabled: true + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + mountPath: "/home/node/.n8n" + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +# ----------------------------------------------------------------------------- +# Workers (Queue Mode) +# ----------------------------------------------------------------------------- +worker: + mode: queue + count: 2 + concurrency: 10 + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 5 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + +# ----------------------------------------------------------------------------- +# Webhooks (Queue Mode) +# ----------------------------------------------------------------------------- +# URL é configurada dinamicamente pelo setup.sh via --set +webhook: + mode: queue + count: 1 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + +# ----------------------------------------------------------------------------- +# Configurações de Segurança +# ----------------------------------------------------------------------------- +securityContext: + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault diff --git a/aula-09/setup.sh b/aula-09/setup.sh new file mode 100755 index 0000000..ae83c81 --- /dev/null +++ b/aula-09/setup.sh @@ -0,0 +1,526 @@ +#!/bin/bash +# ============================================================================= +# Setup da Aula 09 - n8n via Helm (Hetzner Cloud) +# ============================================================================= +# +# Este script instala e configura: +# 1. cert-manager (opcional, para Let's Encrypt) +# 2. n8n com PostgreSQL, Redis, Workers e Webhooks +# +# Pré-requisitos: +# - Kubernetes cluster Talos na Hetzner (aula-08) com: +# - Hetzner CSI Driver (StorageClass: hcloud-volumes) +# - NGINX Ingress Controller com LoadBalancer +# - kubectl configurado (KUBECONFIG=../aula-08/kubeconfig) +# - Helm 3.x instalado +# +# Uso: +# export KUBECONFIG=$(pwd)/../aula-08/kubeconfig +# chmod +x setup.sh +# ./setup.sh +# +# ============================================================================= + +set -e # Para na primeira falha + +# 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' # No Color + +# 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}[ERRO]${NC} $1"; } + +# Diretório do script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Variáveis de configuração (serão preenchidas pelo usuário ou .env) +N8N_HOST="" +DOMAIN="" # Extraído automaticamente do N8N_HOST +USE_CLOUDFLARE="" +USE_LETSENCRYPT="" +LETSENCRYPT_EMAIL="" + +# ============================================================================= +# FUNÇÕES DE CONFIGURAÇÃO +# ============================================================================= + +save_config() { + cat > "$SCRIPT_DIR/.env" </dev/null || true + helm repo update jetstack + + if helm status cert-manager -n cert-manager &> /dev/null; then + log_success "cert-manager já está instalado" + else + helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.16.2 \ + --set crds.enabled=true \ + --wait \ + --timeout 5m + + log_success "cert-manager instalado" + fi +} + +create_cluster_issuer() { + log_info "Criando ClusterIssuer para Let's Encrypt..." + + cat </dev/null || echo "") + + # Extrair nome do host (parte antes do primeiro ponto) + HOST_NAME=$(echo "$N8N_HOST" | cut -d. -f1) + + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configure o DNS${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" + + if [[ "$USE_CLOUDFLARE" == "true" ]]; then + echo "" + 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 " ${YELLOW}Tipo:${NC} A" + echo -e " ${YELLOW}Nome:${NC} * (wildcard, para multi-tenant)" + 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 "" + 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}" + echo "" + echo -e " ${YELLOW}Tipo:${NC} A" + echo -e " ${YELLOW}Nome:${NC} * (wildcard, para multi-tenant)" + 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." + fi + fi + + # Determinar protocolo + PROTOCOL="https" + if [[ "$USE_CLOUDFLARE" == "false" && "$USE_LETSENCRYPT" == "false" ]]; then + PROTOCOL="http" + fi + + echo "" + echo -e "Acesse: ${GREEN}${PROTOCOL}://${N8N_HOST}${NC}" +} + +# ============================================================================= +# VERIFICAÇÕES INICIAIS +# ============================================================================= + +log_info "Verificando pré-requisitos..." + +# Verificar kubectl +if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado. Instale o kubectl primeiro." + exit 1 +fi +log_success "kubectl encontrado" + +# Verificar conexão com cluster +if ! kubectl cluster-info &> /dev/null; then + log_error "Não foi possível conectar ao cluster Kubernetes." + exit 1 +fi +log_success "Conectado ao cluster Kubernetes" + +# Verificar helm +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado. Instale o Helm >= 3.14 primeiro." + log_info "Instalação: https://helm.sh/docs/intro/install/" + exit 1 +fi +log_success "Helm $(helm version --short) encontrado" + +# ============================================================================= +# COLETAR INPUT DO USUÁRIO +# ============================================================================= + +collect_user_input + +echo "" + +# ============================================================================= +# 1. VERIFICAR PRÉ-REQUISITOS DA AULA-08 +# ============================================================================= + +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." + exit 1 +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." + exit 1 +fi +log_success "NGINX Ingress Controller instalado" + +echo "" + +# ============================================================================= +# 2. INSTALAR CERT-MANAGER (se Let's Encrypt) +# ============================================================================= + +if [[ "$USE_LETSENCRYPT" == "true" ]]; then + log_info "=== Configurando cert-manager ===" + install_cert_manager + create_cluster_issuer + echo "" +fi + +# ============================================================================= +# 3. CRIAR NAMESPACE E APLICAR SECRETS +# ============================================================================= + +log_info "=== Configurando namespace n8n ===" + +# Criar namespace se não existir +if kubectl get namespace n8n &> /dev/null; then + log_success "Namespace n8n já existe" +else + log_info "Criando namespace n8n..." + kubectl create namespace n8n + log_success "Namespace n8n criado" +fi + +echo "" + +# ============================================================================= +# 4. INSTALAR n8n VIA HELM +# ============================================================================= + +log_info "=== Instalando n8n ===" + +# Adicionar repo do community-charts +log_info "Adicionando repositório Helm do community-charts..." +helm repo add community-charts https://community-charts.github.io/helm-charts 2>/dev/null || true +helm repo update + +# Construir argumentos do Helm dinamicamente +HELM_ARGS="" + +# Configurar host do ingress +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].host=${N8N_HOST}" +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].path=/" +HELM_ARGS="$HELM_ARGS --set ingress.hosts[0].paths[0].pathType=Prefix" + +# Configurar webhook URL +if [[ "$USE_CLOUDFLARE" == "true" || "$USE_LETSENCRYPT" == "true" ]]; then + HELM_ARGS="$HELM_ARGS --set webhook.url=https://${N8N_HOST}" +else + HELM_ARGS="$HELM_ARGS --set webhook.url=http://${N8N_HOST}" +fi + +# Configurar TLS +if [[ "$USE_LETSENCRYPT" == "true" ]]; then + HELM_ARGS="$HELM_ARGS --set ingress.annotations.cert-manager\\.io/cluster-issuer=letsencrypt" + HELM_ARGS="$HELM_ARGS --set ingress.tls[0].hosts[0]=${N8N_HOST}" + HELM_ARGS="$HELM_ARGS --set ingress.tls[0].secretName=n8n-tls" +fi + +# Configurar N8N_SECURE_COOKIE +if [[ "$USE_CLOUDFLARE" == "true" || "$USE_LETSENCRYPT" == "true" ]]; then + HELM_ARGS="$HELM_ARGS --set main.extraEnvVars.N8N_SECURE_COOKIE=true" + HELM_ARGS="$HELM_ARGS --set worker.extraEnvVars.N8N_SECURE_COOKIE=true" + HELM_ARGS="$HELM_ARGS --set webhook.extraEnvVars.N8N_SECURE_COOKIE=true" +fi + +# Verificar se já está instalado +if helm status n8n -n n8n &> /dev/null; then + log_warn "n8n já está instalado. Atualizando..." + eval helm upgrade n8n community-charts/n8n \ + --namespace n8n \ + --values "$SCRIPT_DIR/custom-values.yaml" \ + $HELM_ARGS \ + --wait \ + --timeout 10m + log_success "n8n atualizado com sucesso!" +else + log_info "Instalando n8n..." + eval helm install n8n community-charts/n8n \ + --namespace n8n \ + --values "$SCRIPT_DIR/custom-values.yaml" \ + $HELM_ARGS \ + --wait \ + --timeout 10m + log_success "n8n instalado com sucesso!" +fi + +echo "" + +# ============================================================================= +# 5. AGUARDAR PODS FICAREM PRONTOS +# ============================================================================= + +log_info "=== Aguardando pods ficarem prontos ===" + +log_info "Aguardando PostgreSQL..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/name=postgresql \ + -n n8n \ + --timeout=180s 2>/dev/null || log_warn "Timeout aguardando PostgreSQL" + +log_info "Aguardando Redis..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/name=redis \ + -n n8n \ + --timeout=120s 2>/dev/null || log_warn "Timeout aguardando Redis" + +log_info "Aguardando n8n main..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/component=main \ + -n n8n \ + --timeout=180s 2>/dev/null || log_warn "Timeout aguardando n8n main" + +log_info "Aguardando n8n workers..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/component=worker \ + -n n8n \ + --timeout=120s 2>/dev/null || log_warn "Timeout aguardando workers" + +log_info "Aguardando n8n webhooks..." +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/component=webhook \ + -n n8n \ + --timeout=120s 2>/dev/null || log_warn "Timeout aguardando webhooks" + +log_success "Todos os componentes estão rodando!" +echo "" + +# ============================================================================= +# RESUMO FINAL +# ============================================================================= + +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo -e "${GREEN} Setup Completo!${NC}" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo "" +echo "Componentes instalados:" +if [[ "$USE_LETSENCRYPT" == "true" ]]; then + echo " - cert-manager (ClusterIssuer: letsencrypt)" +fi +echo " - n8n (namespace: n8n)" +echo " - Main node" +echo " - Workers (2-5 réplicas, autoscaling)" +echo " - Webhooks (1-3 réplicas, autoscaling)" +echo " - PostgreSQL" +echo " - Redis" +echo "" +echo "Configuração:" +echo " Hostname: ${N8N_HOST}" +echo " CloudFlare: ${USE_CLOUDFLARE}" +echo " Let's Encrypt: ${USE_LETSENCRYPT}" + +# Mostrar instruções de DNS +show_dns_instructions + +echo "" +echo "Comandos úteis:" +echo " # Ver todos os pods" +echo " kubectl get pods -n n8n" +echo "" +echo " # Ver logs do n8n" +echo " kubectl logs -f -n n8n deployment/n8n" +echo "" +echo " # Ver HPA (autoscaler)" +echo " kubectl get hpa -n n8n" +echo "" +echo " # Ver ingress" +echo " kubectl get ingress -n n8n" +echo "" +if [[ "$USE_LETSENCRYPT" == "true" ]]; then + echo " # Ver status do certificado" + echo " kubectl get certificate -n n8n" + echo "" +fi +echo " # Adicionar novo cliente" +echo " ./add-client.sh " +echo "" +echo " # Desinstalar" +echo " helm uninstall n8n -n n8n" +echo "" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo "" + +# Mostrar status atual +log_info "Status atual dos pods:" +echo "" +kubectl get pods -n n8n +echo "" +log_info "Status do HPA:" +echo "" +kubectl get hpa -n n8n 2>/dev/null || echo "HPA ainda não criado" +echo "" +log_info "Status do Ingress:" +echo "" +kubectl get ingress -n n8n +echo "" diff --git a/aula-10/README.md b/aula-10/README.md new file mode 100644 index 0000000..376112b --- /dev/null +++ b/aula-10/README.md @@ -0,0 +1,339 @@ +# 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 + +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 + +## 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 +``` + +## 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 +3. **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: + +```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 | +| 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: + +```yaml +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`: +- 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): + +```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= +``` + +## Acesso + +### 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 +``` + +### Container Registry + +```bash +# 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 + +```bash +# 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 + +```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 +``` + +### SSH não conecta + +```bash +# 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 + +```bash +# 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. + +## 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/) diff --git a/aula-10/cleanup.sh b/aula-10/cleanup.sh new file mode 100755 index 0000000..78e30f8 --- /dev/null +++ b/aula-10/cleanup.sh @@ -0,0 +1,184 @@ +#!/bin/bash +# ============================================================================= +# Cleanup da Aula 10 - Remove GitLab +# ============================================================================= +# +# Este script remove: +# - GitLab (Helm release) +# - PVCs (dados persistentes) +# - Secrets +# - Namespace gitlab +# - cert-manager (se instalado por esta aula) +# - Arquivo .env +# +# NÃO remove (gerenciados pela aula-08): +# - NGINX Ingress Controller +# - Hetzner CSI Driver +# - LoadBalancer +# +# ATENÇÃO: Todos os dados do GitLab serão perdidos! +# +# ============================================================================= + +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}[ERRO]${NC} $1"; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "" +echo -e "${CYAN}============================================${NC}" +echo -e "${CYAN} Cleanup - Aula 10 (GitLab)${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, MinIO, Gitaly)" +echo " - cert-manager (se instalado)" +echo " - Arquivo .env" +echo "" +echo "Mantido (aula-08):" +echo " - NGINX Ingress Controller" +echo " - Hetzner CSI Driver" +echo " - LoadBalancer" +echo "" +read -p "Continuar? (digite 'sim' para confirmar): " confirm + +if [ "$confirm" != "sim" ]; then + log_info "Operação cancelada" + exit 0 +fi + +echo "" + +# ============================================================================= +# 1. REMOVER TCP SERVICES DO NGINX +# ============================================================================= + +log_info "=== Removendo configuração TCP do NGINX ===" + +if kubectl get configmap tcp-services -n ingress-nginx &> /dev/null; then + kubectl patch configmap tcp-services \ + -n ingress-nginx \ + --type json \ + -p '[{"op": "remove", "path": "/data/22"}]' 2>/dev/null || true + log_success "Configuração TCP removida" +else + log_info "ConfigMap tcp-services não encontrado" +fi + +# ============================================================================= +# 2. DESINSTALAR GITLAB +# ============================================================================= + +log_info "=== Removendo GitLab ===" + +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" +else + log_info "GitLab 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 +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" +fi + +# ============================================================================= +# 3. REMOVER CERT-MANAGER (se instalado) +# ============================================================================= + +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 \ + challenges.acme.cert-manager.io \ + clusterissuers.cert-manager.io \ + issuers.cert-manager.io \ + 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" +else + log_info "cert-manager não está instalado" +fi + +# ============================================================================= +# 4. REMOVER ARQUIVO .env +# ============================================================================= + +log_info "=== Removendo arquivo de configuração ===" + +if [ -f "$SCRIPT_DIR/.env" ]; then + rm -f "$SCRIPT_DIR/.env" + log_success "Arquivo .env removido" +else + log_info "Arquivo .env não existe" +fi + +# ============================================================================= +# RESUMO +# ============================================================================= + +echo "" +echo -e "${CYAN}============================================${NC}" +echo -e "${GREEN} Cleanup Concluído!${NC}" +echo -e "${CYAN}============================================${NC}" +echo "" +echo "Removido:" +echo " - GitLab e todos os componentes" +echo " - cert-manager (se existia)" +echo " - Arquivo .env" +echo "" +echo "Mantido (aula-08):" +echo " - NGINX Ingress Controller" +echo " - Hetzner CSI Driver" +echo " - LoadBalancer" +echo " - Cluster Kubernetes" +echo "" +log_warn "Volumes Hetzner podem demorar alguns minutos para serem" +log_warn "completamente removidos. Verifique no painel da Hetzner." +echo "" diff --git a/aula-10/gitlab-values.yaml b/aula-10/gitlab-values.yaml new file mode 100644 index 0000000..099c9ef --- /dev/null +++ b/aula-10/gitlab-values.yaml @@ -0,0 +1,298 @@ +# ============================================================================= +# GitLab Helm Chart - Configuração Base para Hetzner CAX11 +# ============================================================================= +# +# Esta configuração: +# - Usa NGINX Ingress Controller externo (instalado na aula-08) +# - Define ~5GB de recursos distribuídos em 2 workers CAX11 (antiAffinity) +# - Desabilita componentes não essenciais para economizar recursos +# - Configura Registry para container images +# +# Valores dinâmicos (configurados via --set no setup.sh): +# - global.hosts.domain +# - global.hosts.gitlab.name +# - global.hosts.registry.name +# - global.hosts.minio.name +# - global.hosts.https +# - global.ingress.tls.enabled +# - global.ingress.configureCertmanager +# +# Cenário CloudFlare (proxy ativo): +# - global.ingress.tls.enabled=false +# - global.hosts.https=true +# - gitlab.webservice.workhorse.trustedCIDRsForXForwardedFor=[CloudFlare IPs] +# +# Cenário Let's Encrypt: +# - global.ingress.tls.enabled=true +# - global.hosts.https=true +# - global.ingress.configureCertmanager=true +# - global.ingress.annotations.cert-manager.io/cluster-issuer=letsencrypt-prod +# +# ============================================================================= + +global: + # Usar Ingress Controller externo + ingress: + class: nginx + # configureCertmanager é definido via --set no setup.sh (true para Let's Encrypt) + 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" + nginx.ingress.kubernetes.io/proxy-buffering: "off" + # cert-manager.io/cluster-issuer é adicionado via --set quando Let's Encrypt + + # Timezone + time_zone: America/Sao_Paulo + + # Pod Security - evitar warnings de PodSecurity + pod: + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + + # Email (opcional - configurar depois) + # email: + # from: gitlab@kube.quest + # display_name: GitLab + # reply_to: noreply@kube.quest + +# ============================================================================= +# NGINX BUNDLED - DESABILITAR (usamos o externo) +# ============================================================================= +nginx-ingress: + enabled: false + +# ============================================================================= +# CERT-MANAGER - DESABILITAR BUNDLED +# ============================================================================= +# O cert-manager é instalado separadamente se Let's Encrypt for escolhido. +# global.ingress.configureCertmanager controla a integração. + +# ============================================================================= +# GITLAB COMPONENTS +# ============================================================================= + +gitlab: + # Webservice (Rails app - UI e API) + # 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: 2Gi + 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 + affinity: + 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 + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: webservice + topologyKey: kubernetes.io/hostname + + # Gitaly (Git storage) + gitaly: + resources: + requests: + memory: 512Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 500m + persistence: + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + storageClass: hcloud-volumes + + # GitLab Shell (SSH) + gitlab-shell: + minReplicas: 1 + maxReplicas: 1 + service: + type: ClusterIP # TCP passthrough pelo NGINX + resources: + requests: + memory: 64Mi + cpu: 50m + limits: + memory: 128Mi + cpu: 100m + + # Toolbox (backup, rake tasks) + toolbox: + enabled: true + resources: + requests: + memory: 256Mi + cpu: 50m + limits: + memory: 512Mi + cpu: 200m + + # GitLab Exporter (métricas) + gitlab-exporter: + enabled: false # Economiza recursos + + # Migrations + migrations: + resources: + requests: + memory: 256Mi + cpu: 100m + + # KAS (Kubernetes Agent Server) - desabilitar + kas: + enabled: false + +# ============================================================================= +# POSTGRESQL +# ============================================================================= +postgresql: + install: true + primary: + resources: + requests: + memory: 512Mi + cpu: 100m + limits: + memory: 1Gi + cpu: 500m + persistence: + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + storageClass: hcloud-volumes + +# ============================================================================= +# REDIS +# ============================================================================= +redis: + install: true + master: + resources: + requests: + memory: 256Mi + cpu: 50m + limits: + memory: 512Mi + cpu: 200m + persistence: + size: 10Gi # Mínimo Hetzner ($0.0484/GB) + storageClass: hcloud-volumes + +# ============================================================================= +# MINIO (Object Storage) +# ============================================================================= +# NOTA: As imagens padrão do GitLab chart não suportam ARM64. +# Usamos as imagens oficiais multi-arch do MinIO. +minio: + install: true + image: minio/minio + imageTag: RELEASE.2024-06-13T22-53-53Z + minioMc: + image: minio/mc + tag: RELEASE.2024-06-12T14-34-03Z + resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 256Mi + cpu: 200m + persistence: + size: 10Gi + storageClass: hcloud-volumes + +# Ou usar object storage externo (S3, etc): +# global: +# minio: +# enabled: false +# appConfig: +# object_store: +# enabled: true +# connection: +# secret: gitlab-object-storage +# key: connection + +# ============================================================================= +# REGISTRY (Container Registry) +# ============================================================================= +registry: + enabled: true + hpa: + minReplicas: 1 + maxReplicas: 1 + resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 256Mi + cpu: 200m + # Storage usa MinIO bundled automaticamente quando minio.install=true + +# ============================================================================= +# COMPONENTES DESABILITADOS (economia de recursos) +# ============================================================================= + +# GitLab Runner - instalar separadamente se necessário +gitlab-runner: + install: false + +# Prometheus - usar Victoria Metrics da aula-05 +prometheus: + install: false + +# Grafana +grafana: + install: false + +# GitLab Pages +gitlab-pages: + enabled: false + +# Praefect (Gitaly HA) - não necessário para instalação pequena +praefect: + enabled: false + +# Spamcheck +spamcheck: + enabled: false + +# ============================================================================= +# UPGRADECHECK +# ============================================================================= +upgradeCheck: + enabled: false diff --git a/aula-10/setup.sh b/aula-10/setup.sh new file mode 100755 index 0000000..8838f27 --- /dev/null +++ b/aula-10/setup.sh @@ -0,0 +1,569 @@ +#!/bin/bash +# ============================================================================= +# Setup da Aula 10 - GitLab via Helm (Hetzner Cloud) +# ============================================================================= +# +# Este script instala e configura: +# 1. cert-manager (opcional, para Let's Encrypt) +# 2. GitLab com todos os componentes +# +# Pré-requisitos: +# - Kubernetes cluster Talos na Hetzner (aula-08) com: +# - Hetzner CSI Driver (StorageClass: hcloud-volumes) +# - NGINX Ingress Controller com LoadBalancer +# - kubectl configurado (KUBECONFIG=../aula-08/kubeconfig) +# - Helm 3.x instalado +# +# Uso: +# export KUBECONFIG=$(pwd)/../aula-08/kubeconfig +# chmod +x setup.sh +# ./setup.sh +# +# ============================================================================= + +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}[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 +USE_CLOUDFLARE="" +USE_LETSENCRYPT="" +LETSENCRYPT_EMAIL="" + +# ============================================================================= +# FUNÇÕES DE CONFIGURAÇÃO +# ============================================================================= + +save_config() { + cat > "$SCRIPT_DIR/.env" </dev/null || true + helm repo update jetstack + + if helm status cert-manager -n cert-manager &> /dev/null; then + log_success "cert-manager já está instalado" + else + helm install cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version v1.16.2 \ + --set crds.enabled=true \ + --wait \ + --timeout 5m + + log_success "cert-manager instalado" + fi +} + +create_cluster_issuer() { + log_info "Criando ClusterIssuer para Let's Encrypt..." + + cat </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}$//" ) + + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configure o DNS${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" + + if [[ "$USE_CLOUDFLARE" == "true" ]]; then + echo "" + 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}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 "" + 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}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." + 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}" +} + +# ============================================================================= +# VERIFICAÇÕES INICIAIS +# ============================================================================= + +log_info "Verificando pré-requisitos..." + +if ! command -v kubectl &> /dev/null; then + log_error "kubectl não encontrado" + exit 1 +fi +log_success "kubectl encontrado" + +if ! command -v helm &> /dev/null; then + log_error "Helm não encontrado" + exit 1 +fi +log_success "Helm $(helm version --short) encontrado" + +if ! kubectl cluster-info &> /dev/null; then + log_error "Não foi possível conectar ao cluster" + exit 1 +fi +log_success "Conectado ao cluster Kubernetes" + +# ============================================================================= +# 1. VERIFICAR PRÉ-REQUISITOS DA AULA-08 +# ============================================================================= + +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." + exit 1 +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." + exit 1 +fi +log_success "NGINX Ingress Controller instalado" + +echo "" + +# ============================================================================= +# 2. COLETAR INPUT DO USUÁRIO +# ============================================================================= + +collect_user_input + +echo "" + +# ============================================================================= +# 3. INSTALAR CERT-MANAGER (se Let's Encrypt) +# ============================================================================= + +if [[ "$USE_LETSENCRYPT" == "true" ]]; then + log_info "=== Configurando cert-manager ===" + install_cert_manager + create_cluster_issuer + echo "" +fi + +# ============================================================================= +# 4. ADICIONAR REPOSITÓRIO HELM +# ============================================================================= + +log_info "=== Preparando instalação do GitLab ===" + +log_info "Adicionando repositório Helm do GitLab..." +helm repo add gitlab https://charts.gitlab.io/ 2>/dev/null || true +helm repo update + +# ============================================================================= +# 5. CRIAR NAMESPACE +# ============================================================================= + +log_info "Criando namespace gitlab..." +kubectl create namespace gitlab --dry-run=client -o yaml | kubectl apply -f - + +echo "" + +# ============================================================================= +# 6. INSTALAR GITLAB VIA HELM +# ============================================================================= + +log_info "=== Instalando GitLab (isso pode levar 10-15 minutos) ===" + +# Construir argumentos do Helm dinamicamente +HELM_ARGS="" + +# 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}" +HELM_ARGS="$HELM_ARGS --set global.hosts.minio.name=minio.${GITLAB_HOST}" + +# Configurar 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" +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.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.tls.enabled=false" + HELM_ARGS="$HELM_ARGS --set global.hosts.https=false" +fi + +# 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" \ + $HELM_ARGS \ + --timeout 15m \ + --wait + log_success "GitLab atualizado com sucesso!" +else + log_info "Instalando GitLab..." + eval helm install gitlab gitlab/gitlab \ + --namespace gitlab \ + -f "$SCRIPT_DIR/gitlab-values.yaml" \ + $HELM_ARGS \ + --timeout 15m \ + --wait + log_success "GitLab instalado com sucesso!" +fi + +echo "" + +# ============================================================================= +# 7. CONFIGURAR TCP PASSTHROUGH PARA SSH +# ============================================================================= + +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"}}' +else + kubectl create configmap tcp-services \ + -n ingress-nginx \ + --from-literal="22=gitlab/gitlab-gitlab-shell: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)" + +echo "" + +# ============================================================================= +# 8. 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 "${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 " - MinIO (object storage)" +echo " - Container Registry" +echo "" +echo "Configuração:" +echo " GitLab: ${GITLAB_HOST}" +echo " Registry: ${REGISTRY_HOST}" +echo " Domínio: ${DOMAIN}" +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 "" +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 + +# Mostrar instruções de DNS +show_dns_instructions + +echo "" +echo "Comandos úteis:" +echo " # Ver pods" +echo " kubectl get pods -n gitlab" +echo "" +echo " # Ver logs" +echo " kubectl logs -n gitlab -l app=webservice -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 "" +echo -e "${CYAN}═══════════════════════════════════════════════════${NC}" +echo "" + +# Mostrar status dos pods +log_info "Status dos pods:" +kubectl get pods -n gitlab