diff --git a/CLAUDE.md b/CLAUDE.md index ed6ab29..eca63d6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,7 @@ App de demonstração: `node-bugado` - trava após N requests para demonstrar he | 14 | Istio Traffic Splitting | Hetzner | | 15 | APM: Grafana Tempo + OpenTelemetry | Hetzner | | 16 | Canary Automatizado com Flagger | Hetzner | +| 17 | Cloud Native PostgreSQL (CNPG) | Hetzner | ## Comandos Rápidos @@ -34,7 +35,7 @@ App de demonstração: `node-bugado` - trava após N requests para demonstrar he # Aulas 01-06 (Local) cd aula-XX && ./setup.sh # ou kubectl apply -f . -# Aulas 07-15 (Hetzner) +# Aulas 07-17 (Hetzner) cd aula-08 && ./setup.sh # Cluster base cd aula-09 && ./setup.sh # n8n cd aula-10 && ./setup.sh # Gitea + Registry + Runner @@ -44,6 +45,7 @@ cd aula-13 && ./setup.sh # Container Factory cd aula-14 && ./setup.sh # Istio Traffic Splitting cd aula-15 && ./setup.sh # APM: Tempo + OpenTelemetry cd aula-16 && ./setup.sh # Canary Automatizado com Flagger +cd aula-17 && ./setup.sh # CNPG - PostgreSQL HA ``` ## App node-bugado diff --git a/aula-17/.gitignore b/aula-17/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/aula-17/.gitignore @@ -0,0 +1 @@ +.env diff --git a/aula-17/README.md b/aula-17/README.md new file mode 100644 index 0000000..7fd022f --- /dev/null +++ b/aula-17/README.md @@ -0,0 +1,379 @@ +# Aula 17 - Cloud Native PostgreSQL (CNPG) + +## Motivação + +Você está pagando **$200-400/mês em AWS RDS** pra rodar PostgreSQL. E ainda não tem HA cross-region — isso custa mais $100-200/mês. + +O **CloudNativePG (CNPG)** é um operador Kubernetes que roda PostgreSQL production-grade dentro do seu cluster. Na Hetzner, com 3 instâncias (2 sync + 1 async cross-region), você obtém o equivalente ao **RDS Multi-AZ + Cross-Region Read Replica** por **~$56/mês**. + +Nesta aula, vamos instalar o CNPG do zero, criar um cluster HA com backup automático para Hetzner Object Storage, e conectar uma app demo. + +## Comparativo de Custo + +### AWS RDS Multi-AZ vs CNPG na Hetzner + +| Item | AWS RDS Multi-AZ | CNPG na Hetzner | +|------|------------------|-----------------| +| Compute | db.r6g.large ~$185/mês | 3× CPX31 ~€36/mês (~$40) | +| Storage 200GB | ~$23/mês (gp3) | ~€9/mês (hcloud volumes) | +| Backup | 7 dias (free) | ~€6/mês (Object Storage 1TB) | +| **Total mensal** | **~$208/mês** | **~€51/mês (~$56)** | +| HA cross-region | +$100-200/mês (produto separado) | **Incluso** (async replica em HEL) | +| PITR (Point-in-Time Recovery) | 7 dias | **Ilimitado** (retido no S3) | +| Controle total | Não (gerenciado pela AWS) | **Sim** (seu cluster, suas regras) | + +**Economia: ~75% (só HA local) ou ~85% (HA + cross-region)** + +### Por que Hetzner? + +- CPX31: 4 vCPU AMD, 8GB RAM, 160GB NVMe → **~€12/mês** +- Object Storage: 1TB S3-compatible, 1TB egress free → **~€6/mês** +- Sem surpresas: preço fixo, sem egress cost como AWS ($0.09/GB) + +## Comparativo Técnico: CNPG vs StackGres + +Dois operadores PostgreSQL maduros para Kubernetes. Qual escolher? + +| Aspecto | CNPG | StackGres | +|---------|------|-----------| +| **Governance** | CNCF Sandbox (2025) | OnGres (independente) | +| **Sidecars por pod** | 0-1 (PgBouncer opcional) | 4+ (Envoy, PgBouncer, Fluentd, Prometheus) | +| **RAM overhead** | ~50-100MB | ~300-500MB (sidecars) | +| **YAML mínimo** | ~80 linhas | ~150+ linhas | +| **Backup** | Barman Cloud (built-in) | WAL-G (built-in) | +| **Connection pooling** | PgBouncer sidecar (opcional) | PgBouncer sidecar (always-on) | +| **GitOps friendly** | CRDs simples, ArgoCD nativo | CRDs complexos, possível | +| **Público ideal** | Indie/startup (lean) | Enterprise "batteries included" | +| **Comunidade** | Muito ativa, CNCF backing | Menor, mas dedicada | + +### Por que CNPG para indie hackers? + +1. **Menos recursos = custo menor**. StackGres consome 3-5× mais RAM em sidecars. Num CPX31 com 8GB, isso faz diferença. +2. **Setup mais simples**. Um Cluster CRD resolve. StackGres precisa de SGCluster, SGPostgresConfig, SGPgBouncerConfig, SGBackupConfig, SGPoolingConfig... +3. **CNCF Sandbox**. Governança aberta, roadmap transparente, não depende de uma empresa. +4. **GitOps nativo**. CRDs foram desenhados pra serem declarativos. Funciona com ArgoCD/Flux sem hacks. + +StackGres é excelente se você quer PgBouncer, Envoy, Fluentd, e Prometheus já configurados. Mas pra indie hacker que precisa de custo baixo e simplicidade, CNPG é a escolha certa. + +## Arquitetura + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Cluster Kubernetes (Talos) │ +│ │ +│ eu-central (FSN + NBG, ~1-2ms latência) │ +│ ┌──────────────────────────────────────────────────────────────┐ │ +│ │ CNPG Operator (cnpg-system) │ │ +│ │ │ │ +│ │ ┌──────────────┐ sync ┌──────────────┐ │ │ +│ │ │ Primary │◄────────►│ Replica 1 │ │ │ +│ │ │ (RW) │ ~1ms │ (RO, sync) │ │ │ +│ │ └──────┬───────┘ └──────────────┘ │ │ +│ │ │ │ │ +│ │ Services: │ │ +│ │ shared-postgres-rw → Primary (read-write) │ │ +│ │ shared-postgres-ro → Replicas (read-only) │ │ +│ └─────────┼────────────────────────────────────────────────────┘ │ +│ │ async (~20-30ms) │ +│ ▼ │ +│ hel-southeast (HEL, Finlândia) │ +│ ┌──────────────────────────────────────────────────────────────┐ │ +│ │ ┌──────────────┐ │ │ +│ │ │ Replica 2 │ ← DR cross-region (async) │ │ +│ │ │ (RO, async) │ │ │ +│ │ └──────────────┘ │ │ +│ └──────────────────────────────────────────────────────────────┘ │ +│ │ +│ Backup: WAL archiving contínuo + base backup diário │ +│ → Hetzner Object Storage (S3, zero egress cost) │ +│ │ +│ Monitoring: PodMonitor → Victoria Metrics (aula-12) │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### HA em duas camadas + +| Camada | Caminho | Latência | Propósito | +|--------|---------|----------|-----------| +| **Sync** | FSN ↔ NBG | ~1-2ms | Zero RPO, failover automático | +| **Async** | FSN → HEL | ~20-30ms | DR cross-region, RPO ~30s | + +### Separando etcd do PostgreSQL + +O etcd (control plane do Talos/Kubernetes) fica em eu-central (FSN + NBG). O node em HEL é **worker-only** — não participa do etcd. Isso respeita o requisito de latência do etcd (< 10ms). + +### Node Pools e Placement + +O CNPG não decide sozinho onde cada replica roda. Ele respeita as regras de scheduling do Kubernetes: + +| Camada | Regra | Tipo | Efeito | +|--------|-------|------|--------| +| **Host** | `podAntiAffinity` on `kubernetes.io/hostname` | Hard (required) | Nunca 2 pods PG no mesmo node | +| **Zone** | `podAntiAffinity` on `topology.kubernetes.io/zone` | Soft (preferred) | Espalha entre datacenters quando possível | + +Para a topologia cross-region (2 em eu-central + 1 em HEL), você precisa: + +1. **Node pool eu-central** (aula-08): Control plane + workers em FSN/NBG +2. **Node pool HEL**: Worker dedicado em Helsinki (adicionar depois da aula-08) + +O Hetzner CCM labela nodes automaticamente: +``` +topology.kubernetes.io/zone=fsn1 # Frankfurt +topology.kubernetes.io/zone=nbg1 # Nuremberg +topology.kubernetes.io/zone=hel1 # Helsinki +topology.kubernetes.io/region=eu-central +topology.kubernetes.io/region=hel-southeast +``` + +**Sem nodes em HEL**: Os 3 pods ficam em eu-central (FSN + NBG). Ainda é HA dentro da região. O soft constraint de zona é ignorado graciosamente. + +**Com nodes em HEL**: O scheduler prefere espalhar — 1 pod por datacenter. Você ganha DR cross-region automaticamente. + +## Conceitos + +| Conceito | Descrição | +|----------|-----------| +| **Cluster** | CRD principal do CNPG. Define instâncias, storage, backup, e config PostgreSQL | +| **Primary** | Instância read-write. Recebe todas as escritas | +| **Replica (sync)** | Réplica síncrona. Confirma cada transação antes do commit. Zero RPO | +| **Replica (async)** | Réplica assíncrona. DR cross-region. Pequeno RPO (~30s) | +| **Barman Cloud** | Tool de backup integrado ao CNPG. WAL archiving + base backups → S3 | +| **PITR** | Point-in-Time Recovery. Restaura o banco para qualquer momento com backup | +| **Switchover** | Promoção planejada de uma replica a primary (zero downtime) | +| **Failover** | Promoção automática quando o primary falha | +| **PodMonitor** | CRD do Victoria Metrics que scrafa métricas dos pods CNPG | + +## Pré-requisitos + +- Cluster Kubernetes na Hetzner (aula-08) + - Mínimo: 3 nodes em eu-central (FSN + NBG) para HA dentro da região + - Para DR cross-region: adicionar 1 worker em HEL (hel-southeast) +- Victoria Metrics (aula-12) — opcional, mas recomendado para métricas +- Hetzner Object Storage com bucket criado +- kubectl e helm instalados + +## Estrutura + +``` +aula-17/ +├── README.md # Esta documentação +├── setup.sh # Instalação automatizada +├── cleanup.sh # Remoção limpa +├── cnpg/ +│ ├── cluster.yaml # Cluster CNPG (3 instâncias, backup S3) +│ ├── backup-secret.yaml # Template de credenciais S3 +│ └── scheduled-backup.yaml # Backup diário às 03:00 +└── app-demo/ + ├── deployment.yaml # App demo conectando no CNPG + └── service.yaml # Service da app demo +``` + +## Instalação + +```bash +cd aula-17 +./setup.sh +``` + +O script vai: +1. Verificar pré-requisitos +2. Pedir as credenciais do Hetzner Object Storage +3. Instalar o CNPG operator +4. Criar o cluster PostgreSQL (3 instâncias) +5. Configurar backup automático +6. Deployar app demo para validar + +## Verificação + +Após a instalação, verifique se tudo está funcionando: + +```bash +# Status do cluster +kubectl get cluster -n cnpg + +# Deve mostrar: +# NAME AGE INSTANCES READY STATUS PRIMARY +# shared-postgres 5m 3 3 Cluster in healthy shared-postgres-1 + +# Instâncias (primary vs replicas) +kubectl get pods -n cnpg -l cnpg.io/cluster=shared-postgres -o wide + +# Deve mostrar 3 pods em nodes diferentes: +# NAME ROLE STATUS NODE +# shared-postgres-1 primary Running node-fsn-1 +# shared-postgres-2 replica Running node-nbg-2 +# shared-postgres-3 replica Running node-hel-3 + +# Serviços criados pelo CNPG +kubectl get svc -n cnpg -l cnpg.io/cluster=shared-postgres + +# Deve mostrar: +# shared-postgres-rw (points to primary) +# shared-postgres-ro (points to replicas) +# shared-postgres-r (any instance) + +# Logs do app demo (valida conexão) +kubectl logs deployment/pg-demo -n cnpg +``` + +## Comandos Essenciais + +### Status e observação + +```bash +# Status geral do cluster +kubectl get cluster shared-postgres -n cnpg + +# Detalhes completos (instâncias, replication, disk, backups) +kubectl describe cluster shared-postgres -n cnpg + +# Ver instâncias com role e node +kubectl get pods -n cnpg -l cnpg.io/cluster=shared-postgres -o wide + +# Logs de uma instância específica +kubectl logs shared-postgres-1 -n cnpg -c postgres +``` + +### Failover / Mudar o leader + +O CNPG faz **failover automático** quando o primary falha. Mas você pode forçar manualmente: + +```bash +# Ver qual instância é o primary +kubectl get pods -n cnpg -l cnpg.io/cluster=shared-postgres \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.cnpg\.io/instanceRole}{"\n"}{end}' + +# Promover uma replica específica a primary (switchover manual) +kubectl cnpg promote shared-postgres shared-postgres-2 -n cnpg + +# Acompanhar o switchover +kubectl get pods -n cnpg -l cnpg.io/cluster=shared-postgres -o wide -w +``` + +### Backup + +```bash +# Ver backups existentes +kubectl get backups -n cnpg + +# Trigger backup manual (além do agendado) +kubectl cnpg backup shared-postgres -n cnpg + +# Ver detalhes de um backup +kubectl describe backup -n cnpg + +# O backup agendado roda às 03:00 diariamente +# Configurado em: cnpg/scheduled-backup.yaml +``` + +### Recovery / PITR + +Restaurar o cluster a partir de um backup S3 para um ponto específico no tempo: + +```bash +# 1. Remover cluster existente (CUIDADO: perde dados atuais) +kubectl delete cluster shared-postgres -n cnpg + +# 2. Restaurar a partir de backup com PITR +cat </dev/null || true +kubectl delete service pg-demo -n cnpg --ignore-not-found=true 2>/dev/null || true +log_success "App demo removido" + +# 2. Remover ScheduledBackup +log_info "Removendo backup agendado..." +kubectl delete scheduledbackup shared-postgres-backup -n cnpg --ignore-not-found=true 2>/dev/null || true +log_success "Backup agendado removido" + +# 3. Remover Cluster PostgreSQL (isso também remove PVCs) +log_info "Removendo cluster PostgreSQL..." +kubectl delete cluster shared-postgres -n cnpg --ignore-not-found=true 2>/dev/null || true +log_info "Aguardando pods serem removidos..." +kubectl wait --for=delete cluster/shared-postgres -n cnpg --timeout=300s 2>/dev/null || true +log_success "Cluster removido" + +# 4. Remover secrets +log_info "Removendo secrets..." +kubectl delete secret cnpg-backup-credentials -n cnpg --ignore-not-found=true 2>/dev/null || true +kubectl delete secret shared-postgres-superuser -n cnpg --ignore-not-found=true 2>/dev/null || true +log_success "Secrets removidos" + +# 5. Remover namespace +log_info "Removendo namespace cnpg..." +kubectl delete namespace cnpg --ignore-not-found=true 2>/dev/null || true +log_success "Namespace cnpg removido" + +# 6. Remover CNPG Operator +log_info "Removendo CNPG operator..." +helm uninstall cnpg -n cnpg-system --wait 2>/dev/null || true +kubectl delete namespace cnpg-system --ignore-not-found=true 2>/dev/null || true +log_success "CNPG operator removido" + +# 7. Remover CRDs +log_info "Removendo CNPG CRDs..." +kubectl delete crd clusters.postgresql.cnpg.io backups.postgresql.cnpg.io scheduledbackups.postgresql.cnpg.io poolers.postgresql.cnpg.io imagecatalogs.postgresql.cnpg.io 2>/dev/null || true +log_success "CRDs removidos" + +# 8. Remover .env +read -p "Remover arquivo .env? [s/N]: " remove_env +if [[ "$remove_env" == "s" || "$remove_env" == "S" ]]; then + rm -f "${SCRIPT_DIR}/.env" + log_success ".env removido" +fi + +echo "" +echo -e "${GREEN}Cleanup concluído!${NC}" +echo "" +echo -e "${YELLOW}Lembrete:${NC} Backups no Hetzner Object Storage NÃO foram removidos." +echo " Para remover: acesse o console Hetzner > Object Storage" +echo "" diff --git a/aula-17/cnpg/backup-secret.yaml b/aula-17/cnpg/backup-secret.yaml new file mode 100644 index 0000000..00b2e99 --- /dev/null +++ b/aula-17/cnpg/backup-secret.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cnpg-backup-credentials + namespace: cnpg +type: Opaque +stringData: + ACCESS_KEY_ID: CHANGE_ACCESS_KEY + ACCESS_SECRET_KEY: CHANGE_SECRET_KEY \ No newline at end of file diff --git a/aula-17/cnpg/cluster.yaml b/aula-17/cnpg/cluster.yaml new file mode 100644 index 0000000..aa7d994 --- /dev/null +++ b/aula-17/cnpg/cluster.yaml @@ -0,0 +1,89 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: shared-postgres + namespace: cnpg +spec: + imageName: ghcr.io/cloudnative-pg/postgresql:17.2-4 + instances: 3 + enableSuperuserAccess: true + # ── Topology-aware placement ── + # Hetzner CCM labels nodes automatically: + # topology.kubernetes.io/zone = fsn1 | nbg1 | hel1 (datacenter) + # topology.kubernetes.io/region = eu-central | hel-southeast + # + # Layer 1: podAntiAffinity on hostname (hard constraint) + # → No 2 PG pods on the same node. Ever. + # + # Layer 2: podAntiAffinity on zone (soft constraint) + # → Prefer spreading across datacenters. With nodes in fsn1, nbg1, hel1, + # you get 1 pod per DC. With only eu-central nodes, they still spread + # across FSN and NBG (both in eu-central, ~1-2ms apart). + # + # Prerequisite for cross-region (eu-central + HEL): + # - Node pool in eu-central (FSN + NBG) → aula-08 + # - Node pool in hel-southeast (HEL) → add worker after aula-08 + # - Without HEL nodes, all 3 pods stay in eu-central (still HA within region) + affinity: + topologyKey: kubernetes.io/hostname + podAntiAffinityType: required + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: topology.kubernetes.io/zone + labelSelector: + matchLabels: + cnpg.io/cluster: shared-postgres + storage: + size: 10Gi + storageClass: hcloud-volumes + walStorage: + size: 5Gi + storageClass: hcloud-volumes + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "1" + memory: 1Gi + postgresql: + parameters: + max_connections: "100" + shared_buffers: "256MB" + effective_cache_size: "512MB" + work_mem: "4MB" + maintenance_work_mem: "128MB" + log_min_duration_statement: "1000" + bootstrap: + initdb: + database: app + owner: app + secret: + name: shared-postgres-superuser + superuserSecret: + name: shared-postgres-superuser + backup: + barmanObjectStore: + destinationPath: s3://CHANGE_BUCKET_NAME/cnpg-backups/shared-postgres + endpointURL: https://CHANGE_ENDPOINT + s3Credentials: + accessKeyId: + name: cnpg-backup-credentials + key: ACCESS_KEY_ID + secretAccessKey: + name: cnpg-backup-credentials + key: ACCESS_SECRET_KEY + wal: + compression: gzip + data: + compression: gzip + jobs: 2 + retentionPolicy: "30d" + monitoring: + enablePodMonitor: true + startDelay: 3600 + livenessProbeTimeout: 90 + primaryUpdateStrategy: unsupervised + primaryUpdateMethod: switchover \ No newline at end of file diff --git a/aula-17/cnpg/scheduled-backup.yaml b/aula-17/cnpg/scheduled-backup.yaml new file mode 100644 index 0000000..889f1aa --- /dev/null +++ b/aula-17/cnpg/scheduled-backup.yaml @@ -0,0 +1,11 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: ScheduledBackup +metadata: + name: shared-postgres-backup + namespace: cnpg +spec: + cluster: + name: shared-postgres + schedule: "0 3 * * *" + backupOwnerReference: cluster + method: barmanObjectStore \ No newline at end of file diff --git a/aula-17/setup.sh b/aula-17/setup.sh new file mode 100755 index 0000000..2a686f2 --- /dev/null +++ b/aula-17/setup.sh @@ -0,0 +1,346 @@ +#!/bin/bash +# ============================================================================ +# Aula 17 - Cloud Native PostgreSQL (CNPG) +# ============================================================================ +# Instala o CloudNativePG operator e cria um cluster PostgreSQL HA +# com backup automático para Hetzner Object Storage (S3). +# +# Componentes: +# - CNPG Operator (gerenciamento do ciclo de vida do PostgreSQL) +# - Cluster PostgreSQL (3 instâncias: 1 primary + 1 sync + 1 async) +# - Backup S3 (WAL archiving + base backup diário) +# - App demo (valida conexão com o banco) +# +# Pré-requisitos: +# - Cluster Kubernetes na Hetzner (aula-08) +# - Victoria Metrics (aula-12) para métricas +# - Hetzner Object Storage com bucket criado +# - kubectl e helm instalados +# ============================================================================ + +set -e + +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)" +ENV_FILE="${SCRIPT_DIR}/.env" + +# ============================================================================ +# Gerenciamento de Configuração +# ============================================================================ + +load_config() { + if [[ -f "$ENV_FILE" ]]; then + source "$ENV_FILE" + return 0 + fi + return 1 +} + +save_config() { + cat > "$ENV_FILE" << EOF +# Configuração da Aula 17 - CNPG +# Gerado em: $(date) + +S3_ACCESS_KEY=${S3_ACCESS_KEY} +S3_SECRET_KEY=${S3_SECRET_KEY} +S3_ENDPOINT=${S3_ENDPOINT} +S3_BUCKET=${S3_BUCKET} +EOF + log_success "Configuração salva em .env" +} + +# ============================================================================ +# Verificação de Pré-requisitos +# ============================================================================ + +check_prerequisites() { + echo "" + log_info "Verificando pré-requisitos..." + + local failed=false + + for cmd in kubectl helm; do + if command -v "$cmd" &> /dev/null; then + log_success "$cmd encontrado" + else + log_error "$cmd não encontrado" + failed=true + fi + done + + if ! kubectl cluster-info &> /dev/null; then + log_error "Cluster Kubernetes não acessível. Execute a aula-08 primeiro." + exit 1 + fi + log_success "Cluster Kubernetes acessível" + + # Verificar Victoria Metrics + if kubectl get svc -n monitoring vmsingle-monitoring-victoria-metrics-k8s-stack &> /dev/null; then + log_success "Victoria Metrics encontrado" + else + log_warn "Victoria Metrics não encontrado (aula-12). PodMonitor não terá scrape target." + log_warn "O CNPG funcionará sem métricas, mas recomenda-se instalar a aula-12." + fi + + if [[ "$failed" == "true" ]]; then + exit 1 + fi +} + +# ============================================================================ +# Coleta de Configuração +# ============================================================================ + +collect_config() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Configuração do CNPG - Hetzner Object Storage${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo "" + echo -e "Você precisa de um bucket no Hetzner Object Storage." + echo -e "Crie em: ${GREEN}https://console.hetzner.cloud/storage/object${NC}" + echo "" + + if load_config; then + echo -e "Configuração existente encontrada:" + echo -e " Endpoint: ${GREEN}${S3_ENDPOINT}${NC}" + echo -e " Bucket: ${GREEN}${S3_BUCKET}${NC}" + echo "" + echo -e "[1] Usar configuração existente" + echo -e "[2] Inserir nova configuração" + read -p "Escolha [1/2]: " choice + if [[ "$choice" == "1" ]]; then + return 0 + fi + fi + + echo "" + echo -e "${YELLOW}Endpoints disponíveis:${NC}" + echo " fsn1.your-objectstorage.com (Frankfurt)" + echo " nbg1.your-objectstorage.com (Nuremberg)" + echo " hel1.your-objectstorage.com (Helsinki)" + echo "" + + if [[ -z "$S3_ENDPOINT" ]]; then + S3_ENDPOINT="nbg1.your-objectstorage.com" + fi + echo -ne "Endpoint (${GREEN}${S3_ENDPOINT}${NC}): " + read -r input + [[ -n "$input" ]] && S3_ENDPOINT="$input" + + if [[ -z "$S3_BUCKET" ]]; then + S3_BUCKET="cnpg-backups" + fi + echo -ne "Bucket name (${GREEN}${S3_BUCKET}${NC}): " + read -r input + [[ -n "$input" ]] && S3_BUCKET="$input" + + echo -ne "Access Key ID: " + read -r S3_ACCESS_KEY + if [[ -z "$S3_ACCESS_KEY" ]]; then + log_error "Access Key é obrigatória" + exit 1 + fi + + echo -ne "Secret Access Key: " + read -rs S3_SECRET_KEY + echo "" + if [[ -z "$S3_SECRET_KEY" ]]; then + log_error "Secret Key é obrigatória" + exit 1 + fi + + save_config +} + +# ============================================================================ +# Instalação do CNPG Operator +# ============================================================================ + +install_cnpg_operator() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalando CNPG Operator${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + helm repo add cnpg https://cloudnative-pg.github.io/charts 2>/dev/null || true + helm repo update cnpg + + log_info "Instalando CNPG operator no namespace cnpg-system..." + if helm status cnpg -n cnpg-system &> /dev/null; then + helm upgrade cnpg cnpg/cloudnative-pg \ + -n cnpg-system \ + --wait + else + helm install cnpg cnpg/cloudnative-pg \ + -n cnpg-system \ + --create-namespace \ + --wait + fi + log_success "CNPG operator instalado" + + log_info "Aguardando operator ficar ready..." + kubectl wait --for=condition=available deployment/cnpg-controller-manager -n cnpg-system --timeout=120s + log_success "CNPG operator pronto" +} + +# ============================================================================ +# Criar Namespace e Secrets +# ============================================================================ + +setup_namespace_and_secrets() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Criando namespace e secrets${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + kubectl create namespace cnpg --dry-run=client -o yaml | kubectl apply -f - + + log_info "Criando secret de credenciais S3..." + kubectl create secret generic cnpg-backup-credentials \ + -n cnpg \ + --from-literal=ACCESS_KEY_ID="$S3_ACCESS_KEY" \ + --from-literal=ACCESS_SECRET_KEY="$S3_SECRET_KEY" \ + --dry-run=client -o yaml | kubectl apply -f - + log_success "Secret criado" +} + +# ============================================================================ +# Criar Cluster PostgreSQL +# ============================================================================ + +create_cluster() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Criando Cluster PostgreSQL${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Gerando cluster.yaml com suas configurações..." + sed -e "s|CHANGE_BUCKET_NAME|${S3_BUCKET}|g" \ + -e "s|CHANGE_ENDPOINT|https://${S3_ENDPOINT}|g" \ + "${SCRIPT_DIR}/cnpg/cluster.yaml" | kubectl apply -f - + + log_success "Cluster aplicado" + + log_info "Aplicando backup agendado..." + kubectl apply -f "${SCRIPT_DIR}/cnpg/scheduled-backup.yaml" + log_success "Backup agendado aplicado" + + log_info "Aguardando cluster ficar ready (pode levar 2-5 minutos)..." + kubectl wait --for=condition=Ready cluster/shared-postgres -n cnpg --timeout=600s + log_success "Cluster PostgreSQL pronto!" +} + +# ============================================================================ +# Deploy App Demo +# ============================================================================ + +deploy_demo() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Deploy App Demo${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + + log_info "Deployando app de demonstração..." + kubectl apply -f "${SCRIPT_DIR}/app-demo/deployment.yaml" + kubectl apply -f "${SCRIPT_DIR}/app-demo/service.yaml" + + log_info "Aguardando app demo..." + kubectl wait --for=condition=available deployment/pg-demo -n cnpg --timeout=120s 2>/dev/null || true + + # Mostrar resultado + sleep 3 + echo "" + log_info "Resultado da conexão do app demo:" + kubectl logs deployment/pg-demo -n cnpg --tail=20 2>/dev/null || log_warn "Aguardando logs..." + log_success "App demo deployado" +} + +# ============================================================================ +# Resumo +# ============================================================================ + +show_summary() { + echo "" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo -e "${CYAN} Instalação Concluída${NC}" + echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" + echo "" + echo -e "${GREEN}Componentes instalados:${NC}" + echo " - CNPG Operator (namespace: cnpg-system)" + echo " - Cluster PostgreSQL 3 instâncias (namespace: cnpg)" + echo " - Backup diário às 03:00 → Hetzner Object Storage" + echo " - App demo (valida conexão)" + echo "" + + # Status do cluster + echo -e "${GREEN}Status do cluster:${NC}" + kubectl get cluster -n cnpg + echo "" + + echo -e "${GREEN}Instâncias:${NC}" + kubectl get pods -n cnpg -l cnpg.io/cluster=shared-postgres + echo "" + + echo -e "${GREEN}Serviços criados pelo CNPG:${NC}" + kubectl get svc -n cnpg -l cnpg.io/cluster=shared-postgres + echo "" + + echo -e "${GREEN}Comandos essenciais:${NC}" + echo "" + echo " # Status do cluster" + echo " kubectl get cluster -n cnpg" + echo "" + echo " # Ver instâncias (primary vs replica)" + echo " kubectl get pods -n cnpg -l cnpg.io/cluster=shared-postgres -o wide" + echo "" + echo " # Failover manual (promover replica)" + echo " kubectl cnpg promote shared-postgres -n cnpg" + echo "" + echo " # Trigger backup manual" + echo " kubectl cnpg backup shared-postgres -n cnpg" + echo "" + echo " # Listar backups" + echo " kubectl get backups -n cnpg" + echo "" + echo " # Logs do app demo" + echo " kubectl logs -f deployment/pg-demo -n cnpg" + echo "" + echo -e "${YELLOW}Próximos passos:${NC}" + echo " - Conecte sua app ao service: shared-postgres-rw.cnpg.svc:5432" + echo " - O backup automático roda às 03:00 diariamente" + echo " - Para recovery/PITR, consulte o README" + echo "" +} + +# ============================================================================ +# Execução +# ============================================================================ + +main() { + echo "" + echo -e "${CYAN}╔═══════════════════════════════════════════════════════════╗${NC}" + echo -e "${CYAN}║ Aula 17 - Cloud Native PostgreSQL (CNPG) ║${NC}" + echo -e "${CYAN}╚═══════════════════════════════════════════════════════════╝${NC}" + + check_prerequisites + collect_config + install_cnpg_operator + setup_namespace_and_secrets + create_cluster + deploy_demo + show_summary +} + +main "$@"