Introducción

El aprovisionamiento de infraestructura es uno de los pilares fundamentales del trabajo con Terraform. Antes de poder crear cualquier recurso en la nube, necesitas entender qué significa exactamente aprovisionar, cómo funciona el flujo de trabajo de Terraform y qué herramientas tienes disponibles para controlar ese proceso.

En este módulo aprenderás desde los conceptos más básicos hasta técnicas avanzadas como el aprovisionamiento parcial y la visualización del grafo de dependencias.


¿Qué Significa Aprovisionar Infraestructura?

Aprovisionar (o provisionar) infraestructura significa crear, configurar y preparar recursos de tecnología para que estén listos para su uso. Esto incluye:

  • Crear servidores virtuales
  • Configurar redes y subredes
  • Establecer reglas de seguridad
  • Crear bases de datos
  • Asignar almacenamiento
  • Configurar permisos y roles

Antes de herramientas como Terraform, este proceso era mayoritariamente manual: un administrador de sistemas iniciaba sesión en una consola web, hacía clic en varios menús y configuraba cada recurso de forma individual. Este enfoque tiene varios problemas:

  • No es reproducible: dos entornos configurados manualmente nunca son exactamente iguales
  • No está documentado: los cambios no quedan registrados automáticamente
  • Es propenso a errores humanos: un clic incorrecto puede tener consecuencias graves
  • No escala bien: gestionar cientos de recursos manualmente es inviable

Terraform resuelve todos estos problemas mediante la Infraestructura como Código (IaC).


Diferencia entre Aprovisionamiento Declarativo vs Imperativo

Existen dos grandes paradigmas para aprovisionar infraestructura:

Aprovisionamiento Imperativo

En el modelo imperativo, describes cómo llegar al estado deseado, paso a paso.

# Ejemplo imperativo (pseudocódigo de un script de shell)
aws ec2 create-vpc --cidr-block 10.0.0.0/16
VPC_ID=$(aws ec2 describe-vpcs --query "Vpcs[0].VpcId" --output text)
aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 10.0.1.0/24
SUBNET_ID=$(aws ec2 describe-subnets --query "Subnets[0].SubnetId" --output text)
aws ec2 run-instances --image-id ami-12345678 --subnet-id $SUBNET_ID

Problemas del enfoque imperativo:

  • Debes manejar el estado tú mismo
  • Si el script falla a mitad, puede dejar la infraestructura en un estado inconsistente
  • No puedes ejecutarlo dos veces sin modificarlo

Aprovisionamiento Declarativo

En el modelo declarativo, describes qué quieres que exista, y la herramienta determina cómo llegar ahí.

# Ejemplo declarativo con Terraform
resource "aws_vpc" "mi_vpc" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "mi_subred" {
  vpc_id     = aws_vpc.mi_vpc.id
  cidr_block = "10.0.1.0/24"
}

resource "aws_instance" "mi_servidor" {
  ami       = "ami-12345678"
  subnet_id = aws_subnet.mi_subred.id
}

Ventajas del enfoque declarativo:

  • Terraform maneja el estado automáticamente
  • Puedes ejecutar el mismo código múltiples veces sin problemas (idempotente)
  • Terraform calcula automáticamente el orden de creación de recursos

Tabla Comparativa

Característica Imperativo Declarativo (Terraform)
Defines Cómo hacerlo Qué quieres
Gestión del estado Manual Automática
Idempotencia Difícil de lograr Incorporada
Reproducibilidad Baja Alta
Curva de aprendizaje Más familiar al inicio Requiere nuevo paradigma
Ejemplos Scripts Bash, Ansible en modo push Terraform, CloudFormation

El Flujo de Trabajo de Aprovisionamiento con Terraform

El flujo de trabajo estándar de Terraform sigue estos pasos:

Escribir código HCL → terraform init → terraform plan → terraform apply → terraform destroy

Cada paso tiene un propósito específico:

  1. Escribir código HCL: Defines los recursos que quieres crear
  2. terraform init: Inicializa el directorio, descarga providers y módulos
  3. terraform plan: Muestra qué cambios se realizarán sin ejecutarlos
  4. terraform apply: Aplica los cambios al entorno real
  5. terraform destroy: Elimina todos los recursos gestionados

Planificación: terraform plan y Su Salida Explicada

terraform plan es uno de los comandos más importantes de Terraform. Analiza el código HCL, lo compara con el estado actual (archivo .tfstate) y genera un plan de ejecución detallado.

Ejecución básica

terraform plan

Guardar el plan en un archivo (recomendado en CI/CD)

# Guardar el plan para aplicarlo exactamente igual después
terraform plan -out=mi-plan.tfplan

# Luego aplicar ese plan exacto
terraform apply mi-plan.tfplan

Ejemplo de salida de terraform plan

Supongamos que tenemos este código:

# main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_bucket" "ejemplo" {
  bucket = "mi-bucket-terraform-ejemplo-2024"

  tags = {
    Environment = "desarrollo"
    Team        = "devops"
  }
}

Al ejecutar terraform plan, veremos una salida similar a esta:

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_s3_bucket.ejemplo will be created
  + resource "aws_s3_bucket" "ejemplo" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "mi-bucket-terraform-ejemplo-2024"
      + bucket_domain_name          = (known after apply)
      + force_destroy               = false
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "Environment" = "desarrollo"
          + "Team"        = "devops"
        }
      + tags_all                    = {
          + "Environment" = "desarrollo"
          + "Team"        = "devops"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Explicación de cada parte de la salida:

  • + create: El símbolo + indica que este recurso será creado
  • # aws_s3_bucket.ejemplo will be created: Identifica exactamente qué recurso se afectará
  • Los valores con (known after apply) son atributos que AWS asignará automáticamente y que Terraform no puede predecir antes de la creación
  • Plan: 1 to add, 0 to change, 0 to destroy: Resumen del plan

Tabla: Símbolos en el Plan de Terraform

Esta tabla es esencial para interpretar cualquier plan de Terraform:

Símbolo Significado Descripción
+ Create El recurso se creará por primera vez
- Destroy El recurso será eliminado
~ Update in-place El recurso se modificará sin recrearlo (solo cambian atributos)
-/+ Replace El recurso debe ser destruido y recreado (cambio en atributo inmutable)
<= Read Data source que será leído (no crea infraestructura)
! Tainted El recurso está marcado para ser recreado en el próximo apply

Ejemplo visual de cada símbolo

# Recurso nuevo (+)
  + resource "aws_instance" "web" {
      + ami = "ami-12345678"
    }

# Actualización in-place (~)
  ~ resource "aws_instance" "web" {
      ~ tags = {
          ~ "Name" = "servidor-v1" -> "servidor-v2"
        }
    }

# Destrucción y recreación (-/+)
-/+ resource "aws_instance" "web" {
      ~ ami = "ami-12345678" -> "ami-87654321" # forces replacement
    }

# Destrucción (-)
  - resource "aws_instance" "web" {
      - ami = "ami-12345678"
    }

Advertencia importante: Los recursos marcados con -/+ (replace) implican tiempo de inactividad. Un servidor EC2 marcado para reemplazo será destruido y recreado, lo que puede interrumpir el servicio. Siempre revisa cuidadosamente estos casos antes de aplicar.


Aplicación: terraform apply y Confirmación

Una vez revisado el plan, terraform apply ejecuta los cambios reales.

Ejecución interactiva

terraform apply

Terraform mostrará el plan completo y pedirá confirmación:

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Debes escribir exactamente yes (no y ni si). Cualquier otra respuesta cancela la operación.

Aplicación automática (sin confirmación)

Útil en pipelines de CI/CD:

terraform apply -auto-approve

Precaución: Usa -auto-approve solo en entornos automatizados y controlados. En entornos de producción, siempre revisa el plan manualmente antes de aplicar.

Aplicar un plan guardado

La forma más segura en producción: primero genera y revisa el plan, luego aplícalo exactamente:

# Paso 1: Generar y guardar el plan
terraform plan -out=produccion.tfplan

# Paso 2: Revisar el plan (puedes compartirlo con tu equipo)
terraform show produccion.tfplan

# Paso 3: Aplicar exactamente ese plan
terraform apply produccion.tfplan

Destrucción Controlada: terraform destroy

terraform destroy elimina todos los recursos gestionados por Terraform en el directorio actual.

terraform destroy

Al igual que apply, pedirá confirmación antes de ejecutarse:

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

Destrucción sin confirmación

terraform destroy -auto-approve

¿Cuándo usar terraform destroy?

  • Eliminar entornos de desarrollo o prueba después de usarlos
  • Limpiar infraestructura temporal de demos o experimentos
  • Reducir costos eliminando recursos no necesarios

Importante: terraform destroy no elimina el archivo de estado (.tfstate). Solo elimina la infraestructura real. El archivo de estado queda vacío después de la destrucción.


Aprovisionamiento Parcial: El Flag -target

En ocasiones, necesitas aplicar cambios solo a un recurso específico sin afectar el resto de la infraestructura. Para esto existe el flag -target.

Sintaxis

# Plan solo para un recurso específico
terraform plan -target=aws_instance.mi_servidor

# Apply solo para un recurso específico
terraform apply -target=aws_instance.mi_servidor

# Apply para un recurso dentro de un módulo
terraform apply -target=module.red.aws_vpc.principal

# Apply para múltiples recursos (múltiples flags -target)
terraform apply -target=aws_instance.web -target=aws_security_group.web_sg

Ejemplo práctico

Imagina que tienes este código y solo quieres crear el bucket S3 sin tocar el servidor EC2:

resource "aws_s3_bucket" "logs" {
  bucket = "mis-logs-aplicacion"
}

resource "aws_instance" "app" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"
}
# Solo crea el bucket S3
terraform apply -target=aws_s3_bucket.logs

Cuándo usar -target y cuándo evitarlo

Casos de uso válidos:

  • Resolver problemas de orden de creación cuando hay dependencias circulares
  • Recuperarse de un error en un recurso específico
  • Importar un recurso existente y luego gestionarlo gradualmente

Cuándo evitarlo:

  • Como práctica habitual (sugiere que el código tiene problemas de diseño)
  • Para omitir recursos "molestos" sistemáticamente
  • En lugar de refactorizar el código correctamente

Advertencia de Terraform: Cuando usas -target, Terraform te muestra esta advertencia: "Resource targeting is in effect. Note that the -target option is not suitable for routine use, and is provided only for exceptional circumstances." Esto es intencional: -target es una herramienta de emergencia, no una práctica regular.


Orden de Creación de Recursos: El Grafo de Dependencias

Terraform analiza automáticamente las dependencias entre recursos y los crea en el orden correcto. Existen dos tipos de dependencias:

Dependencias Implícitas

Se crean automáticamente cuando un recurso referencia el atributo de otro:

resource "aws_vpc" "principal" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "publica" {
  # Al referenciar aws_vpc.principal.id, Terraform sabe que debe
  # crear la VPC ANTES que la subred
  vpc_id     = aws_vpc.principal.id
  cidr_block = "10.0.1.0/24"
}

Dependencias Explícitas con depends_on

Cuando la dependencia no es evidente por referencias directas:

resource "aws_iam_role_policy" "politica" {
  name   = "mi-politica"
  role   = aws_iam_role.rol.id
  policy = data.aws_iam_policy_document.documento.json
}

resource "aws_instance" "app" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  # La instancia necesita que la política IAM exista primero,
  # aunque no la referencia directamente en sus atributos
  depends_on = [aws_iam_role_policy.politica]
}

terraform graph y Cómo Visualizarlo

terraform graph genera una representación del grafo de dependencias en formato DOT (lenguaje de Graphviz).

# Generar el grafo en formato DOT
terraform graph

# Guardar el grafo en un archivo
terraform graph > mi-infraestructura.dot

Visualizar el grafo con Graphviz

# Instalar Graphviz (Ubuntu/Debian)
sudo apt-get install graphviz

# Convertir el archivo DOT a imagen PNG
terraform graph | dot -Tpng > grafo.png

# Convertir a SVG (mejor calidad para zooms)
terraform graph | dot -Tsvg > grafo.svg

Visualización online

También puedes usar herramientas online como Blast Radius o simplemente pegar el contenido DOT en WebGraphviz.

Ejemplo de salida de terraform graph

digraph {
    compound = "true"
    newrank = "true"
    subgraph "root" {
        "[root] aws_instance.app (expand)" [label = "aws_instance.app", shape = "box"]
        "[root] aws_security_group.web (expand)" [label = "aws_security_group.web", shape = "box"]
        "[root] aws_vpc.principal (expand)" [label = "aws_vpc.principal", shape = "box"]
        "[root] aws_instance.app (expand)" -> "[root] aws_security_group.web (expand)"
        "[root] aws_security_group.web (expand)" -> "[root] aws_vpc.principal (expand)"
    }
}

Este grafo nos dice que:

  1. Primero se crea aws_vpc.principal
  2. Luego aws_security_group.web (depende de la VPC)
  3. Finalmente aws_instance.app (depende del Security Group)

Paralelismo en Terraform: El Flag -parallelism

Por defecto, Terraform crea hasta 10 recursos en paralelo cuando no hay dependencias entre ellos. Puedes ajustar este valor:

# Aumentar el paralelismo (más velocidad, más carga en la API)
terraform apply -parallelism=20

# Reducir el paralelismo (útil cuando la API tiene rate limiting)
terraform apply -parallelism=2

# Deshabilitar el paralelismo (1 recurso a la vez)
terraform apply -parallelism=1

Cuándo ajustar el paralelismo

Aumentar:

  • Tienes muchos recursos independientes y la aplicación tarda mucho
  • Tu proveedor de nube soporta alta concurrencia

Reducir:

  • Encuentras errores de "Rate limit exceeded" de la API del proveedor
  • Estás depurando un problema y quieres ver los recursos crearse uno a uno
  • Tienes recursos que, aunque Terraform no los vea como dependientes, en realidad sí lo son
# Ejemplo: AWS con muchos recursos y rate limiting
terraform apply -parallelism=3

Ejercicio Práctico: Interpretar un Plan de Terraform Complejo

Analiza el siguiente plan de Terraform y responde las preguntas al final:

Terraform will perform the following actions:

  # aws_instance.web_server will be created
  + resource "aws_instance" "web_server" {
      + ami                         = "ami-0c55b159cbfafe1f0"
      + instance_type               = "t3.micro"
      + subnet_id                   = (known after apply)
      + tags                        = {
          + "Environment" = "staging"
          + "Name"        = "web-server-staging"
        }
      + vpc_security_group_ids      = (known after apply)
    }

  # aws_security_group.web_sg will be created
  + resource "aws_security_group" "web_sg" {
      + description = "Security group para el servidor web"
      + name        = "web-sg-staging"
      + vpc_id      = (known after apply)
    }

  # aws_s3_bucket.logs will be destroyed
  - resource "aws_s3_bucket" "logs" {
      - bucket = "mi-app-logs-produccion"
      - tags   = {}
    }

  # aws_db_instance.database will be updated in-place
  ~ resource "aws_db_instance" "database" {
        id                   = "mi-base-datos"
      ~ backup_retention_period = 7 -> 14
        # (otros atributos sin cambios)
    }

  # aws_elasticache_cluster.cache must be replaced
-/+ resource "aws_elasticache_cluster" "cache" {
      ~ id                    = "mi-cache" -> (known after apply)
      ~ num_cache_nodes       = 1 -> 3
      + parameter_group_name  = "default.redis6.x" # forces replacement
    }

Plan: 2 to add, 1 to change, 1 to destroy.

Preguntas:

  1. ¿Cuántos recursos se van a crear?
  2. ¿Qué recurso se va a eliminar y por qué podría ser peligroso?
  3. ¿Qué cambio se hará en la base de datos?
  4. ¿Por qué el cluster de ElastiCache necesita ser reemplazado?
  5. ¿El resumen del plan es correcto? ¿Por qué o por qué no?

Solución con Análisis Detallado

Respuesta 1: Recursos a crear

Se crearán 2 recursos:

  • aws_instance.web_server: Un servidor EC2 con AMI ami-0c55b159cbfafe1f0 de tipo t3.micro
  • aws_security_group.web_sg: Un security group llamado web-sg-staging

Nota importante: Ambos tienen atributos con (known after apply). Por ejemplo, aws_instance.web_server tiene vpc_security_group_ids = (known after apply), lo que indica que este valor se asignará automáticamente por AWS o se tomará de otro recurso.

Respuesta 2: Recurso a eliminar

Se eliminará aws_s3_bucket.logs, el bucket S3 llamado mi-app-logs-produccion.

¿Por qué es peligroso? El nombre del bucket incluye "produccion" y podría contener logs importantes de la aplicación. Antes de destruir un bucket S3, deberías verificar:

  • ¿El bucket está vacío? (Terraform no puede destruir buckets con contenido por defecto)
  • ¿Los logs son necesarios para auditoría o cumplimiento legal?
  • ¿Por qué se eliminó este recurso del código HCL?

Respuesta 3: Cambio en la base de datos

La aws_db_instance.database se actualizará in-place (símbolo ~), lo que significa que la base de datos no se recreará. El único cambio es:

  • backup_retention_period: de 7 días → 14 días

Este es un cambio seguro y sin tiempo de inactividad en RDS.

Respuesta 4: Reemplazo del cluster ElastiCache

El cluster aws_elasticache_cluster.cache necesita ser reemplazado (símbolo -/+) porque se está cambiando el atributo parameter_group_name. En AWS ElastiCache, el grupo de parámetros es inmutable: no se puede cambiar en un cluster existente. Terraform debe destruir el cluster actual y crear uno nuevo.

Implicaciones:

  • El cluster estará temporalmente inaccesible (tiempo de inactividad)
  • Se pierden todos los datos en caché (normal para un caché)
  • num_cache_nodes cambia de 1 a 3, lo que indica que también se está escalando

Respuesta 5: ¿El resumen es correcto?

El resumen dice: Plan: 2 to add, 1 to change, 1 to destroy

Analicemos:

  • 2 to add: Correcto (web_server + web_sg)
  • 1 to change: Correcto (database actualización in-place)
  • 1 to destroy: ¡Incorrecto! El resumen cuenta solo 1 destrucción (el bucket S3), pero el reemplazo del cluster ElastiCache implica también una destrucción seguida de una creación.

En realidad, si contamos operaciones reales:

  • 3 to add: 2 nuevos + 1 por el reemplazo de ElastiCache
  • 1 to change: la database
  • 2 to destroy: el bucket S3 + el cluster ElastiCache existente

Terraform agrupa el reemplazo (-/+) como una sola operación en el resumen, pero en la ejecución real implica destrucción y creación.


Errores Comunes en el Aprovisionamiento

Error 1: Conflicto de nombre de recurso ya existente

Error: creating S3 Bucket (mi-bucket): BucketAlreadyExists: The requested bucket name is not available

Solución: Si el bucket ya existe en AWS pero no en el estado de Terraform, usa terraform import:

terraform import aws_s3_bucket.mi_bucket mi-bucket

Error 2: Dependencias circulares

Error: Cycle: aws_instance.app, aws_security_group.web

Solución: Revisa las referencias entre recursos y elimina la referencia circular. Usa depends_on solo cuando sea necesario.

Error 3: Permisos insuficientes

Error: error creating EC2 Instance: UnauthorizedOperation: You are not authorized to perform this operation.

Solución: Verifica que las credenciales de AWS tienen los permisos IAM necesarios. En desarrollo, el rol debe tener las políticas apropiadas.

Error 4: Intento de modificar un atributo inmutable

Error: modifying RDS DB Instance (mi-bd): InvalidParameterCombination:
Cannot modify the engine version to 8.0.28 (current version: 5.7.44)

Solución: Algunos atributos de recursos AWS no se pueden modificar en caliente. Terraform puede requerir recrear el recurso. Verifica la documentación del recurso para saber qué atributos son inmutables.

Error 5: Estado desincronizado

Error: Resource already exists

Solución: Alguien modificó la infraestructura manualmente fuera de Terraform. Usa terraform refresh para sincronizar el estado, o terraform import para importar recursos existentes.

# Actualizar el estado para que refleje la realidad
terraform refresh

Consejos Adicionales

  1. Siempre revisa el plan antes de aplicar: Nunca ejecutes terraform apply -auto-approve en producción sin haber revisado el plan previamente.

  2. Usa -out para planes importantes: Guardar el plan garantiza que aplicarás exactamente lo que revisaste, sin sorpresas.

  3. Entiende los valores (known after apply): No es un error, es Terraform diciéndote que ese valor será determinado por el proveedor en el momento de la creación.

  4. Los reemplazos son peligrosos en producción: Un -/+ en un servidor o base de datos implica tiempo de inactividad. Planifica mantenimientos para estos cambios.

  5. El grafo de dependencias es tu amigo: Cuando algo falla por orden incorrecto, visualiza el grafo para entender qué depende de qué.


Resumen y Próximos Pasos

En este módulo has aprendido los fundamentos del aprovisionamiento con Terraform:

  • La diferencia entre el modelo declarativo (Terraform) y el imperativo (scripts)
  • El flujo de trabajo completo: initplanapplydestroy
  • Cómo interpretar la salida de terraform plan y sus símbolos (+, -, ~, -/+)
  • El flag -target para aprovisionamiento parcial y cuándo usarlo
  • Cómo Terraform gestiona las dependencias mediante el grafo de recursos
  • La herramienta terraform graph para visualizar dependencias
  • El control del paralelismo con -parallelism
  • Los errores más comunes y cómo resolverlos

Con estos fundamentos sólidos, en los próximos módulos aplicarás estos conocimientos a proveedores de nube reales. Comenzaremos con AWS en el siguiente módulo, donde verás cómo configurar el provider, autenticarte y crear infraestructura real paso a paso.

Tarea de preparación: Antes del siguiente módulo, asegúrate de tener una cuenta de AWS con credenciales configuradas (aws configure) o variables de entorno configuradas. También instala la AWS CLI si no la tienes.

© Copyright 2026. Todos los derechos reservados