Saltar para o conteúdo
RDS PostgreSQL na AWS: Setup, Segurança e Monitoramento
AWS

RDS PostgreSQL na AWS: Setup, Segurança e Monitoramento

1 de julho de 2024·Paulo de Paula

RDS PostgreSQL é o banco de dados relacional mais usado na AWS. Bem configurado, você tem backups automáticos, failover, patching e monitoramento gerenciados. Mal configurado, você tem um banco caro, lento e vulnerável.

Escolhendo a classe de instância

db.t3.micro   — dev/teste, 2 vCPU, 1GB RAM. Burst CPU (não use em produção)
db.t3.medium  — pequenos apps, 2 vCPU, 4GB RAM. Burst CPU
db.m6g.large  — produção pequena, 2 vCPU, 8GB RAM. ARM64, 15% mais barato que m5
db.m6g.xlarge — produção média, 4 vCPU, 16GB RAM
db.r6g.large  — read-heavy, 2 vCPU, 16GB RAM (otimizado para memória)

Regra prática: use db.t3 apenas para ambientes não-produtivos. Em produção, comece com db.m6g.large e escale verticalmente baseado em métricas reais.

Terraform: provisionamento completo

# rds.tf
resource "aws_db_subnet_group" "main" {
  name       = "${var.project}-rds-subnet-group"
  subnet_ids = var.private_subnet_ids  # NUNCA em subnet pública

  tags = { Name = "${var.project}-rds-subnet-group" }
}

resource "aws_security_group" "rds" {
  name   = "${var.project}-rds-sg"
  vpc_id = var.vpc_id

  # Só aceita conexões de dentro da VPC (app servers)
  ingress {
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [var.app_security_group_id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_db_parameter_group" "postgres16" {
  name   = "${var.project}-postgres16"
  family = "postgres16"

  # Logging de queries lentas (> 1 segundo)
  parameter {
    name  = "log_min_duration_statement"
    value = "1000"
  }

  # Performance
  parameter {
    name  = "shared_buffers"
    value = "{DBInstanceClassMemory/4}"  # 25% da RAM
  }

  parameter {
    name  = "work_mem"
    value = "16384"  # 16MB por sort/hash operation
  }

  parameter {
    name  = "effective_cache_size"
    value = "{DBInstanceClassMemory*3/4}"  # 75% da RAM
  }

  # Durabilidade vs Performance
  parameter {
    name  = "synchronous_commit"
    value = "on"  # Nunca desative em produção
  }
}

resource "aws_db_instance" "main" {
  identifier        = "${var.project}-postgres"
  engine            = "postgres"
  engine_version    = "16.3"
  instance_class    = var.db_instance_class
  allocated_storage = 100
  storage_type      = "gp3"
  storage_encrypted = true
  kms_key_id        = aws_kms_key.rds.arn

  db_name  = var.db_name
  username = "dbadmin"
  password = random_password.db_master.result

  # Alta disponibilidade
  multi_az               = true   # Replica síncrona em outra AZ
  publicly_accessible    = false  # NUNCA true em produção

  # Backup
  backup_retention_period = 30  # 30 dias de backup
  backup_window           = "03:00-04:00"  # UTC (meia-noite BRT)
  maintenance_window      = "Mon:04:00-Mon:05:00"

  # Configuração
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  parameter_group_name   = aws_db_parameter_group.postgres16.name

  # Proteção contra deleção acidental
  deletion_protection      = true
  skip_final_snapshot      = false
  final_snapshot_identifier = "${var.project}-final-snapshot"

  # IAM authentication (dispensa senha na app)
  iam_database_authentication_enabled = true

  tags = { Name = "${var.project}-postgres" }
}

Multi-AZ: o que realmente protege

Multi-AZ cria uma réplica síncrona em outra AZ. Em caso de falha da instância primária, o RDS faz failover automático em 60-120 segundos.

O que Multi-AZ protege:

  • Falha de hardware na AZ primária
  • Manutenção programada (failover durante patching)
  • Falha de rede na AZ primária

O que Multi-AZ NÃO protege:

  • Corrupção de dados (replicada instantaneamente para a standby)
  • Erro humano (DELETE sem WHERE — precisa de backup point-in-time)
  • Falha regional (precisa de Cross-Region Read Replica)

IAM Database Authentication

Elimina senhas hardcoded na aplicação. A app usa um token temporário gerado via SDK:

// lib/db.js
import { Signer } from '@aws-sdk/rds-signer';
import pg from 'pg';

const signer = new Signer({
  hostname: process.env.RDS_HOSTNAME,
  port:     5432,
  region:   'us-east-1',
  username: 'app_user',
});

export async function criarConexao() {
  const token = await signer.getAuthToken();  // válido por 15 minutos
  
  return new pg.Pool({
    host:     process.env.RDS_HOSTNAME,
    database: process.env.DB_NAME,
    user:     'app_user',
    password: token,
    ssl:      { rejectUnauthorized: true, ca: process.env.RDS_CA },
    max: 10,
  });
}

A role IAM da aplicação precisa de permissão rds-db:connect:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "rds-db:connect",
    "Resource": "arn:aws:rds-db:us-east-1:123456789:dbuser:*/app_user"
  }]
}

Point-in-Time Recovery

# Restore para um ponto específico via AWS CLI
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier meu-projeto-postgres \
  --target-db-instance-identifier meu-projeto-postgres-restored \
  --restore-time "2024-11-15T14:30:00Z"

Com 30 dias de backup, você pode restaurar para qualquer segundo dentro desse período — essencial após DELETE/UPDATE acidental.

CloudWatch: métricas para monitorar

CPUUtilization         > 80% por 5min → alertar
FreeableMemory         < 500MB        → alertar
DatabaseConnections    > 80% do max   → alertar (max = max_connections no param group)
ReadLatency            > 20ms         → investigar
WriteLatency           > 20ms         → investigar
FreeStorageSpace       < 20GB         → alertar (auto-grow ou aumentar alocação)
ReplicaLag             > 1s           → alertar (se tiver read replica)

Connection Pooling com RDS Proxy

Lambdas e containers criam e destroem conexões frequentemente. Sem pooler, você esgota o max_connections do PostgreSQL:

resource "aws_db_proxy" "main" {
  name                   = "${var.project}-proxy"
  debug_logging          = false
  engine_family          = "POSTGRESQL"
  idle_client_timeout    = 1800
  require_tls            = true
  role_arn               = aws_iam_role.rds_proxy.arn
  vpc_security_group_ids = [aws_security_group.rds_proxy.id]
  vpc_subnet_ids         = var.private_subnet_ids

  auth {
    auth_scheme = "SECRETS"
    secret_arn  = aws_secretsmanager_secret.db_password.arn
    iam_auth    = "REQUIRED"
  }

  target_group {
    db_instance_identifier = aws_db_instance.main.identifier
    connection_pool_config {
      max_connections_percent      = 90
      max_idle_connections_percent = 50
    }
  }
}

A aplicação conecta no endpoint do Proxy em vez do RDS direto. O Proxy reutiliza conexões entre invocações de Lambda, reduzindo latência e evitando too many connections.

Checklist de segurança

  • publicly_accessible = false — sem acesso direto da internet
  • Subnet privada — sem rotas para internet gateway
  • Security group só permite porta 5432 dos app servers
  • storage_encrypted = true — dados em repouso cifrados
  • SSL obrigatório na conexão
  • IAM auth em vez de senha fixa
  • deletion_protection = true
  • Backup mínimo 7 dias (30 para produção)