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
300 lines
10 KiB
Bash
Executable File
300 lines
10 KiB
Bash
Executable File
#!/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}"
|