#!/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 read -r -p "Endpoint (${GREEN}${S3_ENDPOINT}${NC}): " input < /dev/tty [[ -n "$input" ]] && S3_ENDPOINT="$input" if [[ -z "$S3_BUCKET" ]]; then S3_BUCKET="cnpg-backups" fi read -r -p "Bucket name (${GREEN}${S3_BUCKET}${NC}): " input < /dev/tty [[ -n "$input" ]] && S3_BUCKET="$input" read -r -p "Access Key ID: " S3_ACCESS_KEY < /dev/tty if [[ -z "$S3_ACCESS_KEY" ]]; then log_error "Access Key é obrigatória" exit 1 fi read -rs -p "Secret Access Key: " S3_SECRET_KEY < /dev/tty 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-cloudnative-pg -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}" if kubectl get namespace cnpg &>/dev/null; then NS_PHASE=$(kubectl get namespace cnpg -o jsonpath='{.status.phase}' 2>/dev/null) if [[ "$NS_PHASE" == "Terminating" ]]; then log_info "Namespace cnpg em Terminating, forçando remoção..." kubectl get namespace cnpg -o json | \ python3 -c "import sys,json; d=json.load(sys.stdin); d['metadata'].pop('finalizers',None); json.dump(d,sys.stdout)" | \ kubectl replace --raw /api/v1/namespaces/cnpg/finalize -f - 2>/dev/null || true kubectl wait --for=delete namespace cnpg --timeout=60s 2>/dev/null || sleep 5 fi fi 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|${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 "$@"