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:
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 ""
|
||||
Reference in New Issue
Block a user