Provisiona una VPC de AWS con Terraform
Como usar Terraform para provisionar una VPC de AWS lista para produccion con subredes publicas y privadas, NAT gateways y security groups
Una VPC bien estructurada es la fundacion de infraestructura cloud segura. Terraform te permite definir toda la topologia de red — subredes, ruteo, gateways y security groups — como codigo versionado que puede recrearse identicamente entre ambientes.
Cuando Usar Esto
- Necesitas infraestructura de red repetible y versionada entre dev/staging/prod. Consulta Git Workflow para infraestructura versionada.
- Las aplicaciones requieren recursos tanto publicos como internos. Consulta AWS ECS Fargate para despliegue de contenedores.
- Quieres enforcear segmentacion de red entre diferentes capas. Consulta Load Balancing HAProxy para separación de capas.
Requisitos Previos
- AWS CLI configurado con credenciales apropiadas
- Terraform 1.5+ instalado
Solucion
1. VPC y Subredes
# vpc.tf
locals {
azs = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "production-vpc"
}
}
resource "aws_subnet" "public" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = local.azs[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index + 1}"
Type = "public"
}
}
resource "aws_subnet" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 100)
availability_zone = local.azs[count.index]
tags = {
Name = "private-subnet-${count.index + 1}"
Type = "private"
}
}
2. Gateways de Internet y NAT
# gateways.tf
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "main-igw" }
}
resource "aws_eip" "nat" {
count = length(local.azs)
domain = "vpc"
tags = { Name = "nat-eip-${count.index + 1}" }
}
resource "aws_nat_gateway" "main" {
count = length(local.azs)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = { Name = "nat-gateway-${count.index + 1}" }
}
3. Tablas de Ruteo
# routing.tf
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "public-rt" }
}
resource "aws_route_table_association" "public" {
count = length(local.azs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
count = length(local.azs)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = { Name = "private-rt-${count.index + 1}" }
}
resource "aws_route_table_association" "private" {
count = length(local.azs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
4. Security Groups
# security.tf
resource "aws_security_group" "web" {
name_prefix = "web-"
vpc_id = aws_vpc.main.id
description = "Permitir trafico HTTP y HTTPS"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
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 = "web-sg" }
}
resource "aws_security_group" "database" {
name_prefix = "db-"
vpc_id = aws_vpc.main.id
description = "Permitir acceso a base de datos solo desde capa web"
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
tags = { Name = "database-sg" }
}
Como Funciona
- VPC define el espacio de direcciones IP privadas para todos los recursos
- Subredes Publicas rutean trafico a traves del Internet Gateway para acceso externo
- Subredes Privadas rutean trafico saliente a traves de NAT Gateways por seguridad
- Tablas de Ruteo controlan direccion de trafico por subred
- Security Groups actuan como firewalls stateful a nivel de instancia
Consideraciones de Produccion
- Usa Terraform workspaces o archivos de estado separados para cada ambiente
- Habilita VPC Flow Logs a CloudWatch para auditoria de trafico de red
- Coloca bases de datos y servicios internos solo en subredes privadas
- Usa AWS Network Firewall o Security Groups para defensa en profundidad
Errores Comunes
- Olvidar habilitar
map_public_ip_on_launchpara instancias en subredes publicas - Colocar NAT Gateways en subredes privadas en lugar de publicas
- Usar bloques CIDR demasiado permisivos como
0.0.0.0/0en ingress de security groups
Tips de Rendimiento
- Usa Gateway endpoints para S3 y DynamoDB. Sin charge por GB, a diferencia de Interface endpoints:
# Gateway endpoint: gratis, sin costo por GB
vpc_endpoint_type = "Gateway"
# Interface endpoint: $7/mes + por GB
vpc_endpoint_type = "Interface"
- Coloca NAT Gateways en cada AZ. Trafico NAT cross-AZ incursa costos dobles de transferencia:
# Un NAT por AZ evita cargos cross-AZ
count = length(var.availability_zones)
- Usa
aws_ec2_transit_gatewaypara multi-VPC. Peering escala como O(n²) conexiones; Transit Gateway es O(n):
resource "aws_ec2_transit_gateway" "main" {
description = "Platform transit gateway"
tags = local.common_tags
} Preguntas frecuentes
5. VPC Flow Logs
# flow_logs.tf
resource "aws_cloudwatch_log_group" "vpc_flow" {
name = "/aws/vpc/flow-logs"
retention_in_days = 30
}
resource "aws_iam_role" "vpc_flow" {
name = "vpc-flow-logs-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "vpc-flow-logs.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy" "vpc_flow" {
name = "vpc-flow-logs-policy"
role = aws_iam_role.vpc_flow.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups"
]
Resource = "*"
}]
})
}
resource "aws_flow_log" "main" {
vpc_id = aws_vpc.main.id
iam_role_arn = aws_iam_role.vpc_flow.arn
log_destination = aws_cloudwatch_log_group.vpc_flow.arn
log_destination_type = "cloud-watch-logs"
traffic_type = "ALL"
}
6. VPC Endpoints para Servicios AWS
# endpoints.tf
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [for rt in aws_route_table.private : rt.id]
}
resource "aws_vpc_endpoint" "secrets_manager" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.us-east-1.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = [for s in aws_subnet.private : s.id]
security_group_ids = [aws_security_group.database.id]
private_dns_enabled = true
}
7. Network ACLs para Defensa en Profundidad
# nacls.tf
resource "aws_network_acl" "public" {
vpc_id = aws_vpc.main.id
subnet_ids = [for s in aws_subnet.public : s.id]
ingress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
ingress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
egress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
}
8. Variables para Reusabilidad
# variables.tf
variable "environment" {
type = string
default = "production"
}
variable "cidr_block" {
type = string
default = "10.0.0.0/16"
}
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
variable "single_nat_gateway" {
type = bool
default = false
}
# NAT condicional: uno por AZ para prod, uno solo para dev
resource "aws_nat_gateway" "main" {
count = var.single_nat_gateway ? 1 : length(var.availability_zones)
allocation_id = var.single_nat_gateway ? aws_eip.nat[0].id : aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
}
Recursos Relacionados
Desplegar Contenedores en AWS ECS con Fargate
Como desplegar contenedores Docker en AWS ECS usando computacion serverless Fargate con Terraform y GitHub Actions
GuideInfrastructure as Code — Terraform y Pulumi
Guía práctica para gestionar infraestructura como código: beneficios de enfoques declarativo vs imperativo, manejo de estado, módulos y testing de cambios de infraestructura.
PatternBuilder Pattern para Objetos de Configuracion Complejos
Usa el Builder pattern para construir objetos de configuracion complejos con parametros opcionales y valores por defecto sensatos sin constructores telescopicos
RecipeLoad Balancing con HAProxy y Health Checks
Configura HAProxy como load balancer de alto rendimiento con health checks activos, sticky sessions y SSL termination para distribucion resiliente de servicios
RecipeConfigura Nginx como Reverse Proxy y API Gateway
Como usar Nginx como reverse proxy para servicios backend, implementar balanceo de carga, terminacion SSL y rate limiting para API gateways de produccion
RecipePlaybook de Ansible para Configuración de Servidores
Cómo escribir y ejecutar playbooks de Ansible para provisionar, configurar y gestionar servidores con tareas idempotentes, roles y archivos de inventario.