# 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× CX33 ~€24/mês (~$27) | | 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? - CX33: 4 vCPU Intel, 8GB RAM, 80GB NVMe → **~€8/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 CX33 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 <