Como Montei uma Arquitetura Three-Tier na AWS que Aguenta Pancada

Minha experiência montando uma arquitetura de 3 camadas na AWS. VPC, Load Balancer, Auto Scaling e RDS Multi-AZ - o que funcionou e o que eu mudaria.

AWSArquitetura CloudVPCAlta DisponibilidadeTerraform

Depois de quebrar a cabeça algumas vezes com arquiteturas mal planejadas, resolvi documentar o que aprendi montando uma infraestrutura three-tier na AWS que realmente funciona em produção.

Por que Three-Tier?

Antes de sair criando recursos, vale entender o porquê dessa separação em camadas:

  • Camada Pública - ALB fica aqui, exposto pra internet
  • Camada de Aplicação - Onde seus servidores rodam, protegidos
  • Camada de Dados - Banco de dados isolado, sem acesso externo

Parece óbvio, mas já vi muita gente jogando RDS em subnet pública “porque é mais fácil conectar”. Não faça isso.

A VPC que Uso

Aprendi que vale a pena gastar um tempo planejando o CIDR block. Nada pior que ficar sem IPs ou ter conflito com VPN depois.

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
}

Com /16 você tem 65.536 IPs - suficiente pra crescer sem dor de cabeça.

Subnets em 3 AZs

Distribuo em 3 Availability Zones. Já tive problema quando uma AZ ficou fora do ar - com apenas 2, metade do tráfego caiu. Com 3, o impacto é bem menor.

# Públicas - ALB fica aqui
resource "aws_subnet" "public" {
  count                   = 3
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index + 1}.0/24"
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
}

# Privadas App - EC2/ECS ficam aqui
resource "aws_subnet" "private_app" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 10}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
}

# Privadas Data - RDS/ElastiCache
resource "aws_subnet" "private_data" {
  count             = 3
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 20}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]
}

O NAT Gateway que Dói no Bolso

Uma coisa que me pegou de surpresa: NAT Gateway é caro. Cada um custa uns $32/mês parado, mais $0.045 por GB de dados.

A tentação é usar só 1 NAT pra economizar. Problema: se a AZ onde ele tá cair, suas instâncias privadas perdem acesso à internet.

Minha decisão foi usar 3 NATs (um por AZ) em produção, mas apenas 1 em ambiente de dev/staging.

resource "aws_nat_gateway" "main" {
  count         = var.environment == "production" ? 3 : 1
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
}

Load Balancer com as Configs que Importam

O ALB é simples de configurar, mas tem detalhes que fazem diferença:

resource "aws_lb" "main" {
  name               = "app-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id

  # Ativa isso em produção - evita deletar sem querer
  enable_deletion_protection = true
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = "443"
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = aws_acm_certificate.main.arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

O health check também precisa de atenção. Já vi aplicação ser derrubada porque o health check tava muito agressivo:

resource "aws_lb_target_group" "app" {
  name     = "app-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    enabled             = true
    healthy_threshold   = 2
    unhealthy_threshold = 3   # Dá 3 chances antes de marcar unhealthy
    timeout             = 5
    interval            = 30  # Não precisa checar toda hora
    path                = "/health"
    matcher             = "200"
  }
}

Auto Scaling que Funciona

O Auto Scaling parece mágico no papel, mas na prática tem pegadinhas.

Primeiro, o launch template precisa ter tudo que a instância precisa pra subir sozinha:

resource "aws_launch_template" "app" {
  name_prefix   = "app-"
  image_id      = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.medium"

  user_data = base64encode(<<-EOF
    #!/bin/bash
    # Suas dependências aqui
    yum update -y
    # Puxa a aplicação e sobe
    docker pull minha-app:latest
    docker run -d -p 80:80 minha-app:latest
  EOF
  )

  # IMDSv2 obrigatório - questão de segurança
  metadata_options {
    http_endpoint = "enabled"
    http_tokens   = "required"
  }
}

As políticas de scaling que uso:

resource "aws_autoscaling_group" "app" {
  name                = "app-asg"
  desired_capacity    = 3
  min_size            = 2     # Nunca fica zerado
  max_size            = 10
  target_group_arns   = [aws_lb_target_group.app.arn]
  vpc_zone_identifier = aws_subnet.private_app[*].id
  health_check_type   = "ELB"  # Usa o health check do ALB
}

# Scale up quando CPU passar de 70%
resource "aws_autoscaling_policy" "scale_up" {
  name                   = "scale-up"
  scaling_adjustment     = 2
  adjustment_type        = "ChangeInCapacity"
  cooldown               = 300  # 5 min entre scales
  autoscaling_group_name = aws_autoscaling_group.app.name
}

RDS Multi-AZ - Não Economize Aqui

Banco de dados é onde você não quer ter downtime. Multi-AZ custa mais, mas o failover automático já me salvou.

resource "aws_db_instance" "main" {
  identifier     = "production-db"
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.r6g.large"

  allocated_storage     = 100
  max_allocated_storage = 500  # Cresce automático
  storage_type          = "gp3"
  storage_encrypted     = true

  multi_az = true  # Failover automático

  backup_retention_period = 30
  deletion_protection     = true

  # Performance Insights ajuda muito no debug
  performance_insights_enabled = true
}

Quanto Isso Custa?

Sendo realista com os números (us-east-1):

RecursoCusto/mês
ALB~$25
3x t3.medium~$90
3x NAT Gateway~$100
RDS r6g.large Multi-AZ~$350
Total~$565/mês

Dá pra reduzir bastante usando Reserved Instances ou Savings Plans se o workload for previsível.

O que Eu Mudaria

Olhando pra trás, algumas coisas que faria diferente:

  1. Começaria com Fargate ao invés de EC2 - menos coisa pra gerenciar
  2. Usaria Parameter Store desde o início pra secrets
  3. Terraform workspaces ao invés de pastas separadas por ambiente

A arquitetura three-tier é battle-tested e funciona. O importante é entender o porquê de cada componente, não só copiar configs.


Se quiser trocar ideia sobre arquitetura cloud, me chama no LinkedIn ou vê meus projetos no GitHub.