Introducción
Las variables y los outputs son el mecanismo que hace que las configuraciones de Terraform sean reutilizables y parametrizables. En lugar de escribir valores fijos directamente en el código (lo que se conoce como "hardcoding"), las variables permiten que el mismo código funcione en diferentes entornos con distintas configuraciones. Los outputs, por su parte, exponen información sobre los recursos creados para que pueda ser consultada o utilizada por otros módulos.
Variables de entrada (Input Variables)
¿Qué son las variables de entrada?
Las variables de entrada son parámetros que puedes pasar a tu configuración de Terraform desde el exterior. Son similares a los argumentos de una función en programación.
Sintaxis básica
variable "nombre_variable" {
type = tipo_de_dato # (opcional) tipo esperado
description = "Descripción" # (recomendado) qué hace esta variable
default = valor_por_defecto # (opcional) valor si no se especifica
}Ejemplo básico
# Declaración de una variable simple
variable "region_aws" {
type = string
description = "La región de AWS donde se crearán los recursos"
default = "us-east-1"
}
# Uso de la variable en un bloque provider
provider "aws" {
region = var.region_aws # Accedemos con var.nombre_variable
}Variables requeridas vs opcionales
Una variable sin valor por defecto es requerida: Terraform la pedirá si no se proporciona.
# Variable REQUERIDA: no tiene default, Terraform la pedirá obligatoriamente
variable "nombre_proyecto" {
type = string
description = "Nombre del proyecto. Es obligatorio especificarlo."
# Sin default -> el usuario DEBE proporcionarlo
}
# Variable OPCIONAL: tiene un default razonable
variable "entorno" {
type = string
description = "Entorno de despliegue"
default = "desarrollo" # Si no se especifica, se usa "desarrollo"
}Tipos de variables con tabla comparativa
| Tipo | Sintaxis | Ejemplo de valor | Uso típico |
|---|---|---|---|
string |
type = string |
"us-east-1" |
Nombres, IDs, regiones |
number |
type = number |
3 o 2.5 |
Cantidades, puertos, tamaños |
bool |
type = bool |
true o false |
Flags de activación/desactivación |
list(string) |
type = list(string) |
["a", "b"] |
Listas de zonas, CIDRs, nombres |
list(number) |
type = list(number) |
[80, 443, 8080] |
Listas de puertos |
map(string) |
type = map(string) |
{k = "v"} |
Etiquetas (tags), configuraciones clave-valor |
set(string) |
type = set(string) |
["a", "b"] |
Como list pero sin duplicados ni orden |
object({...}) |
type = object({a=string}) |
{a="valor"} |
Estructuras de configuración complejas |
tuple([...]) |
type = tuple([string, number]) |
["a", 1] |
Raramente usado directamente |
any |
type = any |
Cualquiera | Cuando no quieres restringir el tipo |
Ejemplos completos de cada tipo
# STRING: nombre o identificador de texto
variable "nombre_vpc" {
type = string
description = "Nombre de la VPC principal"
default = "vpc-produccion"
}
# NUMBER: cantidad numérica
variable "max_instancias" {
type = number
description = "Número máximo de instancias en el autoscaling group"
default = 10
}
# BOOL: activar o desactivar funcionalidades
variable "habilitar_cifrado" {
type = bool
description = "Si true, habilita el cifrado en reposo para los volúmenes"
default = true
}
# LIST(STRING): múltiples valores de texto
variable "zonas_disponibilidad" {
type = list(string)
description = "Zonas de disponibilidad donde desplegar subnets"
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
# LIST(NUMBER): múltiples valores numéricos
variable "puertos_entrada" {
type = list(number)
description = "Puertos que se permitirán en el security group de entrada"
default = [80, 443, 8080]
}
# MAP(STRING): pares clave-valor de texto
variable "etiquetas" {
type = map(string)
description = "Etiquetas (tags) que se aplicarán a todos los recursos"
default = {
Entorno = "produccion"
Equipo = "infraestructura"
Proyecto = "tienda-online"
}
}
# OBJECT: estructura con múltiples campos tipados
variable "configuracion_rds" {
type = object({
clase_instancia = string # ej: "db.t3.medium"
motor = string # ej: "mysql", "postgres"
version_motor = string # ej: "8.0", "13.4"
almacenamiento_gb = number # ej: 100
multiaz = bool # true = alta disponibilidad
backup_dias = number # días que se guardan los backups
})
description = "Configuración completa de la base de datos RDS"
default = {
clase_instancia = "db.t3.micro"
motor = "mysql"
version_motor = "8.0"
almacenamiento_gb = 20
multiaz = false
backup_dias = 7
}
}
# Uso de las variables definidas arriba
resource "aws_db_instance" "principal" {
# Accedemos a los campos del objeto con la notación de punto
instance_class = var.configuracion_rds.clase_instancia
engine = var.configuracion_rds.motor
engine_version = var.configuracion_rds.version_motor
allocated_storage = var.configuracion_rds.almacenamiento_gb
multi_az = var.configuracion_rds.multiaz
backup_retention_period = var.configuracion_rds.backup_dias
storage_encrypted = var.habilitar_cifrado # Usamos el bool directamente
tags = var.etiquetas # Pasamos el mapa directamente como tags
}Valores por defecto y variables requeridas
Cuándo usar cada opción
# SIEMPRE ponle default cuando tenga un valor razonable para la mayoría de casos
variable "tipo_instancia" {
type = string
default = "t3.micro" # Un valor que funciona para desarrollo por defecto
}
# NO pongas default cuando el valor es específico del despliegue
variable "id_cuenta_aws" {
type = string
description = "ID de la cuenta de AWS (12 dígitos). Obligatorio."
# Sin default: cada equipo tiene su propia cuenta
}
# CUIDADO con defaults que parecen seguros pero no lo son
variable "cidr_acceso_ssh" {
type = string
description = "CIDR desde donde se permite SSH"
# Evita poner "0.0.0.0/0" como default aunque sea tentador
# No default -> el usuario debe elegir conscientemente
}Archivo terraform.tfvars y .auto.tfvars
Los archivos .tfvars permiten definir los valores de las variables sin pasarlos en la línea de comandos.
terraform.tfvars
# Archivo: terraform.tfvars
# Terraform lo carga automáticamente si existe en el directorio
nombre_proyecto = "mi-tienda-online"
entorno = "produccion"
region_aws = "eu-west-1"
max_instancias = 20
habilitar_cifrado = true
zonas_disponibilidad = [
"eu-west-1a",
"eu-west-1b",
"eu-west-1c"
]
etiquetas = {
Entorno = "produccion"
Equipo = "devops"
Proyecto = "tienda"
CostCenter = "TI-001"
}
configuracion_rds = {
clase_instancia = "db.t3.large"
motor = "postgres"
version_motor = "14.5"
almacenamiento_gb = 200
multiaz = true
backup_dias = 14
}Múltiples archivos .tfvars para distintos entornos
Es una práctica común tener un archivo por entorno:
# Estructura de archivos recomendada
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars # Valores por defecto (desarrollo)
├── entornos/
│ ├── desarrollo.tfvars # Valores para desarrollo
│ ├── staging.tfvars # Valores para staging
│ └── produccion.tfvars # Valores para producción# Archivo: entornos/produccion.tfvars
entorno = "produccion"
max_instancias = 50
habilitar_cifrado = true
configuracion_rds = {
clase_instancia = "db.r5.xlarge"
motor = "postgres"
version_motor = "14.5"
almacenamiento_gb = 500
multiaz = true
backup_dias = 30
}# Aplicar con el archivo de producción específico
terraform apply -var-file="entornos/produccion.tfvars"
# Puedes combinar múltiples archivos (el último sobreescribe)
terraform apply \
-var-file="terraform.tfvars" \
-var-file="entornos/produccion.tfvars"Archivos .auto.tfvars
Los archivos con extensión .auto.tfvars se cargan automáticamente en orden alfabético, igual que terraform.tfvars:
# Estos archivos se cargan AUTOMÁTICAMENTE sin necesidad de -var-file
produccion.auto.tfvars
secretos.auto.tfvars # Útil para credenciales (¡no subas esto a git!)Variables de entorno TF_VAR_*
Terraform también puede leer variables desde variables de entorno del sistema operativo. Son útiles para credenciales y secretos en pipelines de CI/CD.
# Las variables de entorno siguen el patrón TF_VAR_nombre_variable
# Todo en minúsculas
# Ejemplo: establecer la variable "nombre_proyecto"
export TF_VAR_nombre_proyecto="mi-proyecto-cicd"
# Ejemplo: establecer la variable "max_instancias"
export TF_VAR_max_instancias=5
# Ejemplo: para tipos complejos, usar sintaxis HCL entre comillas
export TF_VAR_etiquetas='{ Entorno = "produccion", Equipo = "devops" }'
# Luego ejecutar terraform normalmente
terraform plan
terraform applyPrioridad de fuentes de variables
Terraform evalúa las variables en este orden (de menor a mayor prioridad):
| Prioridad | Fuente | Descripción |
|---|---|---|
| 1 (menor) | Valores por defecto en variable {} |
El default en el código |
| 2 | terraform.tfvars |
Archivo de variables principal |
| 3 | *.auto.tfvars |
Archivos de variables automáticos (orden alfabético) |
| 4 | -var-file="archivo.tfvars" |
Archivo especificado en línea de comandos |
| 5 | -var "nombre=valor" |
Variable especificada en línea de comandos |
| 6 (mayor) | Variables de entorno TF_VAR_* |
Variables del sistema operativo |
Validación de variables (validation block)
El bloque validation permite definir reglas personalizadas que los valores deben cumplir. Si la condición no se cumple, Terraform muestra el mensaje de error y detiene la ejecución.
variable "entorno" {
type = string
description = "Entorno de despliegue"
validation {
# La condición debe ser verdadera para que la validación pase
# contains() verifica si el valor está en la lista
condition = contains(["desarrollo", "staging", "produccion"], var.entorno)
error_message = "El entorno debe ser uno de: desarrollo, staging, produccion."
}
}
variable "puerto_aplicacion" {
type = number
description = "Puerto donde escucha la aplicación"
default = 8080
validation {
# Los puertos válidos son del 1024 al 65535 (puertos no privilegiados)
condition = var.puerto_aplicacion >= 1024 && var.puerto_aplicacion <= 65535
error_message = "El puerto debe estar entre 1024 y 65535."
}
}
variable "nombre_bucket_s3" {
type = string
description = "Nombre del bucket S3 (solo minúsculas, números y guiones)"
validation {
# regex() devuelve error si no coincide; can() captura ese error y devuelve false
# El patrón regex verifica el formato correcto de nombre de bucket
condition = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.nombre_bucket_s3))
error_message = "El nombre del bucket debe tener entre 3 y 63 caracteres, solo minúsculas, números y guiones, y no puede empezar ni terminar con guión."
}
}
variable "cidr_vpc" {
type = string
description = "Bloque CIDR para la VPC"
default = "10.0.0.0/16"
validation {
# Verificamos que sea un CIDR IPv4 válido
condition = can(cidrnetmask(var.cidr_vpc))
error_message = "El valor debe ser un bloque CIDR IPv4 válido, por ejemplo 10.0.0.0/16."
}
}
variable "num_replicas" {
type = number
description = "Número de réplicas de la aplicación"
default = 2
# Podemos tener múltiples bloques validation
validation {
condition = var.num_replicas >= 1
error_message = "Debe haber al menos 1 réplica."
}
validation {
condition = var.num_replicas <= 100
error_message = "El número máximo de réplicas es 100."
}
}Variables de salida (Outputs)
¿Qué son los outputs?
Los outputs son valores que Terraform muestra al finalizar apply y que pueden ser consultados posteriormente con terraform output. También son el mecanismo principal para pasar información entre módulos.
Sintaxis básica
output "nombre_output" {
description = "Descripción de qué valor se exporta"
value = expresión_del_valor
}Ejemplos de outputs
# Output simple: ID de un recurso
output "id_vpc" {
description = "El ID único de la VPC creada"
value = aws_vpc.principal.id
# Terraform mostrará esto al final del apply:
# id_vpc = "vpc-0abc123def456"
}
# Output de múltiples atributos usando un objeto
output "info_servidor_web" {
description = "Información completa del servidor web"
value = {
id = aws_instance.web.id
ip_publica = aws_instance.web.public_ip
ip_privada = aws_instance.web.private_ip
dns_publico = aws_instance.web.public_dns
}
}
# Output de una lista (cuando se usa count)
output "ips_servidores" {
description = "IPs públicas de todos los servidores creados"
# [*] obtiene el atributo de todos los elementos del count
value = aws_instance.web[*].public_ip
# Ejemplo resultado: ["1.2.3.4", "5.6.7.8", "9.10.11.12"]
}
# Output de un mapa para cuando se usa for_each
output "ids_subnets" {
description = "Mapa de nombre de subnet a su ID"
# for_each crea un mapa, accedemos a todos los values con [*]
value = { for k, v in aws_subnet.publica : k => v.id }
# Ejemplo resultado: { "subnet-a" = "subnet-001", "subnet-b" = "subnet-002" }
}
# Output calculado con una expresión
output "url_aplicacion" {
description = "URL completa para acceder a la aplicación"
# Construimos la URL a partir del DNS público del balanceador
value = "https://${aws_lb.principal.dns_name}"
}
# Output con condición
output "ip_publica_o_mensaje" {
description = "IP pública si existe, o mensaje indicando que es privado"
value = aws_instance.app.public_ip != "" ? aws_instance.app.public_ip : "Sin IP pública (instancia privada)"
}Outputs sensibles (sensitive = true)
Cuando un output contiene información confidencial (contraseñas, tokens, claves), marca el output como sensitive = true. Terraform ocultará el valor en los logs y en la salida de apply.
# Output normal: se muestra en la consola
output "endpoint_base_datos" {
description = "Endpoint de conexión a la base de datos"
value = aws_db_instance.principal.endpoint
}
# Output sensible: el valor se oculta en la consola
output "password_base_datos" {
description = "Contraseña generada para la base de datos"
value = aws_db_instance.principal.password
sensitive = true # Terraform mostrará: password_base_datos = <sensitive>
}
output "cadena_conexion_db" {
description = "String de conexión completo (incluye credenciales)"
value = "postgresql://${aws_db_instance.principal.username}:${aws_db_instance.principal.password}@${aws_db_instance.principal.endpoint}/${aws_db_instance.principal.db_name}"
sensitive = true # Oculto porque contiene la contraseña
}# Para ver el valor de un output sensible debes hacerlo explícitamente
terraform output password_base_datos
# O ver todos los outputs incluyendo sensibles en formato JSON
terraform output -jsonImportante: sensitive = true solo oculta el valor en la salida de Terraform. El valor sigue almacenado en texto plano en el archivo de estado (terraform.tfstate). Protege tu archivo de estado.
Referenciar outputs entre módulos
Los outputs son fundamentales cuando usas módulos. Un módulo expone sus valores con outputs, y el módulo padre los referencia como module.nombre_modulo.nombre_output.
# =============================================
# Archivo: modules/red/main.tf
# Módulo que crea la infraestructura de red
# =============================================
resource "aws_vpc" "principal" {
cidr_block = var.cidr_vpc
}
resource "aws_subnet" "publica" {
count = length(var.zonas)
vpc_id = aws_vpc.principal.id
cidr_block = cidrsubnet(var.cidr_vpc, 8, count.index)
availability_zone = var.zonas[count.index]
}
# Outputs del módulo de red
output "id_vpc" {
description = "ID de la VPC creada"
value = aws_vpc.principal.id
}
output "ids_subnets_publicas" {
description = "Lista de IDs de las subnets públicas"
value = aws_subnet.publica[*].id
}# =============================================
# Archivo: main.tf (módulo raíz)
# Usa el módulo de red y referencia sus outputs
# =============================================
# Llamamos al módulo de red
module "red" {
source = "./modules/red"
cidr_vpc = "10.0.0.0/16"
zonas = ["us-east-1a", "us-east-1b"]
}
# Módulo de servidores que necesita la red
module "servidores" {
source = "./modules/servidores"
# Referenciamos los outputs del módulo "red"
# Sintaxis: module.nombre_modulo.nombre_output
vpc_id = module.red.id_vpc # ID de la VPC
subnet_ids = module.red.ids_subnets_publicas # Lista de subnets
}
# Output del módulo raíz
output "id_vpc_final" {
description = "VPC creada por el módulo de red"
value = module.red.id_vpc
}Ejemplo completo: configuración con variables y outputs
# =============================================
# Archivo: variables.tf
# =============================================
variable "proyecto" {
type = string
description = "Nombre del proyecto"
validation {
condition = length(var.proyecto) >= 3 && length(var.proyecto) <= 20
error_message = "El nombre del proyecto debe tener entre 3 y 20 caracteres."
}
}
variable "entorno" {
type = string
description = "Entorno de despliegue: desarrollo, staging, produccion"
default = "desarrollo"
validation {
condition = contains(["desarrollo", "staging", "produccion"], var.entorno)
error_message = "Entorno no válido. Usa: desarrollo, staging o produccion."
}
}
variable "region" {
type = string
description = "Región de AWS"
default = "us-east-1"
}
variable "tipo_instancia_por_entorno" {
type = map(string)
description = "Tipo de instancia EC2 para cada entorno"
default = {
desarrollo = "t2.micro" # El más barato para pruebas
staging = "t2.small" # Un poco más para pruebas de carga
produccion = "t3.medium" # Con suficiente capacidad
}
}
variable "crear_ip_elastica" {
type = bool
description = "Si se debe crear una IP elástica (estática) para el servidor"
default = false
}
# =============================================
# Archivo: main.tf
# =============================================
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
locals {
# Etiquetas comunes para todos los recursos
etiquetas_comunes = {
Proyecto = var.proyecto
Entorno = var.entorno
Gestionado = "terraform"
}
# Nombre base para recursos
nombre_base = "${var.proyecto}-${var.entorno}"
# Tipo de instancia según entorno (buscamos en el mapa)
tipo_instancia = lookup(var.tipo_instancia_por_entorno, var.entorno, "t2.micro")
}
resource "aws_instance" "app" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = local.tipo_instancia
tags = merge(local.etiquetas_comunes, {
Name = "${local.nombre_base}-servidor"
})
}
# Solo creamos la IP elástica si la variable lo indica
resource "aws_eip" "app" {
count = var.crear_ip_elastica ? 1 : 0
instance = aws_instance.app.id
}
# =============================================
# Archivo: outputs.tf
# =============================================
output "id_instancia" {
description = "ID de la instancia EC2 creada"
value = aws_instance.app.id
}
output "ip_privada" {
description = "IP privada de la instancia"
value = aws_instance.app.private_ip
}
output "ip_publica" {
description = "IP pública (solo disponible si se creó IP elástica)"
# Si se creó la IP elástica (count = 1), mostramos su IP
# Si no (count = 0), mostramos la IP pública temporal de la instancia
value = var.crear_ip_elastica ? aws_eip.app[0].public_ip : aws_instance.app.public_ip
}
output "tipo_instancia_usado" {
description = "Tipo de instancia EC2 que se está usando"
value = local.tipo_instancia
}
output "info_completa" {
description = "Resumen completo de la infraestructura desplegada"
value = {
proyecto = var.proyecto
entorno = var.entorno
region = var.region
instancia = aws_instance.app.id
ip_privada = aws_instance.app.private_ip
}
}Ejercicio práctico
Enunciado
Crea una configuración completa de Terraform con las siguientes características:
-
Define las variables:
nombre_app: string requerido (sin default), validar que tenga entre 5 y 30 caracteres.replicas: number, default 2, validar que sea entre 1 y 10.configuracion: object con camposcpu(number),memoria_mb(number) ypuerto(number).etiquetas_extra: map(string), default vacío.
-
Define un bloque
localsque:- Calcule el nombre completo como
"app-{nombre_app}". - Cree un mapa de etiquetas combinando
{Aplicacion = nombre_app, Replicas = replicas}con lasetiquetas_extra. - Calcule si los recursos son "de alto rendimiento" si
cpu >= 4.
- Calcule el nombre completo como
-
Define los siguientes outputs:
nombre_completo: el nombre completo calculado.etiquetas_finales: el mapa de etiquetas combinado.es_alto_rendimiento: booleano que indica si es de alto rendimiento.resumen: objeto con toda la información resumida.
Solución detallada
# =============================================
# Archivo: variables.tf - Solución del ejercicio
# =============================================
# 1a. Variable nombre_app: string requerido con validación
variable "nombre_app" {
type = string
description = "Nombre de la aplicación. Entre 5 y 30 caracteres."
# Sin default: es obligatorio que el usuario lo proporcione
validation {
# length() devuelve el número de caracteres del string
# Verificamos que esté en el rango permitido
condition = length(var.nombre_app) >= 5 && length(var.nombre_app) <= 30
error_message = "El nombre_app debe tener entre 5 y 30 caracteres. Actualmente tiene ${length(var.nombre_app)}."
}
}
# 1b. Variable replicas: number con default y validación de rango
variable "replicas" {
type = number
description = "Número de réplicas de la aplicación (1-10)"
default = 2 # Por defecto, 2 réplicas para alta disponibilidad básica
validation {
# Verificamos que sea al menos 1 y no más de 10
condition = var.replicas >= 1 && var.replicas <= 10
error_message = "El número de réplicas debe estar entre 1 y 10. Valor recibido: ${var.replicas}."
}
}
# 1c. Variable configuracion: object con campos de recursos
variable "configuracion" {
type = object({
cpu = number # Número de CPUs (ej: 2, 4, 8)
memoria_mb = number # Memoria en MB (ej: 512, 1024, 4096)
puerto = number # Puerto de la aplicación (ej: 8080, 3000)
})
description = "Configuración de recursos de la aplicación"
default = {
cpu = 2 # 2 CPUs por defecto
memoria_mb = 1024 # 1GB de RAM por defecto
puerto = 8080 # Puerto estándar de aplicaciones web
}
}
# 1d. Variable etiquetas_extra: map(string) con default vacío
variable "etiquetas_extra" {
type = map(string)
description = "Etiquetas adicionales para los recursos (opcional)"
default = {} # Por defecto no hay etiquetas extra
}
# =============================================
# Archivo: locals.tf - Valores calculados
# =============================================
locals {
# 2a. Nombre completo de la aplicación
# Interpolamos la variable con el prefijo "app-"
nombre_completo = "app-${var.nombre_app}"
# Ejemplo: si nombre_app = "mi-tienda", resultado = "app-mi-tienda"
# 2b. Mapa de etiquetas combinado
# merge() une dos mapas; si hay claves iguales, el segundo tiene prioridad
etiquetas_finales = merge(
{
# Etiquetas base calculadas a partir de las variables
Aplicacion = var.nombre_app # Nombre de la app
Replicas = tostring(var.replicas) # Convertimos number a string para el tag
# Los tags de AWS son siempre strings, por eso convertimos con tostring()
},
var.etiquetas_extra # Las etiquetas extra del usuario (pueden sobreescribir)
)
# 2c. Determinar si es de alto rendimiento
# Alto rendimiento = 4 o más CPUs
es_alto_rendimiento = var.configuracion.cpu >= 4
# Ejemplo: cpu=2 -> false, cpu=4 -> true, cpu=8 -> true
}
# =============================================
# Archivo: outputs.tf - Valores exportados
# =============================================
# 3a. Nombre completo de la aplicación
output "nombre_completo" {
description = "Nombre completo de la aplicación con prefijo 'app-'"
value = local.nombre_completo
}
# 3b. Mapa de etiquetas combinado
output "etiquetas_finales" {
description = "Mapa completo de etiquetas que se aplicarían a los recursos"
value = local.etiquetas_finales
}
# 3c. Indicador de alto rendimiento
output "es_alto_rendimiento" {
description = "true si la configuración tiene 4 o más CPUs"
value = local.es_alto_rendimiento
}
# 3d. Resumen completo
output "resumen" {
description = "Resumen completo de la configuración de la aplicación"
value = {
nombre = local.nombre_completo # Nombre completo
replicas = var.replicas # Número de réplicas
cpu = var.configuracion.cpu # CPUs configuradas
memoria_mb = var.configuracion.memoria_mb # Memoria configurada
puerto = var.configuracion.puerto # Puerto de la app
alto_rendimiento = local.es_alto_rendimiento # ¿Es de alto rendimiento?
num_etiquetas = length(local.etiquetas_finales) # Cuántas etiquetas tiene
}
}Para probar la solución, crea un archivo terraform.tfvars:
# Archivo: terraform.tfvars para probar el ejercicio
nombre_app = "mi-tienda" # 9 caracteres: válido (entre 5 y 30)
replicas = 3 # Válido (entre 1 y 10)
configuracion = {
cpu = 4 # 4 CPUs -> es_alto_rendimiento = true
memoria_mb = 4096 # 4GB de RAM
puerto = 3000 # Puerto 3000
}
etiquetas_extra = {
Equipo = "backend"
CostCenter = "TI-002"
}# Ejecutar para ver los outputs sin crear recursos
terraform init
terraform plan
# Ver solo los outputs después de apply
terraform output
terraform output resumen
terraform output -json # Formato JSON completoErrores comunes con variables y outputs
Error 1: Referenciar una variable que no existe
# INCORRECTO: la variable "region_aws" no está declarada
provider "aws" {
region = var.region_aws # Error: variable no declarada
}
# CORRECTO: primero declara la variable
variable "region_aws" {
type = string
default = "us-east-1"
}Error 2: Tipo incorrecto en el valor por defecto
# INCORRECTO: el tipo es list(string) pero el default es un string
variable "zonas" {
type = list(string)
default = "us-east-1a" # Error: debe ser una lista
}
# CORRECTO:
variable "zonas" {
type = list(string)
default = ["us-east-1a"] # Lista con un elemento
}Error 3: Output referenciando recurso que no existe aún
# INCORRECTO: si aws_instance.web no está definido en ningún .tf
output "ip_servidor" {
value = aws_instance.web.public_ip # Error: recurso no encontrado
}Error 4: Variable sensible no marcada como tal
# INCORRECTO: la contraseña aparecerá en los logs de CI/CD
output "db_password" {
value = aws_db_instance.main.password
# Sin sensitive = true -> expone la contraseña en los logs
}
# CORRECTO:
output "db_password" {
value = aws_db_instance.main.password
sensitive = true
}Error 5: Confundir var. con local.
# INCORRECTO: los locales se acceden con local. (singular), no locals.
value = locals.nombre_completo # Error
# CORRECTO:
value = local.nombre_completo # local. (singular)Resumen
En este módulo has aprendido todo lo necesario sobre variables y outputs en Terraform:
- Las variables de entrada parametrizan las configuraciones y las hacen reutilizables. Se declaran con
variable {}y se usan convar.nombre. - Los tipos de variables van desde simples (string, number, bool) hasta complejos (list, map, object), cada uno adecuado para diferentes necesidades.
- Las variables requeridas no tienen
defaulty obligan al usuario a especificar un valor; las opcionales tienen un valor por defecto razonable. - Los archivos terraform.tfvars y .auto.tfvars permiten organizar los valores por entorno sin modificar el código principal.
- Las variables de entorno
TF_VAR_*son ideales para pipelines de CI/CD y secretos. - El bloque validation añade validaciones personalizadas con mensajes de error claros.
- Los outputs exportan información de los recursos y son el mecanismo para pasar datos entre módulos.
- Los outputs sensibles ocultan valores confidenciales en la consola pero el archivo de estado sigue siendo texto plano.
Preparación para el siguiente módulo
En el próximo módulo estudiaremos los Bloques de Recursos y Datos en profundidad, incluyendo meta-argumentos como count, for_each y lifecycle. Verás que las variables que has aprendido aquí se usan constantemente para controlar cuántos recursos se crean y cómo se comportan.
Curso de Terraform
Módulo 1: Introducción a Terraform
- ¿Qué es Terraform?
- Instalando Terraform
- Conceptos Básicos de Terraform
- Primera Configuración de Terraform
Módulo 2: Lenguaje de Configuración de Terraform
Módulo 3: Gestión del Estado
Módulo 4: Módulos de Terraform
Módulo 5: Aprovisionamiento de Recursos
- Conceptos Básicos de Aprovisionamiento
- Aprovisionamiento de Recursos AWS
- Aprovisionamiento de Recursos Azure
- Aprovisionamiento de Recursos GCP
Módulo 6: Funcionalidades Avanzadas de Terraform
Módulo 7: Mejores Prácticas de Terraform
- Organización del Código
- Control de Versiones
- Pruebas del Código de Terraform
- Mejores Prácticas de Seguridad
Módulo 8: Terraform en CI/CD
- Integración de Terraform con CI/CD
- Automatización de Terraform con Jenkins
- Uso de Terraform con GitHub Actions
- Terraform Cloud y Enterprise
