Introducción
Cuando terraform validate no detecta problemas pero terraform apply falla de formas inesperadas, o cuando un plan no hace lo que esperas, necesitas herramientas de depuración más potentes. Terraform ofrece un conjunto completo de mecanismos para inspeccionar su comportamiento interno, probar expresiones y rastrear la comunicación con los providers. En esta lección dominarás estas herramientas para convertirte en un diagnosticador eficiente.
- Herramientas de Depuración Disponibles
Terraform proporciona las siguientes herramientas principales para depuración:
TF_LOG: Activa logs detallados del comportamiento interno de TerraformTF_LOG_PATH: Redirige los logs a un archivo en lugar de la terminalTF_LOG_COREyTF_LOG_PROVIDER: Logs separados para el núcleo y los providersterraform console: REPL interactivo para evaluar expresiones HCLterraform validate: Valida la configuración sin conectarse a la nubeterraform plan -json: Plan en formato JSON para análisis detalladoterraform providers: Muestra y verifica la configuración de providers- Outputs temporales: Técnica para exponer valores intermedios durante el desarrollo
- La Variable de Entorno
TF_LOG
TF_LOGTF_LOG es la herramienta de depuración más poderosa de Terraform. Activa un sistema de logging que registra todo lo que Terraform hace internamente, incluyendo las llamadas a la API del proveedor de nube.
Niveles de log disponibles
| Nivel | Descripción | Cuándo usarlo |
|---|---|---|
ERROR |
Solo errores críticos | Para silenciar la mayoría de logs pero capturar fallos |
WARN |
Advertencias y errores | Cuando quieres ver posibles problemas sin todo el ruido |
INFO |
Información general del flujo | Seguimiento del progreso de operaciones |
DEBUG |
Información detallada de depuración | Investigar comportamientos inesperados |
TRACE |
Máximo nivel de detalle (todo) | Depuración profunda, incluye llamadas HTTP completas |
JSON |
Logs en formato JSON estructurado | Análisis programático de logs |
Activar TF_LOG
# En Linux/macOS - nivel DEBUG
export TF_LOG=DEBUG
terraform plan
# En Linux/macOS - nivel TRACE (máximo detalle)
export TF_LOG=TRACE
terraform apply
# En Windows PowerShell
$env:TF_LOG = "DEBUG"
terraform plan
# Para desactivar los logs
unset TF_LOG # Linux/macOS
Remove-Item Env:TF_LOG # PowerShellEjemplo de salida con TF_LOG=DEBUG
2024-01-15T10:30:00.123Z [DEBUG] provider.terraform-provider-aws:
Starting aws provider version 5.31.0 PID=12345
2024-01-15T10:30:00.456Z [DEBUG] provider.terraform-provider-aws:
AWS Auth: Retrieving credentials using AssumeRole
2024-01-15T10:30:01.789Z [DEBUG] provider.terraform-provider-aws:
HTTP Request: PUT https://s3.amazonaws.com/my-bucket
Headers: {"Content-Type": ["application/xml"]}
2024-01-15T10:30:02.012Z [DEBUG] provider.terraform-provider-aws:
HTTP Response: 403 Forbidden
Body: <?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code>
<Message>Access Denied</Message></Error>En este ejemplo, los logs de DEBUG revelan que el problema es un 403 Forbidden al intentar crear el bucket S3, lo que indica falta de permisos.
TF_LOG_PATH: Guardar Logs en Archivo
TF_LOG_PATH: Guardar Logs en ArchivoCuando los logs son muy extensos (especialmente con nivel TRACE), es más práctico guardarlos en un archivo para analizarlos con calma.
# Guardar logs en un archivo mientras se muestra la salida normal en terminal
export TF_LOG=DEBUG
export TF_LOG_PATH=./terraform-debug.log
terraform apply
# Ver el archivo de logs
less ./terraform-debug.log
# Buscar errores específicos en el log
grep -i "error\|failed\|denied" ./terraform-debug.log
# Filtrar solo las llamadas HTTP
grep "HTTP Request\|HTTP Response" ./terraform-debug.logConsejo: Rotar logs entre sesiones
# Crear un log con timestamp para no sobreescribir anteriores
export TF_LOG_PATH="./logs/terraform-$(date +%Y%m%d-%H%M%S).log"
mkdir -p ./logs
terraform apply
TF_LOG_CORE y TF_LOG_PROVIDER: Logs Separados
TF_LOG_CORE y TF_LOG_PROVIDER: Logs SeparadosDesde Terraform 0.15+, puedes separar los logs del núcleo de Terraform de los logs de los providers. Esto es muy útil para aislar si un problema está en la lógica de Terraform o en el provider específico.
# Solo logs del núcleo de Terraform (planificación, estado, etc.)
export TF_LOG_CORE=DEBUG
export TF_LOG_PROVIDER=OFF # Silencia logs del provider
terraform plan
# Solo logs del provider (llamadas a la API de AWS, Azure, etc.)
export TF_LOG_CORE=OFF
export TF_LOG_PROVIDER=DEBUG
terraform apply
# Logs separados en archivos diferentes
export TF_LOG_CORE=DEBUG
export TF_LOG_PROVIDER=TRACE
# Nota: TF_LOG_PATH afecta a ambos; para separarlos completamente
# se recomienda ejecutar con uno activo a la vezCuándo usar cada uno:
TF_LOG_CORE=DEBUG: Cuando el problema parece estar en la lógica del plan, dependencias entre recursos, o el manejo del estado.TF_LOG_PROVIDER=TRACE: Cuando el problema está en la comunicación con la API de AWS/Azure/GCP, problemas de autenticación o respuestas inesperadas de la API.
- Analizar Logs de Debug: Qué Buscar
Los logs de Terraform son extensos. Saber dónde mirar te ahorra horas de análisis.
Patrones importantes en los logs
# Buscar errores HTTP
grep "StatusCode\|status_code\|HTTP/1" terraform-debug.log
# Buscar problemas de autenticación
grep -i "auth\|credential\|token\|denied\|unauthorized" terraform-debug.log
# Buscar qué recursos se están procesando
grep "aws_\|azurerm_\|google_" terraform-debug.log | grep "Refreshing\|Creating\|Updating"
# Ver la secuencia de operaciones
grep "\[DEBUG\]\|\[INFO\]\|\[ERROR\]" terraform-debug.log | head -50Anatomía de un log de TRACE
Con TF_LOG=TRACE verás las llamadas HTTP completas:
2024-01-15T10:30:00.000Z [TRACE] provider.terraform-provider-aws: Request Details: ---[ REQUEST ]---------------------------- PUT /my-terraform-state-bucket HTTP/1.1 Host: s3.us-east-1.amazonaws.com Authorization: AWS4-HMAC-SHA256 Credential=AKIA.../20240115/us-east-1/s3/aws4_request Content-Type: application/xml ---[ RESPONSE ]--------------------------- HTTP/1.1 200 OK x-amz-request-id: ABC123DEF456 ETag: "d41d8cd98f00b204e9800998ecf8427e"
terraform console: Probar Expresiones Interactivamente
terraform console: Probar Expresiones Interactivamenteterraform console es un REPL (Read-Eval-Print Loop) que te permite evaluar expresiones HCL en el contexto de tu configuración actual y el estado de Terraform. Es invaluable para:
- Probar funciones HCL antes de usarlas en el código
- Inspeccionar valores del estado actual
- Depurar expresiones complejas
Uso básico
# Dentro de terraform console:
# Probar funciones de string
> upper("hello world")
"HELLO WORLD"
> replace("ami-123456", "ami-", "")
"123456"
# Probar funciones de lista
> length(["a", "b", "c"])
3
> element(["us-east-1a", "us-east-1b", "us-east-1c"], 1)
"us-east-1b"
# Probar funciones de mapa
> lookup({dev = "t2.micro", prod = "t2.large"}, "prod", "t2.micro")
"t2.large"
# Verificar expresiones condicionales
> var.environment == "prod" ? "t2.large" : "t2.micro"
# (Requiere que var.environment esté definida)Inspeccionar el estado actual
Si ya hay recursos creados en el estado:
# Ver atributos de un recurso existente en el estado
> aws_vpc.main.id
"vpc-0a1b2c3d4e5f67890"
> aws_vpc.main.cidr_block
"10.0.0.0/16"
# Ver outputs definidos
> output.vpc_id
"vpc-0a1b2c3d4e5f67890"
# Iterar sobre recursos creados con count
> aws_subnet.public[0].id
"subnet-0123456789abcdef0"
> [for s in aws_subnet.public : s.id]
[
"subnet-0123456789abcdef0",
"subnet-0fedcba9876543210",
]Depurar expresiones complejas paso a paso
# Supón que tienes esta variable en tu configuración:
# variable "instance_types" {
# default = {
# dev = "t2.micro"
# stg = "t2.small"
# prod = "t2.large"
# }
# }
# En terraform console:
> var.instance_types
{
"dev" = "t2.micro"
"prod" = "t2.large"
"stg" = "t2.small"
}
# Probar el acceso a un elemento
> var.instance_types["dev"]
"t2.micro"
# Si hay un problema con una clave que no existe:
> var.instance_types["qa"]
# Esto daría un error que puedes ver antes de aplicarlo
- Outputs Temporales para Depuración
Una técnica efectiva es añadir outputs temporales para exponer valores intermedios que no son visibles de otra forma durante el terraform plan.
# En outputs.tf - Outputs temporales de depuración
# Ver qué AMI está seleccionando el data source
output "debug_ami_id" {
value = data.aws_ami.amazon_linux.id
description = "DEBUG: AMI seleccionada por el data source"
}
# Ver las AZs disponibles
output "debug_availability_zones" {
value = data.aws_availability_zones.available.names
description = "DEBUG: Lista de AZs disponibles"
}
# Ver la resolución de una expresión compleja
output "debug_subnet_ids" {
value = {
for idx, subnet in aws_subnet.public :
"subnet-${idx}" => {
id = subnet.id
cidr = subnet.cidr_block
az = subnet.availability_zone
}
}
description = "DEBUG: Mapa detallado de subnets creadas"
}# Ejecutar plan para ver los valores que tendrían esos outputs
terraform plan
# O aplicar y ver los valores reales
terraform apply
# Una vez resuelto el problema, eliminar los outputs de debug
# o marcarlos para que no se muestren en la salida normal:# Output de debug que no contamina la salida de apply
output "debug_ami_id" {
value = data.aws_ami.amazon_linux.id
sensitive = true # Oculta el valor en la salida, pero sigue siendo accesible
}
- Inspeccionar el Plan Detallado con
-json
-json# Generar el plan y guardarlo
terraform plan -out=debug.tfplan
# Convertir el plan a JSON y analizarlo
terraform show -json debug.tfplan > debug-plan.json
# Ver la estructura del plan
cat debug-plan.json | jq '.'
# Ver solo los cambios planeados
cat debug-plan.json | jq '.resource_changes[] | {address: .address, action: .change.actions}'
# Ver los valores antes y después de un recurso específico
cat debug-plan.json | jq '.resource_changes[] | select(.address == "aws_instance.web") | .change'
# Ver las variables usadas en el plan
cat debug-plan.json | jq '.variables'Ejemplo de salida JSON útil:
{
"resource_changes": [
{
"address": "aws_instance.web",
"type": "aws_instance",
"name": "web",
"change": {
"actions": ["update"],
"before": {
"instance_type": "t2.micro",
"tags": {"Name": "web-server"}
},
"after": {
"instance_type": "t2.small",
"tags": {"Name": "web-server"}
},
"after_unknown": {}
}
}
]
}
terraform providers: Verificar Configuración de Providers
terraform providers: Verificar Configuración de Providers# Mostrar los providers requeridos y sus versiones
terraform providers
# Salida típica:
# Providers required by configuration:
# .
# ├── provider[registry.terraform.io/hashicorp/aws] ~> 5.0
# └── provider[registry.terraform.io/hashicorp/random] ~> 3.0
# Ver el árbol de providers incluyendo módulos
terraform providers tree # (disponible en versiones recientes)
# Bloquear las versiones actuales en .terraform.lock.hcl
terraform providers lock
# Actualizar el lockfile para una plataforma específica
terraform providers lock -platform=linux_amd64 -platform=darwin_arm64
- Tabla de Comandos de Diagnóstico
| Comando | Descripción | Cuándo usarlo |
|---|---|---|
terraform validate |
Valida sintaxis y referencias sin conectarse a la nube | Primera verificación antes de cualquier operación |
terraform fmt -check |
Verifica el formato del código sin modificarlo | En pipelines de CI/CD |
terraform plan -detailed-exitcode |
Código de salida 0=sin cambios, 1=error, 2=hay cambios | En automatización para detectar drift |
terraform plan -json |
Plan en formato JSON para análisis programático | Analizar cambios complejos o en scripts |
terraform show |
Muestra el estado actual o un plan guardado | Inspeccionar lo que Terraform sabe de la infraestructura |
terraform state list |
Lista todos los recursos en el estado | Ver qué recursos gestiona Terraform |
terraform state show <recurso> |
Muestra todos los atributos de un recurso del estado | Inspeccionar valores específicos de un recurso |
terraform providers |
Lista providers requeridos y sus versiones | Verificar que los providers correctos están configurados |
terraform console |
REPL interactivo para evaluar expresiones | Probar funciones y expresiones HCL antes de usarlas |
TF_LOG=DEBUG terraform apply |
Logs detallados de la operación | Investigar fallos no explicados por los mensajes de error |
- Depuración de Módulos: Conocer el Origen de los Errores
Los errores en módulos tienen una localización más compleja porque muestran la ruta completa:
╷ │ Error: Unsupported argument │ │ on .terraform/modules/vpc/main.tf line 45, in resource "aws_vpc" "this": │ 45: enable_classiclink = var.enable_classiclink │ │ An argument named "enable_classiclink" is not expected here. ╵
Interpretación: El error está dentro del módulo vpc (en .terraform/modules/vpc/main.tf), no en tu código. Esto indica que la versión del módulo que estás usando es incompatible con la versión del provider.
Cómo encontrar el origen:
# Ver qué módulos están descargados y sus versiones
terraform providers
# Ver la fuente de un módulo en particular
cat .terraform/modules/modules.json | jq '.Modules[] | select(.Key == "vpc")'
# Ver las dependencias del módulo
cat .terraform/modules/vpc/.terraform.lock.hclSolución típica: Actualizar o fijar la versión del módulo en source:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2" # Fijar versión compatible con tu provider
# ...
}
- Ejemplo Completo de Sesión de Depuración
Supongamos que ejecutas terraform apply y obtienes un error de AccessDenied. Aquí está la sesión completa de depuración:
Paso 1: Reproducir con logs DEBUG
export TF_LOG=DEBUG
export TF_LOG_PATH=./debug-session.log
terraform apply 2>&1 | tee apply-output.logPaso 2: Analizar el log
# Buscar el error específico
grep -n "AccessDenied\|403\|Forbidden" ./debug-session.log
# Salida:
# 1523: [DEBUG] HTTP Response: 403 Forbidden
# 1524: [DEBUG] Body: {"Code":"AccessDenied","Message":"User: arn:aws:iam::123456789:user/terraform is not authorized to perform: s3:CreateBucket"}Paso 3: Identificar el recurso y la acción
Paso 4: Verificar las credenciales actuales
# Fuera de Terraform, verificar qué usuario/rol está activo
aws sts get-caller-identity
# Salida:
# {
# "UserId": "AIDA...",
# "Account": "123456789",
# "Arn": "arn:aws:iam::123456789:user/terraform"
# }
# Verificar los permisos de ese usuario en AWS
aws iam list-attached-user-policies --user-name terraformPaso 5: Usar terraform console para verificar la configuración
# Verificar que la región está correcta
> var.aws_region
"us-east-1"
# Verificar el nombre del bucket (por si ya existe)
> aws_s3_bucket.terraform_state.bucket
# Si el recurso no existe aún:
# Error: A managed resource "aws_s3_bucket" "terraform_state" has not been declaredPaso 6: Corregir y verificar
# Después de añadir los permisos s3:CreateBucket al usuario IAM
unset TF_LOG
unset TF_LOG_PATH
# Validar de nuevo
terraform validate
# Aplicar de nuevo
terraform apply
- Ejercicio: Depurar una Configuración que Falla
Tienes la siguiente configuración que falla al ejecutar terraform apply:
# variables.tf
variable "environment" {
type = string
}
variable "instance_count" {
type = number
default = 2
}
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
}
}
locals {
instance_types = {
dev = "t2.micro"
prod = "t2.large"
}
# Esta expresión puede fallar - ¿por qué?
selected_type = local.instance_types[var.environment]
}
resource "aws_instance" "app" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = local.selected_type
tags = {
Name = "app-${var.environment}-${count.index}"
Environment = var.environment
}
}
output "instance_ids" {
value = aws_instance.app[*].id
}
output "instance_ips" {
# Esta expresión también puede tener un problema
value = [for inst in aws_instance.app : inst.public_ip if inst.public_ip != ""]
}El error al ejecutar:
╷ │ Error: Invalid index │ │ on main.tf line 38, in locals: │ 38: selected_type = local.instance_types[var.environment] │ ├──────────────── │ │ local.instance_types is object with 2 attributes │ │ var.environment is "staging" │ │ The given key does not identify an element in this collection value. ╵
Tu tarea:
- Usa
terraform consolepara investigar el problema. - Identifica todos los posibles fallos en la configuración.
- Corrige la configuración para que sea robusta.
- Solución Paso a Paso
Paso 1: Investigar con terraform console
# Primero, ejecutar terraform init si no está hecho
terraform init
# Abrir la consola con la variable problemática
terraform console -var="environment=staging"# Investigar el mapa
> local.instance_types
{
"dev" = "t2.micro"
"prod" = "t2.large"
}
# El problema es evidente: "staging" no existe en el mapa
# Probar la función lookup con valor por defecto
> lookup(local.instance_types, "staging", "t2.micro")
"t2.micro"
# Probar con contains para validar
> contains(keys(local.instance_types), "staging")
false
# Verificar los valores válidos
> keys(local.instance_types)
tolist([
"dev",
"prod",
])Paso 2: Identificar todos los problemas
Problema 1: La expresión local.instance_types[var.environment] falla si environment no es dev o prod. No hay valor por defecto ni validación.
Problema 2: La variable environment no tiene validación de valores permitidos.
Problema 3: El output instance_ips filtra por public_ip != "" pero en instancias sin IP pública, este valor puede ser null, no "".
Paso 3: Configuración corregida
# variables.tf - CORREGIDO
variable "environment" {
type = string
description = "Entorno de despliegue (dev, stg, prod)"
# Validación para rechazar valores no soportados
validation {
condition = contains(["dev", "stg", "prod"], var.environment)
error_message = "El entorno debe ser 'dev', 'stg' o 'prod'."
}
}
variable "instance_count" {
type = number
default = 2
validation {
condition = var.instance_count > 0 && var.instance_count <= 10
error_message = "El número de instancias debe estar entre 1 y 10."
}
}
# main.tf - CORREGIDO
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-22.04-amd64-server-*"]
}
}
locals {
instance_types = {
dev = "t2.micro"
stg = "t2.small" # Añadido stg
prod = "t2.large"
}
# Corrección 1: lookup con valor por defecto
selected_type = lookup(local.instance_types, var.environment, "t2.micro")
}
resource "aws_instance" "app" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = local.selected_type
tags = {
Name = "app-${var.environment}-${count.index}"
Environment = var.environment
}
}
output "instance_ids" {
value = aws_instance.app[*].id
}
# Corrección 2: usar != null en lugar de != ""
output "instance_ips" {
value = [for inst in aws_instance.app : inst.public_ip if inst.public_ip != null]
}Verificar la corrección:
# Probar con un entorno válido
terraform plan -var="environment=stg"
# Probar que la validación funciona con valor inválido
terraform plan -var="environment=qa"
# Debe mostrar:
# │ Error: Invalid value for variable
# │ var.environment is "qa"
# │ El entorno debe ser 'dev', 'stg' o 'prod'.
- Consejos Avanzados de Depuración
Consejo 1: Usar TF_CLI_ARGS para flags persistentes
# En lugar de escribir -var-file=... en cada comando
export TF_CLI_ARGS_plan="-var-file=dev.tfvars"
export TF_CLI_ARGS_apply="-var-file=dev.tfvars"
# Ahora terraform plan usará automáticamente -var-file=dev.tfvars
terraform planConsejo 2: Aislar el recurso problemático con -target
# Aplicar solo un recurso específico para depurar
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.webAdvertencia:
-targetes una herramienta de depuración, no debe usarse en producción de forma regular, ya que puede dejar el estado inconsistente.
Consejo 3: Usar terraform state show para inspeccionar el estado
# Ver todos los atributos de un recurso específico en el estado
terraform state show aws_instance.web
# Salida:
# resource "aws_instance" "web" {
# ami = "ami-0c55b159cbfafe1f0"
# instance_type = "t2.micro"
# private_ip = "10.0.1.5"
# public_ip = "54.123.45.67"
# ...
# }Consejo 4: Comparar plan anterior con el actual
# Guardar plan actual
terraform plan -out=before.tfplan
terraform show -json before.tfplan > before.json
# Después de cambios, generar nuevo plan
terraform plan -out=after.tfplan
terraform show -json after.tfplan > after.json
# Comparar (requiere jq instalado)
diff <(jq '.resource_changes' before.json) <(jq '.resource_changes' after.json)Resumen
En esta lección has aprendido a usar las herramientas de depuración de Terraform de forma sistemática:
TF_LOGcon sus cinco niveles (TRACE, DEBUG, INFO, WARN, ERROR) para obtener visibilidad completa del comportamiento interno de Terraform.TF_LOG_PATHpara guardar logs extensos en archivos analizables congrepy otras herramientas.TF_LOG_COREyTF_LOG_PROVIDERpara aislar si el problema está en el núcleo de Terraform o en el provider específico.terraform consolecomo REPL interactivo para probar expresiones HCL antes de aplicarlas y para inspeccionar el estado.- Outputs temporales como técnica para exponer valores intermedios durante el desarrollo.
terraform plan -jsonpara analizar cambios programáticamente.- Un flujo de depuración completo aplicado a un caso real de
AccessDenied.
En la siguiente lección abordaremos los problemas de gestión del estado, que son a menudo los más delicados y que requieren mayor precaución en equipos de trabajo: estados corruptos, drift, recursos huérfanos y bloqueos indefinidos.
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
