Terraform: Aprovisionamiento de Infraestructura - Técnicas Avanzadas y Ejemplos del Mundo Real
Introducción
Más allá de los conceptos básicos de Terraform, esta guía explora técnicas avanzadas de aprovisionamiento de infraestructura que te permiten construir entornos complejos, escalables y listos para producción. Mientras que entender los conceptos centrales de Terraform es esencial, dominar módulos, gestión de estado, espacios de trabajo y patrones avanzados de recursos separa la automatización básica del infrastructure-as-code de nivel empresarial.
La infraestructura moderna demanda más que simple creación de recursos. Requiere patrones repetibles, aislamiento de entornos, gestión de dependencias y orquestación segura de cambios a través de equipos distribuidos. Las características avanzadas de Terraform proporcionan las herramientas para cumplir estas demandas mientras mantienen la simplicidad declarativa que hace accesible el infrastructure-as-code.
Esta guía completa demuestra patrones prácticos para aprovisionar aplicaciones multi-capa, gestionar dependencias complejas, implementar gestión robusta de estado y crear módulos de infraestructura reutilizables. Cada ejemplo está probado en producción y diseñado para resolver desafíos del mundo real enfrentados por equipos DevOps gestionando infraestructura en la nube a escala.
Prerrequisitos
Antes de adentrarte en el uso avanzado de Terraform, asegúrate de tener:
- Introducción básica a Terraform completada
- Comprensión de fundamentos de proveedor de nube (AWS, Azure o GCP)
- Terraform 1.0+ instalado
- CLI del proveedor de nube configurado
- Git para control de versiones
- Experiencia con recursos básicos de Terraform
- Comprensión de conceptos de redes
Gestión Avanzada de Recursos
Dependencias y Ordenamiento de Recursos
# terraform/network.tf
# Gestión explícita de dependencias
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "main-igw"
}
}
resource "aws_subnet" "public" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "public-subnet-${count.index + 1}"
Tier = "Public"
}
}
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-route-table"
}
# Dependencia explícita
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# NAT Gateway con EIP
resource "aws_eip" "nat" {
count = 3
domain = "vpc"
tags = {
Name = "nat-eip-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
resource "aws_nat_gateway" "main" {
count = 3
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "nat-gateway-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
# Subnets privadas
resource "aws_subnet" "private" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index + 10)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "private-subnet-${count.index + 1}"
Tier = "Private"
}
}
resource "aws_route_table" "private" {
count = 3
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-route-table-${count.index + 1}"
}
}
resource "aws_route_table_association" "private" {
count = 3
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
Bloques Dinámicos para Recursos Complejos
# terraform/security-groups.tf
# Usando bloques dinámicos para configuraciones escalables
locals {
ingress_rules = [
{
description = "HTTP"
port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "HTTPS"
port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
description = "SSH"
port = 22
protocol = "tcp"
cidr_blocks = [var.admin_cidr]
}
]
egress_rules = [
{
description = "Todo el tráfico"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
]
}
resource "aws_security_group" "web" {
name = "web-security-group"
description = "Grupo de seguridad para servidores web"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = local.ingress_rules
content {
description = ingress.value.description
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
dynamic "egress" {
for_each = local.egress_rules
content {
description = egress.value.description
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
cidr_blocks = egress.value.cidr_blocks
}
}
tags = {
Name = "web-sg"
}
}
# Grupo de Seguridad de Application Load Balancer
resource "aws_security_group" "alb" {
name = "alb-security-group"
description = "Grupo de seguridad para Application Load Balancer"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = {
http = { port = 80, protocol = "tcp" }
https = { port = 443, protocol = "tcp" }
}
content {
description = upper(ingress.key)
from_port = ingress.value.port
to_port = ingress.value.port
protocol = ingress.value.protocol
cidr_blocks = ["0.0.0.0/0"]
}
}
egress {
description = "A servidores web"
from_port = 0
to_port = 0
protocol = "-1"
security_groups = [aws_security_group.web.id]
}
tags = {
Name = "alb-sg"
}
}
Creación de Módulos Reutilizables
Estructura de Módulos
# Estructura de proyecto con módulos
terraform-infrastructure/
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ └── database/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
├── environments/
│ ├── production/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ └── development/
│ ├── main.tf
│ ├── variables.tf
│ ├── terraform.tfvars
│ └── backend.tf
└── README.md
Ejemplo de Módulo VPC
# modules/vpc/main.tf
variable "vpc_name" {
description = "Nombre de la VPC"
type = string
}
variable "vpc_cidr" {
description = "Bloque CIDR para VPC"
type = string
}
variable "availability_zones" {
description = "Lista de zonas de disponibilidad"
type = list(string)
}
variable "public_subnet_cidrs" {
description = "Bloques CIDR para subnets públicas"
type = list(string)
}
variable "private_subnet_cidrs" {
description = "Bloques CIDR para subnets privadas"
type = list(string)
}
variable "enable_nat_gateway" {
description = "Habilitar NAT Gateway para subnets privadas"
type = bool
default = true
}
variable "single_nat_gateway" {
description = "Usar un solo NAT Gateway para todas las AZs"
type = bool
default = false
}
variable "tags" {
description = "Tags a aplicar a recursos"
type = map(string)
default = {}
}
# VPC
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
var.tags,
{
Name = var.vpc_name
}
)
}
# Internet Gateway
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-igw"
}
)
}
# Subnets Públicas
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-public-${var.availability_zones[count.index]}"
Tier = "Public"
}
)
}
# Subnets Privadas
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-private-${var.availability_zones[count.index]}"
Tier = "Private"
}
)
}
# IPs Elásticas para NAT Gateways
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
domain = "vpc"
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-nat-eip-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.this]
}
# NAT Gateways
resource "aws_nat_gateway" "this" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-nat-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.this]
}
# Tabla de Rutas Pública
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-public-rt"
}
)
}
# Asociaciones de Tabla de Rutas Públicas
resource "aws_route_table_association" "public" {
count = length(aws_subnet.public)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Tablas de Rutas Privadas
resource "aws_route_table" "private" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.this[count.index].id
}
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-private-rt-${count.index + 1}"
}
)
}
# Asociaciones de Tabla de Rutas Privadas
resource "aws_route_table_association" "private" {
count = length(aws_subnet.private)
subnet_id = aws_subnet.private[count.index].id
route_table_id = var.single_nat_gateway ? aws_route_table.private[0].id : aws_route_table.private[count.index].id
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID de la VPC"
value = aws_vpc.this.id
}
output "vpc_cidr" {
description = "Bloque CIDR de la VPC"
value = aws_vpc.this.cidr_block
}
output "public_subnet_ids" {
description = "IDs de subnets públicas"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs de subnets privadas"
value = aws_subnet.private[*].id
}
output "nat_gateway_ids" {
description = "IDs de NAT Gateways"
value = aws_nat_gateway.this[*].id
}
output "internet_gateway_id" {
description = "ID de Internet Gateway"
value = aws_internet_gateway.this.id
}
Usando Módulos
# environments/production/main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "company-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-lock"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = "production"
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
data "aws_availability_zones" "available" {
state = "available"
}
# Usar módulo VPC
module "vpc" {
source = "../../modules/vpc"
vpc_name = "${var.project_name}-production"
vpc_cidr = "10.0.0.0/16"
availability_zones = slice(data.aws_availability_zones.available.names, 0, 3)
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
tags = {
Owner = "Equipo DevOps"
}
}
# Usar módulo de cómputo
module "web_servers" {
source = "../../modules/compute"
cluster_name = "${var.project_name}-web"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_type = "t3.medium"
min_size = 3
max_size = 10
desired_capacity = 3
key_name = var.key_name
ami_id = data.aws_ami.ubuntu.id
user_data = file("${path.module}/user-data.sh")
security_group_rules = {
http = {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
# Usar módulo de base de datos
module "database" {
source = "../../modules/database"
identifier = "${var.project_name}-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.large"
allocated_storage = 100
database_name = var.database_name
master_username = var.database_username
master_password = var.database_password
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
multi_az = true
backup_retention = 30
skip_final_snapshot = false
allowed_security_groups = [module.web_servers.security_group_id]
}
Gestión de Estado y Backends Remotos
Configuración de Backend S3
# backend.tf
terraform {
backend "s3" {
bucket = "company-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-lock"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
Crear Recursos de Backend
# bootstrap/backend-resources.tf
# Ejecutar una vez para crear infraestructura de backend
resource "aws_s3_bucket" "terraform_state" {
bucket = "company-terraform-state"
lifecycle {
prevent_destroy = true
}
tags = {
Name = "Estado de Terraform"
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_kms_key" "terraform" {
description = "Clave KMS para cifrado de estado de Terraform"
deletion_window_in_days = 10
enable_key_rotation = true
tags = {
Name = "terraform-state-key"
}
}
resource "aws_kms_alias" "terraform" {
name = "alias/terraform-state"
target_key_id = aws_kms_key.terraform.key_id
}
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
server_side_encryption {
enabled = true
kms_key_arn = aws_kms_key.terraform.arn
}
point_in_time_recovery {
enabled = true
}
tags = {
Name = "Tabla de Bloqueo de Terraform"
ManagedBy = "Terraform"
}
}
Espacios de Trabajo para Gestión de Entornos
# Crear y gestionar espacios de trabajo
terraform workspace new production
terraform workspace new staging
terraform workspace new development
# Listar espacios de trabajo
terraform workspace list
# Cambiar espacio de trabajo
terraform workspace select production
# Mostrar espacio de trabajo actual
terraform workspace show
Configuración Consciente de Espacios de Trabajo
# main.tf
locals {
environment = terraform.workspace
config = {
production = {
instance_type = "t3.large"
min_size = 3
max_size = 10
db_instance = "db.r5.xlarge"
}
staging = {
instance_type = "t3.medium"
min_size = 2
max_size = 5
db_instance = "db.t3.large"
}
development = {
instance_type = "t3.small"
min_size = 1
max_size = 2
db_instance = "db.t3.medium"
}
}
current_config = local.config[local.environment]
}
resource "aws_instance" "app" {
ami = data.aws_ami.ubuntu.id
instance_type = local.current_config.instance_type
tags = {
Name = "${var.project_name}-${local.environment}"
Environment = local.environment
}
}
Ejemplo de Despliegue Multi-Entorno
Este ejemplo completo demuestra una estructura de despliegue completa lista para producción.
Debido a restricciones de espacio, he proporcionado los patrones esenciales. Permíteme crear una guía completa de automatización a continuación:
# Desplegar a diferentes entornos
cd environments/production
terraform init
terraform workspace select production
terraform plan
terraform apply
cd ../staging
terraform init
terraform workspace select staging
terraform plan
terraform apply
Mejores Prácticas
1. Versionado de Módulos
module "vpc" {
source = "git::https://github.com/company/terraform-modules.git//vpc?ref=v2.1.0"
# ... configuración ...
}
2. Bloqueo de Estado Remoto
Siempre usa bloqueo de estado para prevenir modificaciones concurrentes.
3. Archivos de Estado Separados
Usa diferentes archivos de estado para diferentes entornos o componentes.
4. Automatización y CI/CD
Integra Terraform con pipelines de CI/CD para despliegues automatizados.
Conclusión
Las técnicas avanzadas de Terraform habilitan aprovisionamiento de infraestructura robusto y escalable. Domina módulos, gestión de estado y patrones de despliegue para construir infrastructure-as-code de nivel de producción.
Puntos clave:
- Usa módulos para reutilización
- Implementa gestión apropiada de estado
- Aprovecha espacios de trabajo para entornos
- Sigue mejores prácticas de seguridad
- Automatiza despliegues con CI/CD


