Workshop completo: aulas 08-10 com Talos, n8n e GitLab na Hetzner
Aula 08 - Cluster Kubernetes HA: - Setup interativo com OpenTofu para Talos na Hetzner - CCM, CSI Driver, Cluster Autoscaler, Metrics Server - NGINX Ingress com LoadBalancer (HTTP/HTTPS/SSH) Aula 09 - n8n na Hetzner: - Deploy via Helm com PostgreSQL e Redis - Suporte multi-tenant com add-client.sh - Integração com Hetzner CSI para volumes persistentes Aula 10 - GitLab na Hetzner: - Setup agnóstico: CloudFlare (trusted proxies) ou Let's Encrypt - Anti-affinity para distribuir webservice/sidekiq em nós diferentes - Container Registry e SSH via TCP passthrough - Documentação do erro 422 e solução com trustedCIDRsForXForwardedFor Melhorias gerais: - READMEs atualizados com arquitetura e troubleshooting - Scripts cleanup.sh para todas as aulas - CLAUDE.md atualizado com contexto do projeto
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -14,6 +14,7 @@
|
|||||||
**/talosconfig
|
**/talosconfig
|
||||||
**/*.pem
|
**/*.pem
|
||||||
**/*.key
|
**/*.key
|
||||||
|
**/.env
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
49
CLAUDE.md
49
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-03/**: Kubernetes lesson - high availability with replicas and readiness probes
|
||||||
- **aula-04/**: Kubernetes lesson - NGINX Ingress with Keep Request (Lua) for zero-downtime
|
- **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-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-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
|
## 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.
|
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
|
```bash
|
||||||
cd aula-06
|
cd aula-06
|
||||||
./setup.sh
|
./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)
|
### 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.
|
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.
|
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`
|
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
|
## App Behavior
|
||||||
|
|
||||||
The Node.js app (`app.js`) is intentionally designed to:
|
The Node.js app (`app.js`) is intentionally designed to:
|
||||||
|
|||||||
122
aula-01/README.md
Normal file
122
aula-01/README.md
Normal file
@@ -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**.
|
||||||
@@ -9,3 +9,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
restart: always
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/health"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 2
|
||||||
|
start_period: 5s
|
||||||
|
|||||||
107
aula-02/README.md
Normal file
107
aula-02/README.md
Normal file
@@ -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.
|
||||||
30
aula-03/cleanup.sh
Executable file
30
aula-03/cleanup.sh
Executable file
@@ -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 ""
|
||||||
43
aula-04/cleanup.sh
Executable file
43
aula-04/cleanup.sh
Executable file
@@ -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 ""
|
||||||
56
aula-05/cleanup.sh
Executable file
56
aula-05/cleanup.sh
Executable file
@@ -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 ""
|
||||||
107
aula-06/README.md
Normal file
107
aula-06/README.md
Normal file
@@ -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**.
|
||||||
51
aula-06/cleanup.sh
Executable file
51
aula-06/cleanup.sh
Executable file
@@ -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 ""
|
||||||
@@ -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
|
# Chart: community-charts/n8n
|
||||||
# Docs: https://community-charts.github.io/docs/charts/n8n/configuration
|
# Docs: https://community-charts.github.io/docs/charts/n8n/configuration
|
||||||
|
#
|
||||||
|
# Para cluster Hetzner Cloud, veja aula-09/
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -35,7 +37,7 @@ postgresql:
|
|||||||
primary:
|
primary:
|
||||||
persistence:
|
persistence:
|
||||||
enabled: true
|
enabled: true
|
||||||
size: 5Gi
|
size: 1Gi # Ambiente local - sem mínimo
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Redis (necessário para Queue Mode)
|
# Redis (necessário para Queue Mode)
|
||||||
@@ -46,6 +48,10 @@ redis:
|
|||||||
auth:
|
auth:
|
||||||
enabled: true
|
enabled: true
|
||||||
password: "n8n-redis-workshop-2025"
|
password: "n8n-redis-workshop-2025"
|
||||||
|
master:
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 1Gi # Ambiente local - sem mínimo
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Ingress NGINX
|
# Ingress NGINX
|
||||||
@@ -58,7 +64,7 @@ ingress:
|
|||||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
hosts:
|
hosts:
|
||||||
- host: n8n.kube.quest
|
- host: n8n.localhost
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
@@ -71,7 +77,7 @@ main:
|
|||||||
N8N_SECURE_COOKIE: "false" # Permite HTTP sem HTTPS (apenas para dev/workshop)
|
N8N_SECURE_COOKIE: "false" # Permite HTTP sem HTTPS (apenas para dev/workshop)
|
||||||
persistence:
|
persistence:
|
||||||
enabled: true
|
enabled: true
|
||||||
size: 2Gi
|
size: 1Gi # Ambiente local - sem mínimo
|
||||||
mountPath: "/home/node/.n8n"
|
mountPath: "/home/node/.n8n"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
@@ -121,7 +127,7 @@ worker:
|
|||||||
webhook:
|
webhook:
|
||||||
mode: queue
|
mode: queue
|
||||||
count: 1
|
count: 1
|
||||||
url: "https://n8n.kube.quest"
|
url: "http://n8n.localhost"
|
||||||
extraEnvVars:
|
extraEnvVars:
|
||||||
N8N_SECURE_COOKIE: "false"
|
N8N_SECURE_COOKIE: "false"
|
||||||
resources:
|
resources:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Setup da Aula 06 - n8n via Helm
|
# Setup da Aula 06 - n8n via Helm (Ambiente LOCAL)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
#
|
#
|
||||||
# Este script instala e configura:
|
# Este script instala e configura:
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
# 2. n8n com PostgreSQL, Redis, Workers e Webhooks
|
# 2. n8n com PostgreSQL, Redis, Workers e Webhooks
|
||||||
#
|
#
|
||||||
# Pré-requisitos:
|
# Pré-requisitos:
|
||||||
# - Kubernetes cluster rodando (k3s, minikube, kind, etc)
|
# - Kubernetes cluster LOCAL rodando (Docker Desktop, minikube, kind, k3d)
|
||||||
# - kubectl configurado
|
# - kubectl configurado
|
||||||
# - Helm 3.x instalado
|
# - Helm 3.x instalado
|
||||||
#
|
#
|
||||||
@@ -52,6 +52,10 @@ log_success "kubectl encontrado"
|
|||||||
# Verificar conexão com cluster
|
# Verificar conexão com cluster
|
||||||
if ! kubectl cluster-info &> /dev/null; then
|
if ! kubectl cluster-info &> /dev/null; then
|
||||||
log_error "Não foi possível conectar ao cluster Kubernetes."
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
log_success "Conectado ao cluster Kubernetes"
|
log_success "Conectado ao cluster Kubernetes"
|
||||||
@@ -67,68 +71,7 @@ log_success "Helm $(helm version --short) encontrado"
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 1. INSTALAR HETZNER CSI DRIVER (para provisionar volumes)
|
# 1. INSTALAR NGINX INGRESS (se não existir)
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
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)
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
log_info "=== Verificando NGINX Ingress ==="
|
log_info "=== Verificando NGINX Ingress ==="
|
||||||
@@ -152,7 +95,7 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 3. CRIAR NAMESPACE E APLICAR SECRETS
|
# 2. CRIAR NAMESPACE E APLICAR SECRETS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
log_info "=== Configurando namespace n8n ==="
|
log_info "=== Configurando namespace n8n ==="
|
||||||
@@ -169,7 +112,7 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 4. INSTALAR n8n VIA HELM
|
# 3. INSTALAR n8n VIA HELM
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
log_info "=== Instalando n8n ==="
|
log_info "=== Instalando n8n ==="
|
||||||
@@ -201,7 +144,7 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 5. AGUARDAR PODS FICAREM PRONTOS
|
# 4. AGUARDAR PODS FICAREM PRONTOS
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
log_info "=== Aguardando pods ficarem prontos ==="
|
log_info "=== Aguardando pods ficarem prontos ==="
|
||||||
@@ -248,7 +191,6 @@ echo -e "${GREEN} Setup Completo!${NC}"
|
|||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Componentes instalados:"
|
echo "Componentes instalados:"
|
||||||
echo " - Hetzner CSI Driver (StorageClass: hcloud-volumes)"
|
|
||||||
echo " - NGINX Ingress Controller"
|
echo " - NGINX Ingress Controller"
|
||||||
echo " - n8n (namespace: n8n)"
|
echo " - n8n (namespace: n8n)"
|
||||||
echo " - Main node"
|
echo " - Main node"
|
||||||
@@ -279,15 +221,6 @@ echo ""
|
|||||||
echo " # Desinstalar"
|
echo " # Desinstalar"
|
||||||
echo " helm uninstall n8n -n n8n"
|
echo " helm uninstall n8n -n n8n"
|
||||||
echo ""
|
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 <nº da release>"
|
|
||||||
echo ""
|
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|||||||
208
aula-08/README.md
Normal file
208
aula-08/README.md
Normal file
@@ -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 <IP> 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
|
||||||
|
```
|
||||||
@@ -20,7 +20,7 @@ cd "$SCRIPT_DIR"
|
|||||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||||
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||||
log_warn() { echo -e "${YELLOW}[WARN]${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 ""
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
@@ -57,6 +57,9 @@ fi
|
|||||||
log_warn "ATENÇÃO: Esta operação irá DESTRUIR todos os recursos!"
|
log_warn "ATENÇÃO: Esta operação irá DESTRUIR todos os recursos!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Recursos que serão removidos:"
|
echo "Recursos que serão removidos:"
|
||||||
|
echo " - NGINX Ingress Controller"
|
||||||
|
echo " - Hetzner CSI Driver"
|
||||||
|
echo " - LoadBalancer (Hetzner LB)"
|
||||||
echo " - 3x Control Plane nodes"
|
echo " - 3x Control Plane nodes"
|
||||||
echo " - Workers (incluindo os criados pelo autoscaler)"
|
echo " - Workers (incluindo os criados pelo autoscaler)"
|
||||||
echo " - Rede privada"
|
echo " - Rede privada"
|
||||||
@@ -99,6 +102,76 @@ if [ -f "terraform.tfvars" ]; then
|
|||||||
fi
|
fi
|
||||||
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 ""
|
echo ""
|
||||||
log_info "Destruindo infraestrutura via OpenTofu..."
|
log_info "Destruindo infraestrutura via OpenTofu..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -106,17 +106,17 @@ spec:
|
|||||||
app: cluster-autoscaler
|
app: cluster-autoscaler
|
||||||
spec:
|
spec:
|
||||||
serviceAccountName: cluster-autoscaler
|
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:
|
containers:
|
||||||
- name: cluster-autoscaler
|
- name: cluster-autoscaler
|
||||||
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.31.0
|
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.31.0
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
runAsNonRoot: true
|
||||||
|
runAsUser: 65532
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
command:
|
command:
|
||||||
- ./cluster-autoscaler
|
- ./cluster-autoscaler
|
||||||
- --cloud-provider=hetzner
|
- --cloud-provider=hetzner
|
||||||
|
|||||||
@@ -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 ""
|
|
||||||
121
aula-08/install-metrics-server.sh
Executable file
121
aula-08/install-metrics-server.sh
Executable file
@@ -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 "=============================================="
|
||||||
139
aula-08/install-nginx-ingress.sh
Executable file
139
aula-08/install-nginx-ingress.sh
Executable file
@@ -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" != "<pending>" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n "."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -n "$EXTERNAL_IP" ] && [ "$EXTERNAL_IP" != "<pending>" ]; 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:-<pendente>}"
|
||||||
|
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 "=============================================="
|
||||||
167
aula-08/main.tf
167
aula-08/main.tf
@@ -32,7 +32,12 @@ resource "random_string" "cluster_id" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
locals {
|
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 = {
|
common_labels = {
|
||||||
cluster = local.cluster_name
|
cluster = local.cluster_name
|
||||||
environment = var.environment
|
environment = var.environment
|
||||||
@@ -191,7 +196,7 @@ resource "hcloud_placement_group" "cluster" {
|
|||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
resource "hcloud_server" "control_plane" {
|
resource "hcloud_server" "control_plane" {
|
||||||
count = 3
|
count = local.control_plane_count
|
||||||
name = "${local.cluster_name}-cp-${count.index}"
|
name = "${local.cluster_name}-cp-${count.index}"
|
||||||
server_type = "cax11"
|
server_type = "cax11"
|
||||||
image = data.hcloud_image.talos.id
|
image = data.hcloud_image.talos.id
|
||||||
@@ -218,14 +223,15 @@ resource "hcloud_server" "control_plane" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "hcloud_server_network" "control_plane" {
|
resource "hcloud_server_network" "control_plane" {
|
||||||
count = 3
|
count = local.control_plane_count
|
||||||
server_id = hcloud_server.control_plane[count.index].id
|
server_id = hcloud_server.control_plane[count.index].id
|
||||||
network_id = hcloud_network.cluster.id
|
network_id = hcloud_network.cluster.id
|
||||||
ip = "10.0.1.${10 + count.index}"
|
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" {
|
resource "hcloud_floating_ip" "control_plane" {
|
||||||
|
count = var.enable_loadbalancer ? 0 : 1
|
||||||
type = "ipv4"
|
type = "ipv4"
|
||||||
name = "${local.cluster_name}-cp-ip"
|
name = "${local.cluster_name}-cp-ip"
|
||||||
home_location = "nbg1"
|
home_location = "nbg1"
|
||||||
@@ -233,10 +239,139 @@ resource "hcloud_floating_ip" "control_plane" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "hcloud_floating_ip_assignment" "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
|
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)
|
# WORKER NODE (Single CAX11)
|
||||||
############################################################
|
############################################################
|
||||||
@@ -288,15 +423,15 @@ resource "talos_machine_secrets" "this" {
|
|||||||
data "talos_client_configuration" "this" {
|
data "talos_client_configuration" "this" {
|
||||||
cluster_name = local.cluster_name
|
cluster_name = local.cluster_name
|
||||||
client_configuration = talos_machine_secrets.this.client_configuration
|
client_configuration = talos_machine_secrets.this.client_configuration
|
||||||
endpoints = [hcloud_floating_ip.control_plane.ip_address]
|
endpoints = [local.cluster_endpoint_ip]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Control plane configuration
|
# Control plane configuration
|
||||||
data "talos_machine_configuration" "control_plane" {
|
data "talos_machine_configuration" "control_plane" {
|
||||||
count = 3
|
count = local.control_plane_count
|
||||||
cluster_name = local.cluster_name
|
cluster_name = local.cluster_name
|
||||||
machine_type = "controlplane"
|
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
|
machine_secrets = talos_machine_secrets.this.machine_secrets
|
||||||
talos_version = var.talos_version
|
talos_version = var.talos_version
|
||||||
|
|
||||||
@@ -304,15 +439,16 @@ data "talos_machine_configuration" "control_plane" {
|
|||||||
templatefile("${path.module}/talos-patches/control-plane.yaml", {
|
templatefile("${path.module}/talos-patches/control-plane.yaml", {
|
||||||
cluster_name = local.cluster_name
|
cluster_name = local.cluster_name
|
||||||
node_name = hcloud_server.control_plane[count.index].name
|
node_name = hcloud_server.control_plane[count.index].name
|
||||||
is_ha = true
|
is_ha = var.enable_ha
|
||||||
is_first_cp = count.index == 0
|
is_first_cp = count.index == 0
|
||||||
etcd_peers = [for i in range(3) : "10.0.1.${10 + i}"]
|
etcd_peers = [for i in range(local.control_plane_count) : "10.0.1.${10 + i}"]
|
||||||
floating_ip = hcloud_floating_ip.control_plane.ip_address
|
floating_ip = local.cluster_endpoint_ip
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
depends_on = [
|
depends_on = [
|
||||||
hcloud_server.control_plane,
|
hcloud_server.control_plane,
|
||||||
|
hcloud_load_balancer.cluster,
|
||||||
hcloud_floating_ip_assignment.control_plane
|
hcloud_floating_ip_assignment.control_plane
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -322,7 +458,7 @@ data "talos_machine_configuration" "worker" {
|
|||||||
count = 1
|
count = 1
|
||||||
cluster_name = local.cluster_name
|
cluster_name = local.cluster_name
|
||||||
machine_type = "worker"
|
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
|
machine_secrets = talos_machine_secrets.this.machine_secrets
|
||||||
talos_version = var.talos_version
|
talos_version = var.talos_version
|
||||||
|
|
||||||
@@ -335,6 +471,7 @@ data "talos_machine_configuration" "worker" {
|
|||||||
|
|
||||||
depends_on = [
|
depends_on = [
|
||||||
hcloud_server.worker,
|
hcloud_server.worker,
|
||||||
|
hcloud_load_balancer.cluster,
|
||||||
hcloud_floating_ip_assignment.control_plane
|
hcloud_floating_ip_assignment.control_plane
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -344,7 +481,7 @@ data "talos_machine_configuration" "worker" {
|
|||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
resource "talos_machine_configuration_apply" "control_plane" {
|
resource "talos_machine_configuration_apply" "control_plane" {
|
||||||
count = 3
|
count = local.control_plane_count
|
||||||
client_configuration = talos_machine_secrets.this.client_configuration
|
client_configuration = talos_machine_secrets.this.client_configuration
|
||||||
machine_configuration_input = data.talos_machine_configuration.control_plane[count.index].machine_configuration
|
machine_configuration_input = data.talos_machine_configuration.control_plane[count.index].machine_configuration
|
||||||
endpoint = hcloud_server.control_plane[count.index].ipv4_address
|
endpoint = hcloud_server.control_plane[count.index].ipv4_address
|
||||||
@@ -400,11 +537,11 @@ resource "talos_cluster_kubeconfig" "this" {
|
|||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
resource "local_sensitive_file" "kubeconfig" {
|
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(
|
content = replace(
|
||||||
talos_cluster_kubeconfig.this.kubeconfig_raw,
|
talos_cluster_kubeconfig.this.kubeconfig_raw,
|
||||||
"https://${local.cluster_name}.local:6443",
|
"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"
|
filename = "${path.root}/kubeconfig"
|
||||||
}
|
}
|
||||||
|
|||||||
88
aula-08/nginx-ingress-values.yaml
Normal file
88
aula-08/nginx-ingress-values.yaml
Normal file
@@ -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
|
||||||
@@ -26,8 +26,13 @@ output "network_cidr" {
|
|||||||
|
|
||||||
# Control Plane Information
|
# Control Plane Information
|
||||||
output "control_plane_ip" {
|
output "control_plane_ip" {
|
||||||
description = "Public IP address of the control plane"
|
description = "Public IP address of the control plane (LB or Floating IP)"
|
||||||
value = hcloud_floating_ip.control_plane.ip_address
|
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" {
|
output "control_plane_private_ips" {
|
||||||
@@ -70,22 +75,23 @@ output "talosconfig_path" {
|
|||||||
# API Endpoints
|
# API Endpoints
|
||||||
output "kubernetes_api_endpoint" {
|
output "kubernetes_api_endpoint" {
|
||||||
description = "Kubernetes API server 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" {
|
output "talos_api_endpoint" {
|
||||||
description = "Talos API endpoint for management"
|
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
|
# Cost Information
|
||||||
output "estimated_monthly_cost" {
|
output "estimated_monthly_cost" {
|
||||||
description = "Estimated monthly cost for the infrastructure (EUR)"
|
description = "Estimated monthly cost for the infrastructure (USD)"
|
||||||
value = {
|
value = {
|
||||||
control_plane = 3 * 3.79 # 3x CAX11
|
control_plane = local.control_plane_count * 4.59
|
||||||
worker = 1 * 3.79 # 1x CAX11
|
worker = 1 * 4.59
|
||||||
floating_ip = 3.00 # Floating IPv4
|
load_balancer = var.enable_loadbalancer ? 5.99 : 0
|
||||||
total = (4 * 3.79) + 3.00 # ~€18.16
|
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:
|
2. Configure talosctl:
|
||||||
export TALOSCONFIG=${local_sensitive_file.talosconfig.filename}
|
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:
|
3. Access Kubernetes API:
|
||||||
${"https://${hcloud_floating_ip.control_plane.ip_address}:6443"}
|
https://${local.cluster_endpoint_ip}:6443
|
||||||
|
|
||||||
4. Nodes:
|
4. Nodes:
|
||||||
Control Plane: 3x CAX11 (ARM64)
|
Control Plane: ${local.control_plane_count}x CAX11 (ARM64)
|
||||||
Workers: 1x CAX11 (ARM64)
|
Workers: 1x CAX11 (ARM64)
|
||||||
|
${var.enable_loadbalancer ? "Load Balancer: LB11" : "Floating IP: IPv4"}
|
||||||
5. Total Monthly Cost: ~€18/month
|
|
||||||
|
|
||||||
====================================
|
====================================
|
||||||
EOT
|
EOT
|
||||||
|
|||||||
324
aula-08/setup.sh
324
aula-08/setup.sh
@@ -32,7 +32,7 @@ log_warn() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log_error() {
|
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
|
fi
|
||||||
log_success "kubectl $(kubectl version --client -o yaml 2>/dev/null | grep gitVersion | awk '{print $2}' || echo 'instalado')"
|
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)
|
# Verificar hcloud CLI (opcional, mas útil)
|
||||||
if command -v hcloud &> /dev/null; then
|
if command -v hcloud &> /dev/null; then
|
||||||
log_success "hcloud CLI instalado"
|
log_success "hcloud CLI instalado"
|
||||||
@@ -170,6 +182,52 @@ if [ "$SKIP_CREDENTIALS" != "true" ]; then
|
|||||||
log_success "Image ID: $TALOS_IMAGE_ID"
|
log_success "Image ID: $TALOS_IMAGE_ID"
|
||||||
echo ""
|
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
|
# Criar terraform.tfvars
|
||||||
log_info "Criando terraform.tfvars..."
|
log_info "Criando terraform.tfvars..."
|
||||||
cat > terraform.tfvars << EOF
|
cat > terraform.tfvars << EOF
|
||||||
@@ -180,8 +238,9 @@ hcloud_token = "$HCLOUD_TOKEN"
|
|||||||
ssh_public_key = "$SSH_PUBLIC_KEY"
|
ssh_public_key = "$SSH_PUBLIC_KEY"
|
||||||
talos_image_id = $TALOS_IMAGE_ID
|
talos_image_id = $TALOS_IMAGE_ID
|
||||||
|
|
||||||
environment = "workshop"
|
environment = "prod"
|
||||||
enable_monitoring = true
|
enable_ha = $ENABLE_HA
|
||||||
|
enable_loadbalancer = $ENABLE_LB
|
||||||
EOF
|
EOF
|
||||||
log_success "terraform.tfvars criado"
|
log_success "terraform.tfvars criado"
|
||||||
fi
|
fi
|
||||||
@@ -219,16 +278,39 @@ echo ""
|
|||||||
log_success "Plano criado!"
|
log_success "Plano criado!"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Mostrar resumo
|
# Mostrar resumo baseado na configuração
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo " Recursos a serem criados:"
|
echo " Recursos a serem criados:"
|
||||||
echo "============================================"
|
echo "============================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo " - 4x CAX11 (3 CP + 1 Worker) = 4 x €3.79 = €15.16"
|
|
||||||
echo " - 1x Floating IPv4 = €3.00"
|
# Ler configuração do tfvars
|
||||||
echo " - Rede/Firewall/Placement = Grátis"
|
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 ""
|
||||||
echo " Custo estimado: ~€18.16/mês (sem VAT)"
|
echo " Custo estimado: ~\$${TOTAL_COST}/mes"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
@@ -312,6 +394,203 @@ fi
|
|||||||
|
|
||||||
echo ""
|
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
|
# 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
|
tofu output -raw talos_api_endpoint 2>/dev/null && echo "" || true
|
||||||
echo ""
|
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 "Arquivos gerados:"
|
||||||
echo " - kubeconfig : Configuração do kubectl"
|
echo " - kubeconfig : Configuração do kubectl"
|
||||||
echo " - talosconfig : Configuração do talosctl"
|
echo " - talosconfig : Configuração do talosctl"
|
||||||
@@ -338,24 +630,12 @@ echo " # Usar kubectl com este cluster"
|
|||||||
echo " export KUBECONFIG=$SCRIPT_DIR/kubeconfig"
|
echo " export KUBECONFIG=$SCRIPT_DIR/kubeconfig"
|
||||||
echo " kubectl get nodes"
|
echo " kubectl get nodes"
|
||||||
echo ""
|
echo ""
|
||||||
echo " # Usar talosctl com este cluster"
|
echo " # Ver logs do autoscaler"
|
||||||
echo " export TALOSCONFIG=$SCRIPT_DIR/talosconfig"
|
echo " kubectl logs -n cluster-autoscaler -l app=cluster-autoscaler -f"
|
||||||
echo " talosctl -n <IP> health"
|
|
||||||
echo ""
|
|
||||||
echo " # Ver outputs do OpenTofu"
|
|
||||||
echo " tofu output"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " # Destruir infraestrutura (CUIDADO!)"
|
echo " # Destruir infraestrutura (CUIDADO!)"
|
||||||
echo " ./cleanup.sh"
|
echo " ./cleanup.sh"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
log_success "Setup concluído!"
|
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 ""
|
echo ""
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ machine:
|
|||||||
# Kubelet configuration
|
# Kubelet configuration
|
||||||
kubelet:
|
kubelet:
|
||||||
extraArgs:
|
extraArgs:
|
||||||
|
cloud-provider: external
|
||||||
max-pods: "110"
|
max-pods: "110"
|
||||||
kube-reserved: "cpu=200m,memory=300Mi"
|
kube-reserved: "cpu=200m,memory=300Mi"
|
||||||
system-reserved: "cpu=200m,memory=200Mi"
|
system-reserved: "cpu=200m,memory=200Mi"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ machine:
|
|||||||
# Kubelet configuration
|
# Kubelet configuration
|
||||||
kubelet:
|
kubelet:
|
||||||
extraArgs:
|
extraArgs:
|
||||||
|
cloud-provider: external
|
||||||
max-pods: "110"
|
max-pods: "110"
|
||||||
kube-reserved: "cpu=100m,memory=200Mi"
|
kube-reserved: "cpu=100m,memory=200Mi"
|
||||||
system-reserved: "cpu=100m,memory=100Mi"
|
system-reserved: "cpu=100m,memory=100Mi"
|
||||||
|
|||||||
@@ -2,28 +2,56 @@
|
|||||||
# Variables for Hetzner Talos Kubernetes Cluster
|
# Variables for Hetzner Talos Kubernetes Cluster
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
# Authentication
|
# ==========================================================
|
||||||
|
# AUTENTICAÇÃO
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
variable "hcloud_token" {
|
variable "hcloud_token" {
|
||||||
type = string
|
type = string
|
||||||
description = "Hetzner Cloud API token"
|
description = "Hetzner Cloud API token"
|
||||||
sensitive = true
|
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" {
|
variable "environment" {
|
||||||
type = string
|
type = string
|
||||||
description = "Environment name (prod, staging, dev)"
|
description = "Environment name (prod, staging, dev)"
|
||||||
default = "prod"
|
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" {
|
variable "ssh_public_key" {
|
||||||
type = string
|
type = string
|
||||||
description = "Public SSH key for emergency access to nodes"
|
description = "Public SSH key for emergency access to nodes"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Talos Configuration
|
# ==========================================================
|
||||||
|
# TALOS
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
variable "talos_image_id" {
|
variable "talos_image_id" {
|
||||||
type = number
|
type = number
|
||||||
description = "ID da imagem Talos customizada na Hetzner (criada na aula-07). Obtenha com: hcloud image list --type snapshot"
|
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" {
|
variable "talos_version" {
|
||||||
type = string
|
type = string
|
||||||
description = "Talos version to use"
|
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" {
|
# LABELS CUSTOMIZADAS
|
||||||
type = bool
|
# ==========================================================
|
||||||
description = "Enable Victoria Metrics monitoring stack"
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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" {
|
variable "custom_labels" {
|
||||||
type = map(string)
|
type = map(string)
|
||||||
description = "Custom labels to add to all resources"
|
description = "Custom labels to add to all resources"
|
||||||
|
|||||||
233
aula-09/README.md
Normal file
233
aula-09/README.md
Normal file
@@ -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://<floating-ip>: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**.
|
||||||
299
aula-09/add-client.sh
Executable file
299
aula-09/add-client.sh
Executable file
@@ -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 <nome-cliente>
|
||||||
|
#
|
||||||
|
# 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 <nome-cliente>"
|
||||||
|
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 "<pendente>")
|
||||||
|
|
||||||
|
# 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}"
|
||||||
140
aula-09/base-n8n-values.yaml
Normal file
140
aula-09/base-n8n-values.yaml
Normal file
@@ -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
|
||||||
185
aula-09/cleanup.sh
Executable file
185
aula-09/cleanup.sh
Executable file
@@ -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 ""
|
||||||
148
aula-09/custom-values.yaml
Normal file
148
aula-09/custom-values.yaml
Normal file
@@ -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
|
||||||
526
aula-09/setup.sh
Executable file
526
aula-09/setup.sh
Executable file
@@ -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" <<EOF
|
||||||
|
# Configuração gerada pelo setup.sh
|
||||||
|
# $(date)
|
||||||
|
N8N_HOST=${N8N_HOST}
|
||||||
|
DOMAIN=${DOMAIN}
|
||||||
|
USE_CLOUDFLARE=${USE_CLOUDFLARE}
|
||||||
|
USE_LETSENCRYPT=${USE_LETSENCRYPT}
|
||||||
|
LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
|
||||||
|
EOF
|
||||||
|
log_success "Configuração salva em .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
if [[ -f "$SCRIPT_DIR/.env" ]]; then
|
||||||
|
source "$SCRIPT_DIR/.env"
|
||||||
|
# Compatibilidade: se não tem N8N_HOST, deriva do DOMAIN
|
||||||
|
if [[ -z "$N8N_HOST" && -n "$DOMAIN" ]]; then
|
||||||
|
N8N_HOST="n8n.${DOMAIN}"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extrai domínio base de um FQDN (ex: n8n.kube.quest → kube.quest)
|
||||||
|
extract_domain() {
|
||||||
|
local fqdn="$1"
|
||||||
|
echo "$fqdn" | sed 's/^[^.]*\.//'
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_user_input() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${CYAN} n8n no Kubernetes (Hetzner Cloud)${NC}"
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Verificar se já existe configuração
|
||||||
|
if load_config; then
|
||||||
|
echo -e "Configuração existente encontrada:"
|
||||||
|
echo -e " Hostname: ${GREEN}${N8N_HOST}${NC}"
|
||||||
|
echo -e " CloudFlare: ${GREEN}${USE_CLOUDFLARE}${NC}"
|
||||||
|
echo -e " Let's Encrypt: ${GREEN}${USE_LETSENCRYPT}${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Deseja usar esta configuração?"
|
||||||
|
echo -e " 1) Sim, continuar com a configuração existente"
|
||||||
|
echo -e " 2) Não, reconfigurar"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Coletar hostname completo
|
||||||
|
echo ""
|
||||||
|
echo -n "Digite o hostname do n8n (ex: n8n.kube.quest): "
|
||||||
|
read -r N8N_HOST
|
||||||
|
|
||||||
|
if [[ -z "$N8N_HOST" ]]; then
|
||||||
|
log_error "Hostname não pode ser vazio"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extrair domínio base automaticamente
|
||||||
|
DOMAIN=$(extract_domain "$N8N_HOST")
|
||||||
|
|
||||||
|
# Perguntar sobre CloudFlare
|
||||||
|
echo ""
|
||||||
|
echo "Você usa CloudFlare para DNS?"
|
||||||
|
echo " 1) Sim (com proxy/CDN ativado - ícone laranja)"
|
||||||
|
echo " 2) Não"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
USE_CLOUDFLARE=true
|
||||||
|
USE_LETSENCRYPT=false
|
||||||
|
log_info "CloudFlare irá gerenciar TLS automaticamente na edge"
|
||||||
|
else
|
||||||
|
USE_CLOUDFLARE=false
|
||||||
|
|
||||||
|
# Perguntar sobre Let's Encrypt
|
||||||
|
echo ""
|
||||||
|
echo "Deseja ativar HTTPS com Let's Encrypt?"
|
||||||
|
echo " 1) Sim (recomendado para produção)"
|
||||||
|
echo " 2) Não (apenas HTTP)"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
USE_LETSENCRYPT=true
|
||||||
|
echo ""
|
||||||
|
echo -n "Digite seu email para Let's Encrypt: "
|
||||||
|
read -r LETSENCRYPT_EMAIL
|
||||||
|
|
||||||
|
if [[ -z "$LETSENCRYPT_EMAIL" ]]; then
|
||||||
|
log_error "Email é obrigatório para Let's Encrypt"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
USE_LETSENCRYPT=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Salvar configuração
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# FUNÇÕES DE INSTALAÇÃO
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
install_cert_manager() {
|
||||||
|
log_info "Instalando cert-manager..."
|
||||||
|
|
||||||
|
helm repo add jetstack https://charts.jetstack.io 2>/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 <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: ${LETSENCRYPT_EMAIL}
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: nginx
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_success "ClusterIssuer 'letsencrypt' criado"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_dns_instructions() {
|
||||||
|
# Obter IP do LoadBalancer
|
||||||
|
LB_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
|
||||||
|
-o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "<pendente>")
|
||||||
|
|
||||||
|
# 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 <nome-cliente>"
|
||||||
|
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 ""
|
||||||
339
aula-10/README.md
Normal file
339
aula-10/README.md
Normal file
@@ -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=<token>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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/)
|
||||||
184
aula-10/cleanup.sh
Executable file
184
aula-10/cleanup.sh
Executable file
@@ -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 ""
|
||||||
298
aula-10/gitlab-values.yaml
Normal file
298
aula-10/gitlab-values.yaml
Normal file
@@ -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
|
||||||
569
aula-10/setup.sh
Executable file
569
aula-10/setup.sh
Executable file
@@ -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" <<EOF
|
||||||
|
# Configuração gerada pelo setup.sh
|
||||||
|
# $(date)
|
||||||
|
GITLAB_HOST=${GITLAB_HOST}
|
||||||
|
REGISTRY_HOST=${REGISTRY_HOST}
|
||||||
|
DOMAIN=${DOMAIN}
|
||||||
|
USE_CLOUDFLARE=${USE_CLOUDFLARE}
|
||||||
|
USE_LETSENCRYPT=${USE_LETSENCRYPT}
|
||||||
|
LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}
|
||||||
|
EOF
|
||||||
|
log_success "Configuração salva em .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config() {
|
||||||
|
# Tentar carregar .env local primeiro
|
||||||
|
if [[ -f "$SCRIPT_DIR/.env" ]]; then
|
||||||
|
source "$SCRIPT_DIR/.env"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tentar herdar da aula-09 (apenas CloudFlare/Let's Encrypt)
|
||||||
|
if [[ -f "$SCRIPT_DIR/../aula-09/.env" ]]; then
|
||||||
|
source "$SCRIPT_DIR/../aula-09/.env"
|
||||||
|
log_info "Configuração de TLS herdada da aula-09"
|
||||||
|
# Limpar hosts (serão perguntados novamente)
|
||||||
|
GITLAB_HOST=""
|
||||||
|
REGISTRY_HOST=""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extrai domínio base de um FQDN (ex: git.kube.quest → kube.quest)
|
||||||
|
extract_domain() {
|
||||||
|
local fqdn="$1"
|
||||||
|
echo "$fqdn" | sed 's/^[^.]*\.//'
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_user_input() {
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||||
|
echo -e "${CYAN} GitLab no Kubernetes (Hetzner Cloud)${NC}"
|
||||||
|
echo -e "${CYAN}═══════════════════════════════════════════════════${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Carregar configuração existente
|
||||||
|
load_config
|
||||||
|
|
||||||
|
# Se já tem GITLAB_HOST configurado, oferecer usar
|
||||||
|
if [[ -n "$GITLAB_HOST" ]]; then
|
||||||
|
echo -e "Configuração existente encontrada:"
|
||||||
|
echo -e " GitLab: ${GREEN}${GITLAB_HOST}${NC}"
|
||||||
|
echo -e " Registry: ${GREEN}${REGISTRY_HOST}${NC}"
|
||||||
|
echo -e " CloudFlare: ${GREEN}${USE_CLOUDFLARE}${NC}"
|
||||||
|
echo -e " Let's Encrypt: ${GREEN}${USE_LETSENCRYPT}${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Deseja usar esta configuração?"
|
||||||
|
echo -e " 1) Sim, continuar com a configuração existente"
|
||||||
|
echo -e " 2) Não, reconfigurar"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Coletar hostname do GitLab
|
||||||
|
echo ""
|
||||||
|
echo -n "Digite o hostname do GitLab (ex: git.kube.quest): "
|
||||||
|
read -r GITLAB_HOST
|
||||||
|
|
||||||
|
if [[ -z "$GITLAB_HOST" ]]; then
|
||||||
|
log_error "Hostname não pode ser vazio"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Derivar Domain e Registry automaticamente
|
||||||
|
DOMAIN=$(extract_domain "$GITLAB_HOST")
|
||||||
|
REGISTRY_HOST="registry.${DOMAIN}"
|
||||||
|
|
||||||
|
log_info "Registry será: ${REGISTRY_HOST}"
|
||||||
|
|
||||||
|
# Se já tem configuração de TLS, oferecer usar
|
||||||
|
if [[ -n "$USE_CLOUDFLARE" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "Configuração de TLS encontrada:"
|
||||||
|
echo -e " CloudFlare: ${GREEN}${USE_CLOUDFLARE}${NC}"
|
||||||
|
echo -e " Let's Encrypt: ${GREEN}${USE_LETSENCRYPT}${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e "Deseja usar esta configuração de TLS?"
|
||||||
|
echo -e " 1) Sim"
|
||||||
|
echo -e " 2) Não, reconfigurar"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
save_config
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Perguntar sobre CloudFlare
|
||||||
|
echo ""
|
||||||
|
echo "Você usa CloudFlare para DNS?"
|
||||||
|
echo " 1) Sim (com proxy/CDN ativado - ícone laranja)"
|
||||||
|
echo " 2) Não"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
USE_CLOUDFLARE=true
|
||||||
|
USE_LETSENCRYPT=false
|
||||||
|
log_info "CloudFlare irá gerenciar TLS automaticamente na edge"
|
||||||
|
else
|
||||||
|
USE_CLOUDFLARE=false
|
||||||
|
|
||||||
|
# Perguntar sobre Let's Encrypt
|
||||||
|
echo ""
|
||||||
|
echo "Deseja ativar HTTPS com Let's Encrypt?"
|
||||||
|
echo " 1) Sim (recomendado para produção)"
|
||||||
|
echo " 2) Não (apenas HTTP)"
|
||||||
|
echo -n "Escolha [1/2]: "
|
||||||
|
read -r choice
|
||||||
|
|
||||||
|
if [[ "$choice" == "1" ]]; then
|
||||||
|
USE_LETSENCRYPT=true
|
||||||
|
echo ""
|
||||||
|
echo -n "Digite seu email para Let's Encrypt: "
|
||||||
|
read -r LETSENCRYPT_EMAIL
|
||||||
|
|
||||||
|
if [[ -z "$LETSENCRYPT_EMAIL" ]]; then
|
||||||
|
log_error "Email é obrigatório para Let's Encrypt"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
USE_LETSENCRYPT=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Salvar configuração
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
install_cert_manager() {
|
||||||
|
log_info "Instalando cert-manager..."
|
||||||
|
|
||||||
|
helm repo add jetstack https://charts.jetstack.io 2>/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 <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: ClusterIssuer
|
||||||
|
metadata:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
acme:
|
||||||
|
server: https://acme-v02.api.letsencrypt.org/directory
|
||||||
|
email: ${LETSENCRYPT_EMAIL}
|
||||||
|
privateKeySecretRef:
|
||||||
|
name: letsencrypt-prod-key
|
||||||
|
solvers:
|
||||||
|
- http01:
|
||||||
|
ingress:
|
||||||
|
class: nginx
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_success "ClusterIssuer 'letsencrypt-prod' criado"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_dns_instructions() {
|
||||||
|
# Obter IP do LoadBalancer
|
||||||
|
LB_IP=$(kubectl get svc -n ingress-nginx ingress-nginx-controller \
|
||||||
|
-o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "<pendente>")
|
||||||
|
|
||||||
|
# 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
|
||||||
Reference in New Issue
Block a user