feat(aula-17): adicionar Cloud Native PostgreSQL (CNPG)
- Cluster PostgreSQL 3 instâncias com anti-affinity por hostname (hard) e por zona (soft) para spread entre datacenters Hetzner - Backup automático via Barman Cloud para Hetzner Object Storage (S3) - ScheduledBackup diário às 03:00 - App demo que valida conexão com o banco - setup.sh: instala operator, cria cluster, deploya demo - cleanup.sh: remoção limpa (backups S3 preservados)
This commit is contained in:
Executable
+346
@@ -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 <instance-name> -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 "$@"
|
||||
Reference in New Issue
Block a user