Os artigos anteriores introduziram EC2, VPC e IAM como parte da prática com Terraform. Os recursos foram criados, o código funcionou. Mas criar recursos via Terraform sem entender profundamente os serviços subjacentes é construir sobre uma fundação frágil — quando algo dá errado, quando o comportamento não é o esperado, quando é necessário tomar uma decisão de arquitetura, a falta de conhecimento profundo se manifesta como horas de debugging e decisões subótimas.
Este artigo aprofunda os três serviços mais fundamentais da AWS — EC2, VPC e IAM — com o nível de detalhe necessário para tomar decisões de arquitetura conscientes e diagnosticar problemas com eficiência.
EC2 em Profundidade
Famílias de Instâncias e Quando Usar Cada Uma
A AWS oferece centenas de tipos de instâncias organizados em famílias com propósitos distintos. Escolher o tipo errado significa pagar mais por menos performance ou ter um gargalo que não é o esperado.
As famílias mais relevantes para aplicações web e serviços de backend:
Família T — Burstable Performance. As instâncias T acumulam créditos de CPU durante períodos ociosos e os gastam durante picos. São econômicas para workloads com tráfego irregular — servidores de desenvolvimento, ambientes de staging, aplicações com tráfego variável. A geração atual é T4g (ARM Graviton) e T3. O modelo t3.medium (2 vCPU, 4GB RAM) é o ponto de partida mais comum para APIs de médio porte.
A armadilha das instâncias T é o CPU credit exhaustion: se a aplicação demanda CPU continuamente acima da baseline, os créditos se esgotam e a performance cai drasticamente. O CloudWatch expõe a métrica CPUCreditBalance — quando ela se aproxima de zero em uma instância T, é hora de migrar para uma família M.
Família M — General Purpose. Balanceamento equilibrado de CPU, memória e rede. A família M6i (Intel), M6a (AMD) e M6g (Graviton ARM) representa o padrão para servidores de aplicação em produção. A geração Graviton (sufixo g) oferece, em média, 20% mais performance por custo que as equivalentes x86.
Família C — Compute Optimized. Alta proporção de CPU em relação à memória. Indicada para servidores de aplicação que fazem processamento intensivo — codificação de vídeo, análise científica, servidores de jogos, processamento de machine learning para inferência. A família atual é C7g (Graviton).
Família R — Memory Optimized. Alta proporção de memória em relação à CPU. Indicada para bancos de dados in-memory, caches de grande volume, processamento de big data com Spark ou Hadoop. A família R7g é a geração atual.
Família I — Storage Optimized. Alto throughput de I/O com NVMe local. Indicada para bancos de dados que precisam de latência de storage extremamente baixa — Cassandra, MongoDB, Elasticsearch em alta escala. O armazenamento local é efêmero — perde os dados ao parar a instância.
Ciclo de Vida de uma Instância EC2
Entender os estados possíveis de uma instância é fundamental para diagnosticar comportamentos inesperados e automatizar corretamente:
pending → running → stopping → stopped → terminated
↓
shutting-down → terminated
pending — a instância está sendo inicializada. O user_data está sendo executado. A instância ainda não está pronta para receber tráfego.
running — a instância está em execução. Cobrança ocorre neste estado.
stopping — o sistema operacional está sendo encerrado graciosamente. Em instâncias com armazenamento EBS, o volume é preservado.
stopped — a instância está desligada. O EBS é preservado. A cobrança de EC2 cessa, mas o EBS continua sendo cobrado. O IP público é liberado — ao reiniciar, um novo IP é atribuído, a menos que haja um Elastic IP.
shutting-down / terminated — a instância está sendo destruída permanentemente. Por padrão, os volumes EBS com DeleteOnTermination=true são destruídos junto.
EBS: Tipos de Volume e Quando Usar
O Elastic Block Store é o armazenamento de bloco persistente da AWS. A escolha do tipo de volume afeta diretamente a performance e o custo:
gp3 — General Purpose SSD de terceira geração. O padrão recomendado para a maioria dos casos. Oferece 3.000 IOPS e 125 MB/s de throughput base, independentemente do tamanho do volume. IOPS e throughput adicionais podem ser provisionados separadamente. Mais econômico que o gp2 para volumes acima de 1TB.
io2 Block Express — SSD de alta performance com IOPS provisionadas. Usado em bancos de dados que precisam de latência consistente abaixo de 1ms e IOPS acima de 16.000. Significativamente mais caro que o gp3.
st1 — HDD de throughput otimizado. Para dados acessados sequencialmente com alto volume — logs, data warehouses, backups. Não adequado para sistemas operacionais ou bancos de dados transacionais.
sc1 — HDD de custo otimizado. O mais barato da linha. Para dados acessados raramente — arquivos de backup, dados frios.
# Exemplo de configuração de volumes no Terraform
resource "aws_instance" "banco_dados" {
ami = data.aws_ami.ubuntu.id
instance_type = "r6g.xlarge"
# Volume raiz — sistema operacional
root_block_device {
volume_type = "gp3"
volume_size = 50
encrypted = true
delete_on_termination = true
}
# Volume de dados — banco de dados
ebs_block_device {
device_name = "/dev/sdb"
volume_type = "io2"
volume_size = 500
iops = 10000
encrypted = true
delete_on_termination = false # preserva dados ao terminar a instância
}
}
User Data e Instance Metadata Service
O user data é um script executado uma única vez quando a instância inicia pela primeira vez. É o mecanismo de bootstrapping — instalação de pacotes, configuração inicial, registro em sistemas de configuração.
#!/bin/bash
# user_data.sh — script de bootstrapping de uma instância de aplicação
set -euo pipefail
# Configura logging do user_data
exec > >(tee /var/log/user-data.log | logger -t user-data -s 2>/dev/console) 2>&1
echo "=== Iniciando bootstrapping ==="
echo "Instância: $(curl -s http://169.254.169.254/latest/meta-data/instance-id)"
echo "AZ: $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)"
echo "Tipo: $(curl -s http://169.254.169.254/latest/meta-data/instance-type)"
# Atualiza o sistema
apt-get update -y
apt-get upgrade -y
# Instala dependências
apt-get install -y docker.io awscli jq
# Configura Docker
systemctl enable docker
systemctl start docker
usermod -aG docker ubuntu
# Lê segredos do AWS Secrets Manager
SECRET=$(aws secretsmanager get-secret-value \
--secret-id "minha-api/producao" \
--region us-east-1 \
--query SecretString \
--output text)
# Cria o arquivo .env a partir dos segredos
echo "$SECRET" | jq -r 'to_entries[] | "\(.key)=\(.value)"' \
> /opt/minha-api/.env
chmod 600 /opt/minha-api/.env
# Baixa e inicia a aplicação
ACCOUNT_ID=$(curl -s http://169.254.169.254/latest/meta-data/iam/info \
| jq -r '.InstanceProfileArn' | cut -d: -f5)
REGION=$(curl -s http://169.254.169.254/latest/meta-data/placement/region)
aws ecr get-login-password --region $REGION | \
docker login --username AWS --password-stdin \
$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
docker run -d \
--name minha-api \
--restart unless-stopped \
--env-file /opt/minha-api/.env \
-p 3000:3000 \
$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/minha-api:latest
echo "=== Bootstrapping concluído ==="
O Instance Metadata Service (IMDS) disponível em http://169.254.169.254/latest/meta-data/ expõe informações sobre a instância — ID, tipo, AZ, IP, IAM role — sem necessidade de credenciais. A versão IMDSv2 é mais segura e exige um token de sessão:
# IMDSv2 — recomendado para novas instâncias
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -s \
-H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/instance-id)
PRIVATE_IP=$(curl -s \
-H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/local-ipv4)
IAM_ROLE=$(curl -s \
-H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/)
VPC em Profundidade
CIDR Planning: A Decisão Que É Difícil de Desfazer
O bloco CIDR de uma VPC é uma das poucas decisões em infraestrutura AWS que é genuinamente difícil de reverter. Uma vez que a VPC está criada e tem recursos dentro dela, alterar o CIDR principal exige destruir e recriar tudo.
Princípios para planejamento de CIDR:
Use blocos /16 para VPCs de produção. Um /16 oferece 65.536 endereços — espaço mais que suficiente para crescer sem desperdiçar. Blocos menores parecem econômicos mas criam problemas quando o sistema cresce.
Evite sobreposição com redes on-premises. Se a empresa tem ou planeja ter uma conexão VPN ou Direct Connect com data center próprio, os blocos CIDR não podem se sobrepor. Os ranges 10.0.0.0/8, 172.16.0.0/12 e 192.168.0.0/16 são os ranges privados RFC 1918 — planeje a alocação com antecedência.
Reserve espaço para crescimento. Se a organização tem múltiplas contas AWS com peering entre VPCs, todos os blocos CIDR precisam ser distintos.
Uma estrutura de alocação para uma organização com múltiplos ambientes:
10.0.0.0/8 — range organizacional completo
10.0.0.0/16 — conta Production
10.0.0.0/24 — subnet pública AZ-a
10.0.1.0/24 — subnet pública AZ-b
10.0.2.0/24 — subnet pública AZ-c
10.0.10.0/24 — subnet privada app AZ-a
10.0.11.0/24 — subnet privada app AZ-b
10.0.12.0/24 — subnet privada app AZ-c
10.0.20.0/24 — subnet privada db AZ-a
10.0.21.0/24 — subnet privada db AZ-b
10.0.22.0/24 — subnet privada db AZ-c
10.1.0.0/16 — conta Staging
10.2.0.0/16 — conta Development
10.3.0.0/16 — conta Shared Services
Security Groups vs NACLs: Quando Usar Cada Um
Ambos controlam tráfego de rede, mas com filosofias e aplicações diferentes:
Security Groups são stateful e operam no nível de instâncias e interfaces de rede. Stateful significa que o tráfego de retorno de uma conexão iniciada de dentro é automaticamente permitido, independentemente das regras. Security Groups só têm regras de allow — sem regras de deny explícitas.
Network ACLs são stateless e operam no nível de subnets. Stateless significa que o tráfego de retorno precisa de uma regra explícita de allow. NACLs têm regras de allow e deny, processadas em ordem numérica.
Na prática, a maioria dos times usa Security Groups para controle de acesso e NACLs apenas para regras de bloqueio amplo — bloquear um range de IPs maliciosos, impedir comunicação entre subnets de ambientes diferentes:
# Security Group — controle de acesso granular por recurso
resource "aws_security_group" "aplicacao" {
name = "minha-api-app-sg"
vpc_id = aws_vpc.principal.id
# Apenas permite tráfego do load balancer — referência por SG
ingress {
from_port = 3000
to_port = 3000
protocol = "tcp"
security_groups = [aws_security_group.load_balancer.id]
description = "Tráfego do ALB"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
description = "Todo tráfego de saída"
}
}
# NACL — bloqueio amplo no nível de subnet
resource "aws_network_acl" "privada" {
vpc_id = aws_vpc.principal.id
subnet_ids = aws_subnet.privada[*].id
# Bloqueia um range de IPs conhecido como malicioso
ingress {
rule_no = 50
protocol = "-1"
action = "deny"
cidr_block = "203.0.113.0/24"
from_port = 0
to_port = 0
}
# Permite todo o resto
ingress {
rule_no = 100
protocol = "-1"
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
egress {
rule_no = 100
protocol = "-1"
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
}
VPC Endpoints: Tráfego Interno sem Sair para a Internet
Por padrão, quando uma instância EC2 em uma subnet privada acessa o S3 ou o DynamoDB, o tráfego sai pela internet — passando pelo NAT Gateway e gerando custo de transferência de dados. VPC Endpoints eliminam esse custo e aumentam a segurança ao manter o tráfego dentro da rede da AWS:
# Gateway Endpoint para S3 — gratuito
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.principal.id
service_name = "com.amazonaws.us-east-1.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.privada[*].id
tags = {
Name = "s3-endpoint"
}
}
# Interface Endpoint para Secrets Manager — tem custo por hora
resource "aws_vpc_endpoint" "secrets_manager" {
vpc_id = aws_vpc.principal.id
service_name = "com.amazonaws.us-east-1.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.privada[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = {
Name = "secretsmanager-endpoint"
}
}
IAM em Profundidade
O Modelo de Avaliação de Políticas
O IAM avalia permissões seguindo uma lógica bem definida que é fundamental entender para diagnosticar erros de permissão:
1. Deny explícito em qualquer política? → DENY (fim)
2. Allow explícito em alguma política? → ALLOW
3. Nenhum dos dois? → DENY implícito (padrão)
As políticas avaliadas incluem: políticas de identidade (anexadas ao usuário, grupo ou role), políticas de resource (bucket S3, key KMS), SCPs do AWS Organizations e permission boundaries.
Políticas Inline vs Gerenciadas
Existem três tipos de políticas IAM com trade-offs distintos:
AWS Managed Policies — criadas e mantidas pela AWS. Cobrem casos de uso comuns como AmazonS3ReadOnlyAccess e AmazonSSMManagedInstanceCore. São convenientes mas frequentemente mais permissivas que o necessário — concedem acesso a ações que a aplicação não precisa.
Customer Managed Policies — criadas pelo time, reutilizáveis em múltiplas identidades. O padrão recomendado para políticas que seguem o princípio do mínimo privilégio.
Inline Policies — embutidas diretamente em um usuário, grupo ou role. Adequadas para permissões únicas que não fazem sentido ser reutilizadas.
Escrevendo Políticas com Mínimo Privilégio
A tendência natural ao começar com IAM é conceder permissões amplas — s3:* em vez de s3:GetObject — para evitar erros de permissão durante o desenvolvimento. Isso é tecnicamente correto mas perigoso em produção.
Uma política bem construída para uma aplicação que lê e escreve em um bucket S3 específico:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListarBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::minha-empresa-assets-production",
"Condition": {
"StringLike": {
"s3:prefix": ["uploads/*", "publico/*"]
}
}
},
{
"Sid": "OperacoesNosObjetos",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::minha-empresa-assets-production/uploads/*",
"arn:aws:s3:::minha-empresa-assets-production/publico/*"
]
},
{
"Sid": "LerSecretos",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:minha-api/*"
},
{
"Sid": "LogsCloudWatch",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:us-east-1:123456789:log-group:/minha-api/*"
}
]
}
IAM Roles para EC2: O Fluxo de Credenciais
Quando uma instância EC2 tem uma IAM Role associada via Instance Profile, as credenciais temporárias são renovadas automaticamente pelo serviço de metadados. A aplicação nunca lida com Access Keys estáticas:
// A AWS SDK usa automaticamente as credenciais da Instance Role
// Não é necessário configurar ACCESS_KEY_ID ou SECRET_ACCESS_KEY
const { S3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
// O SDK detecta automaticamente as credenciais nesta ordem:
// 1. Variáveis de ambiente (AWS_ACCESS_KEY_ID, etc.)
// 2. Arquivo ~/.aws/credentials
// 3. Instance Metadata Service (IMDS) — usado em EC2 com IAM Role
// 4. ECS Task Role — usado em containers ECS
const s3 = new S3Client({ region: 'us-east-1' });
async function uploadArquivo(buffer, chave) {
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: chave,
Body: buffer,
ServerSideEncryption: 'AES256',
}));
}
Auditando Permissões com IAM Access Analyzer
O IAM Access Analyzer é um serviço que identifica recursos com acesso externo não intencional e políticas excessivamente permissivas:
# Cria um analyzer para a conta AWS atual
aws accessanalyzer create-analyzer \
--analyzer-name minha-empresa-analyzer \
--type ACCOUNT \
--region us-east-1
# Lista findings — recursos com acesso externo não intencional
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789:analyzer/minha-empresa-analyzer \
--filter '{"status": {"eq": ["ACTIVE"]}}' \
--query 'findings[*].{Resource:resource,Type:resourceType,Principal:principal}' \
--output table
# Valida uma política antes de aplicá-la
aws accessanalyzer validate-policy \
--policy-document file://politica.json \
--policy-type IDENTITY_POLICY \
--query 'findings[*].{Tipo:findingType,Mensagem:findingDetails}' \
--output table
O Que Vem a Seguir
O próximo artigo aprofunda os serviços gerenciados de computação da AWS — ECS para containers, Lambda para funções serverless e como escolher entre as duas abordagens para diferentes tipos de workload.
Referências para Aprofundamento
Documentação oficial AWS - Amazon EC2 Documentation — docs.aws.amazon.com — Documentação completa do EC2, incluindo guias de tipos de instância, armazenamento EBS e ciclo de vida de instâncias. - Amazon VPC Documentation — docs.aws.amazon.com — Referência completa da VPC, cobrindo CIDR planning, security groups, NACLs e VPC endpoints com exemplos detalhados. - AWS IAM Documentation — docs.aws.amazon.com — Documentação do IAM, incluindo referência de políticas, guia do modelo de avaliação e boas práticas de segurança.
Segurança e boas práticas - AWS Security Best Practices — docs.aws.amazon.com — Pilar de segurança do AWS Well-Architected Framework, com recomendações detalhadas para IAM, redes e proteção de dados. - IAM Access Analyzer — docs.aws.amazon.com — Documentação do IAM Access Analyzer com guia de uso para identificar acessos não intencionais e validar políticas.
Ferramentas - AWS Policy Simulator — policysim.aws.amazon.com — Ferramenta oficial da AWS para testar e debugar políticas IAM sem precisar aplicá-las em produção.