Artigo 26 — Seus Primeiros Recursos na AWS com Terraform
Módulo 5 · Infraestrutura como Código Prof. Ricardo Matos — Dominando DevOps & Cloud em 1 Ano
Colocando Infraestrutura Real no Ar
O artigo anterior introduziu o Terraform com um exemplo que já incluía alguns recursos AWS. Este artigo expande esse escopo de forma sistemática: serão criados os recursos fundamentais que formam a base de qualquer infraestrutura na AWS — rede, computação, armazenamento e banco de dados — todos gerenciados pelo Terraform, todos versionáveis e reproduzíveis.
O objetivo ao final deste artigo é ter uma infraestrutura funcional completa provisionada inteiramente por código, compreendendo uma VPC com subnets públicas e privadas, instâncias EC2, um bucket S3 e um banco de dados RDS — os mesmos componentes que sustentam a maioria das aplicações web em produção.
Configurando as Credenciais AWS
Antes de qualquer coisa, o Terraform precisa de credenciais para interagir com a AWS. A forma recomendada é via variáveis de ambiente — nunca hardcoded nos arquivos de configuração:
# Configura credenciais via AWS CLI (forma mais conveniente)
aws configure
# AWS Access Key ID: AKIA...
# AWS Secret Access Key: ...
# Default region name: us-east-1
# Default output format: json
# Ou via variáveis de ambiente diretamente
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_DEFAULT_REGION="us-east-1"
# Verifica que as credenciais estão funcionando
aws sts get-caller-identity
Para ambientes de produção e pipelines de CI/CD, a abordagem mais segura é usar IAM Roles com OIDC — sem chaves estáticas — conforme abordado no artigo sobre secrets. Para desenvolvimento local, as credenciais via aws configure são suficientes.
Construindo uma VPC Completa
A Virtual Private Cloud é o componente de rede fundamental da AWS. Toda infraestrutura séria começa com uma VPC bem configurada — não com a VPC padrão que a AWS cria automaticamente.
# networking.tf
# VPC principal
resource "aws_vpc" "principal" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-${var.environment}-vpc"
}
}
# Internet Gateway — permite tráfego de entrada e saída da internet
resource "aws_internet_gateway" "principal" {
vpc_id = aws_vpc.principal.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
}
}
# Subnets públicas — para load balancers e bastion hosts
resource "aws_subnet" "publica" {
count = length(var.availability_zones)
vpc_id = aws_vpc.principal.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-subnet-publica-${count.index + 1}"
Tier = "public"
}
}
# Subnets privadas — para aplicação e banco de dados
resource "aws_subnet" "privada" {
count = length(var.availability_zones)
vpc_id = aws_vpc.principal.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-${var.environment}-subnet-privada-${count.index + 1}"
Tier = "private"
}
}
# Elastic IPs para os NAT Gateways
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = {
Name = "${var.project_name}-${var.environment}-eip-nat-${count.index + 1}"
}
}
# NAT Gateways — permitem que as subnets privadas acessem a internet
# sem ficarem expostas a conexões de entrada
resource "aws_nat_gateway" "principal" {
count = length(var.availability_zones)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.publica[count.index].id
tags = {
Name = "${var.project_name}-${var.environment}-nat-${count.index + 1}"
}
depends_on = [aws_internet_gateway.principal]
}
# Tabela de rotas para subnets públicas
resource "aws_route_table" "publica" {
vpc_id = aws_vpc.principal.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.principal.id
}
tags = {
Name = "${var.project_name}-${var.environment}-rt-publica"
}
}
# Tabelas de rotas para subnets privadas
resource "aws_route_table" "privada" {
count = length(var.availability_zones)
vpc_id = aws_vpc.principal.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.principal[count.index].id
}
tags = {
Name = "${var.project_name}-${var.environment}-rt-privada-${count.index + 1}"
}
}
# Associações de tabelas de rotas — subnets públicas
resource "aws_route_table_association" "publica" {
count = length(var.availability_zones)
subnet_id = aws_subnet.publica[count.index].id
route_table_id = aws_route_table.publica.id
}
# Associações de tabelas de rotas — subnets privadas
resource "aws_route_table_association" "privada" {
count = length(var.availability_zones)
subnet_id = aws_subnet.privada[count.index].id
route_table_id = aws_route_table.privada[count.index].id
}
Grupos de Segurança
# security_groups.tf
# Security group para o load balancer — aceita tráfego da internet
resource "aws_security_group" "load_balancer" {
name = "${var.project_name}-${var.environment}-sg-alb"
description = "Security group do Application Load Balancer"
vpc_id = aws_vpc.principal.id
ingress {
description = "HTTP da internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS da internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-sg-alb"
}
}
# Security group para as instâncias de aplicação
# Aceita tráfego apenas do load balancer
resource "aws_security_group" "aplicacao" {
name = "${var.project_name}-${var.environment}-sg-app"
description = "Security group das instâncias de aplicação"
vpc_id = aws_vpc.principal.id
ingress {
description = "Tráfego do load balancer"
from_port = 3000
to_port = 3000
protocol = "tcp"
security_groups = [aws_security_group.load_balancer.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-sg-app"
}
}
# Security group para o banco de dados
# Aceita tráfego apenas das instâncias de aplicação
resource "aws_security_group" "banco_dados" {
name = "${var.project_name}-${var.environment}-sg-db"
description = "Security group do banco de dados RDS"
vpc_id = aws_vpc.principal.id
ingress {
description = "PostgreSQL das instâncias de aplicação"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.aplicacao.id]
}
tags = {
Name = "${var.project_name}-${var.environment}-sg-db"
}
}
Instâncias EC2 com Auto Scaling
Em vez de criar instâncias individuais, a prática recomendada para aplicações web é usar um Auto Scaling Group — um conjunto de instâncias que escala automaticamente com base na demanda:
# compute.tf
# IAM Role para as instâncias EC2
resource "aws_iam_role" "instancia_ec2" {
name = "${var.project_name}-${var.environment}-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}]
})
}
# Política para acesso ao SSM — permite acesso sem SSH exposto
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.instancia_ec2.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# Instance Profile — associa a Role à instância
resource "aws_iam_instance_profile" "aplicacao" {
name = "${var.project_name}-${var.environment}-instance-profile"
role = aws_iam_role.instancia_ec2.name
}
# Launch Template — modelo para criação de instâncias
resource "aws_launch_template" "aplicacao" {
name_prefix = "${var.project_name}-${var.environment}-"
image_id = data.aws_ami.ubuntu.id
instance_type = var.instance_type
iam_instance_profile {
arn = aws_iam_instance_profile.aplicacao.arn
}
network_interfaces {
associate_public_ip_address = false
security_groups = [aws_security_group.aplicacao.id]
}
block_device_mappings {
device_name = "/dev/sda1"
ebs {
volume_size = 20
volume_type = "gp3"
encrypted = true
delete_on_termination = true
}
}
user_data = base64encode(<<-EOF
#!/bin/bash
apt-get update -y
apt-get install -y docker.io docker-compose-plugin awscli
systemctl enable docker
systemctl start docker
usermod -aG docker ubuntu
# Baixa e inicia a aplicação
aws ecr get-login-password --region ${var.aws_region} | \
docker login --username AWS --password-stdin \
${data.aws_caller_identity.atual.account_id}.dkr.ecr.${var.aws_region}.amazonaws.com
docker run -d \
--name minha-api \
--restart unless-stopped \
-p 3000:3000 \
${var.app_image}
EOF
)
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.project_name}-${var.environment}-app"
}
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "aplicacao" {
name = "${var.project_name}-${var.environment}-asg"
vpc_zone_identifier = aws_subnet.privada[*].id
min_size = var.asg_min_size
max_size = var.asg_max_size
desired_capacity = var.asg_desired_capacity
launch_template {
id = aws_launch_template.aplicacao.id
version = "$Latest"
}
health_check_type = "ELB"
health_check_grace_period = 60
tag {
key = "Name"
value = "${var.project_name}-${var.environment}-app"
propagate_at_launch = true
}
lifecycle {
create_before_destroy = true
}
}
Bucket S3 com Configurações de Segurança
# storage.tf
resource "aws_s3_bucket" "assets" {
bucket = "${var.project_name}-${var.environment}-assets-${data.aws_caller_identity.atual.account_id}"
tags = {
Name = "${var.project_name}-${var.environment}-assets"
}
}
# Bloqueia todo acesso público — configuração de segurança fundamental
resource "aws_s3_bucket_public_access_block" "assets" {
bucket = aws_s3_bucket.assets.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Habilita versionamento
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration {
status = "Enabled"
}
}
# Habilita criptografia server-side
resource "aws_s3_bucket_server_side_encryption_configuration" "assets" {
bucket = aws_s3_bucket.assets.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Política de ciclo de vida — move objetos antigos para storage mais barato
resource "aws_s3_bucket_lifecycle_configuration" "assets" {
bucket = aws_s3_bucket.assets.id
rule {
id = "mover-para-ia"
status = "Enabled"
transition {
days = 30
storage_class = "STANDARD_IA"
}
transition {
days = 90
storage_class = "GLACIER"
}
expiration {
days = 365
}
}
}
Banco de Dados RDS
# database.tf
# Subnet group para o RDS — define em quais subnets o banco pode ficar
resource "aws_db_subnet_group" "principal" {
name = "${var.project_name}-${var.environment}-db-subnet-group"
subnet_ids = aws_subnet.privada[*].id
tags = {
Name = "${var.project_name}-${var.environment}-db-subnet-group"
}
}
# Parâmetros customizados do PostgreSQL
resource "aws_db_parameter_group" "postgres" {
name = "${var.project_name}-${var.environment}-pg16"
family = "postgres16"
parameter {
name = "log_connections"
value = "1"
}
parameter {
name = "log_disconnections"
value = "1"
}
parameter {
name = "log_min_duration_statement"
value = "1000" # loga queries acima de 1 segundo
}
}
# Instância RDS PostgreSQL
resource "aws_db_instance" "principal" {
identifier = "${var.project_name}-${var.environment}-db"
engine = "postgres"
engine_version = "16.1"
instance_class = var.db_instance_class
allocated_storage = 20
max_allocated_storage = 100 # auto scaling de storage até 100GB
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.principal.name
parameter_group_name = aws_db_parameter_group.postgres.name
vpc_security_group_ids = [aws_security_group.banco_dados.id]
multi_az = var.environment == "production"
publicly_accessible = false
deletion_protection = var.environment == "production"
skip_final_snapshot = var.environment != "production"
final_snapshot_identifier = var.environment == "production" ? \
"${var.project_name}-${var.environment}-final-snapshot" : null
backup_retention_period = var.environment == "production" ? 7 : 1
backup_window = "03:00-04:00"
maintenance_window = "Mon:04:00-Mon:05:00"
performance_insights_enabled = true
tags = {
Name = "${var.project_name}-${var.environment}-db"
}
}
Outputs Completos
# outputs.tf
output "vpc_id" {
description = "ID da VPC criada"
value = aws_vpc.principal.id
}
output "subnets_publicas" {
description = "IDs das subnets públicas"
value = aws_subnet.publica[*].id
}
output "subnets_privadas" {
description = "IDs das subnets privadas"
value = aws_subnet.privada[*].id
}
output "endpoint_rds" {
description = "Endpoint de conexão do banco de dados"
value = aws_db_instance.principal.endpoint
sensitive = true
}
output "bucket_assets" {
description = "Nome do bucket S3 de assets"
value = aws_s3_bucket.assets.id
}
output "database_url" {
description = "URL de conexão com o banco formatada"
value = "postgresql://${var.db_username}:${var.db_password}@${aws_db_instance.principal.endpoint}/${var.db_name}"
sensitive = true
}
O Que Vem a Seguir
Com a infraestrutura básica na AWS funcionando via Terraform, o próximo artigo aprofunda o tema de módulos — como organizar configurações reutilizáveis que podem ser compartilhadas entre projetos e ambientes. Módulos são o mecanismo que transforma código Terraform de scripts pontuais em uma biblioteca de infraestrutura corporativa.
Referências para Aprofundamento
Documentação e referências - Terraform AWS Provider — registry.terraform.io — Referência completa de todos os recursos AWS disponíveis no provider Terraform, com exemplos detalhados. - AWS VPC Documentation — Documentação oficial da AWS sobre VPC, cobrindo conceitos de subnets, route tables e security groups.
Tutoriais práticos - HashiCorp Learn — Build Infrastructure — Tutorial oficial da HashiCorp para provisionar infraestrutura na AWS com Terraform, com ambiente de laboratório incluso. - Terraform AWS Modules — GitHub — Coleção de módulos Terraform oficialmente mantidos para os serviços AWS mais comuns, incluindo VPC, EC2 e RDS. Excelente referência para boas práticas de organização.
Segurança - Checkov — Bridgecrew — Ferramenta de análise estática para código Terraform que verifica boas práticas de segurança. Pode ser integrada ao pipeline de CI para bloquear configurações inseguras antes do apply.
Artigo 26 de 52 · Módulo 5 — Infraestrutura como Código Prof. Ricardo Matos · Série Dominando DevOps & Cloud em 1 Ano
you asked
Continue