Introducción

Imagina que dos ingenieros de tu equipo, Ana y Carlos, ejecutan terraform apply simultáneamente sobre el mismo proyecto. Ana quiere actualizar la capacidad de un servidor; Carlos quiere añadir un nuevo grupo de seguridad. Sin ningún mecanismo de control, ambos leen el mismo estado, calculan sus cambios de forma independiente y luego los dos intentan escribir sus resultados. El estado resultante es una mezcla inconsistente de ambas operaciones, posiblemente corrupto e inutilizable.

El bloqueo de estado (state locking) es el mecanismo que evita exactamente este escenario. En esta lección aprenderás cómo funciona, por qué es crítico y cómo gestionar las situaciones en que el bloqueo falla o queda huérfano.


¿Por Qué es Necesario el Bloqueo de Estado?

El archivo de estado de Terraform es un recurso compartido. Cuando múltiples procesos o usuarios intentan modificarlo simultáneamente, pueden ocurrir varios problemas:

Escenarios de corrupción sin locking

Escenario 1: Escritura simultánea

  • Ana lee el estado: VPC creada, 2 instancias EC2.
  • Carlos lee el mismo estado: VPC creada, 2 instancias EC2.
  • Ana aplica su cambio (añade 1 instancia): escribe el estado con 3 instancias.
  • Carlos aplica su cambio (modifica la VPC): lee el estado desactualizado y escribe sobreescribiendo el trabajo de Ana.
  • Resultado: la instancia que Ana creó desaparece del estado (aunque sigue existiendo en la nube), creando un drift masivo.

Escenario 2: Lectura durante escritura

  • Ana está en medio de un terraform apply largo.
  • Carlos ejecuta terraform plan.
  • Carlos lee el estado a mitad de la escritura de Ana (estado parcial o corrupto).
  • El plan de Carlos puede producir resultados incorrectos o error.

Escenario 3: Pipeline de CI/CD con múltiples ramas

  • El pipeline de la rama develop y el pipeline de la rama hotfix se ejecutan en paralelo.
  • Ambos leen el mismo estado de producción y aplican cambios.
  • Los cambios se solapan y el estado final es impredecible.

Cómo Funciona el Locking en Terraform

El mecanismo de locking en Terraform funciona como un "semáforo":

  1. Antes de cualquier operación que modifique el estado (apply, destroy, refresh, state mv, etc.), Terraform intenta adquirir el lock.
  2. Si el lock está libre, Terraform lo crea (escribe información sobre quién tiene el lock y cuándo).
  3. Terraform ejecuta la operación.
  4. Al finalizar, Terraform libera el lock (elimina la entrada de lock).
  5. Si el lock está ocupado cuando Terraform intenta adquirirlo, la operación falla inmediatamente con un mensaje de error que muestra quién tiene el lock.

Información que contiene un lock

{
  "ID": "f4a5c8b2-1234-5678-abcd-ef9012345678",
  "Operation": "OperationTypeApply",
  "Info": "",
  "Who": "ana@maquina-ana",
  "Version": "1.5.7",
  "Created": "2024-01-15T10:30:00.000000Z",
  "Path": "produccion/mi-proyecto/terraform.tfstate"
}
Campo Descripción
ID UUID único del lock actual
Operation Tipo de operación: OperationTypeApply, OperationTypePlan, etc.
Who Usuario y hostname que adquirió el lock
Version Versión de Terraform que creó el lock
Created Timestamp de creación del lock
Path Ruta del estado que está bloqueado

Backends que Soportan Locking

No todos los backends implementan locking. Es fundamental conocer las capacidades de tu backend:

Tabla: Backends y sus Capacidades de Locking

Backend Locking Mecanismo de Locking Notas
local No N/A Sin locking; solo apto para uso individual
s3 Opcional DynamoDB Requiere configurar dynamodb_table
azurerm Azure Blob Lease Nativo, no requiere configuración adicional
gcs Google Cloud Storage Object Lock Nativo desde GCS
terraform cloud API interna de Terraform Cloud El más robusto; incluye cola de espera
consul Sessions de Consul Requiere Consul desplegado
pg Advisory locks de PostgreSQL Nativo en PostgreSQL
http Opcional Depende de la implementación del servidor El servidor HTTP debe implementar locking
kubernetes Secrets de Kubernetes Nativo en el backend de k8s

Nota importante: El backend local no tiene locking. Si dos terminales ejecutan terraform apply simultáneamente contra el mismo directorio de trabajo, ambas operaciones intentarán modificar el mismo archivo terraform.tfstate sin ninguna coordinación.


Configurar Locking con DynamoDB (para S3 Backend)

El backend S3 por sí solo no tiene locking. El locking se implementa usando una tabla de DynamoDB como "registro de locks".

Cómo funciona el locking S3 + DynamoDB

  1. Terraform intenta escribir un ítem en la tabla DynamoDB con clave LockID = "mi-bucket/mi-clave.tfstate".
  2. La escritura usa una condición: solo tiene éxito si el ítem NO existe (operación atómica garantizada por DynamoDB).
  3. Si el ítem ya existe (otro proceso tiene el lock), DynamoDB rechaza la escritura y Terraform reporta error.
  4. Al terminar la operación, Terraform elimina el ítem de DynamoDB.

Crear la tabla DynamoDB para locking

# Dentro de tu proyecto de bootstrap o directamente en AWS

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-state-locks"
  billing_mode = "PAY_PER_REQUEST"

  # LockID es la clave primaria obligatoria
  # El valor será la ruta completa del estado en S3
  hash_key = "LockID"

  attribute {
    name = "LockID"
    type = "S"  # S = String
  }

  # Opcional: añadir TTL para eliminar locks huérfanos automáticamente
  # (aunque Terraform gestiona el ciclo de vida del lock)
  ttl {
    attribute_name = "TimeToExist"
    enabled        = true
  }

  tags = {
    Proyecto  = "InfraestructuraTerraform"
    Proposito = "LockingEstado"
  }
}

Configuración completa del backend S3 con locking

# backend.tf
terraform {
  backend "s3" {
    bucket = "mi-empresa-terraform-state-prod"
    key    = "produccion/aplicacion-web/terraform.tfstate"
    region = "us-east-1"

    # Activar cifrado en tránsito
    encrypt = true

    # TABLA DYNAMODB PARA LOCKING
    dynamodb_table = "terraform-state-locks"

    # ARN de la clave KMS para cifrado adicional (opcional)
    # kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abc123..."
  }
}

Verificar que el locking funciona

# Terminal 1: Inicia un apply largo
terraform apply

# Terminal 2: Intenta ejecutar otro plan MIENTRAS el apply está en curso
terraform plan

Salida en Terminal 2:

Acquiring state lock. This may take a few moments...

Error: Error acquiring the state lock

Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
  ID:        f4a5c8b2-1234-5678-abcd-ef9012345678
  Path:      produccion/aplicacion-web/terraform.tfstate
  Operation: OperationTypeApply
  Who:       ana@maquina-ana
  Version:   1.5.7
  Created:   2024-01-15 10:30:00.123456 +0000 UTC
  Info:

Terraform acquires a state lock to protect the state from being written
by multiple users at the same time. Please resolve the issue above and try
again. For most commands, you can disable locking with the "-lock=false"
flag, but this is not recommended.

El lock funcionó correctamente: la operación del Terminal 2 fue rechazada.


Locking en Terraform Cloud

Terraform Cloud implementa el locking de forma nativa y con funcionalidades adicionales muy útiles:

Características del locking en Terraform Cloud

  • Cola de espera: En lugar de fallar inmediatamente, Terraform Cloud puede poner en cola los runs que llegan mientras otro está activo.
  • Visibilidad: Puedes ver en la interfaz web quién tiene el lock y qué operación está ejecutando.
  • Integración con aprobaciones: Si configuras aprobaciones manuales, el "lock" dura hasta que alguien aprueba o rechaza el plan.
  • Cancelación: Puedes cancelar un run en progreso desde la interfaz web, lo que libera el lock automáticamente.

Configuración

terraform {
  cloud {
    organization = "mi-empresa"

    workspaces {
      name = "produccion-aplicacion-web"
    }
  }
}

Con Terraform Cloud, el locking es automático y no necesitas configurar nada adicional. Cada workspace gestiona sus propios locks.


El Archivo .terraform.tfstate.lock.info

Cuando usas el backend local, Terraform crea un archivo temporal llamado .terraform.tfstate.lock.info en el directorio de trabajo mientras una operación está en progreso.

mi-proyecto/
├── main.tf
├── terraform.tfstate
└── .terraform.tfstate.lock.info  ← Solo existe durante una operación activa

Contenido del archivo de lock local

{
  "ID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "Operation": "OperationTypeApply",
  "Info": "",
  "Who": "carlos@laptop-carlos",
  "Version": "1.5.7",
  "Created": "2024-01-15T14:22:33.456789Z",
  "Path": "terraform.tfstate"
}

Si el proceso de Terraform termina inesperadamente (falla el sistema, se mata el proceso, etc.), este archivo puede quedar huérfano. En ese caso, verás el error:

Error: Failed to get existing workspaces: stat .terraform.tfstate.lock.info: permission denied

O bien:

Error: Error locking state: Error acquiring the state lock: lock file
.terraform.tfstate.lock.info already exists

La solución para el backend local es eliminar el archivo manualmente (después de verificar que no hay ningún proceso Terraform real ejecutándose):

# Verifica primero que no hay ningún proceso Terraform activo
ps aux | grep terraform

# Si no hay procesos activos, elimina el lock file
rm .terraform.tfstate.lock.info

Desbloqueo Manual con terraform force-unlock

Cuando un lock queda huérfano en un backend remoto (como S3+DynamoDB), no puedes simplemente borrar un archivo. En su lugar, usas el comando terraform force-unlock.

Sintaxis

terraform force-unlock [opciones] ID_DEL_LOCK

Cuándo es seguro usar force-unlock

SOLO es seguro cuando estás absolutamente seguro de que:

  1. No hay ningún proceso de Terraform ejecutándose actualmente que sea el dueño legítimo del lock.
  2. El lock es un "lock zombie" de un proceso que falló o fue interrumpido.

Cómo obtener el ID del lock

El ID del lock aparece en el mensaje de error cuando Terraform no puede adquirirlo:

Error: Error acquiring the state lock

Error message: ConditionalCheckFailedException
Lock Info:
  ID:        f4a5c8b2-1234-5678-abcd-ef9012345678   ← ESTE ES EL ID
  Path:      produccion/terraform.tfstate
  Operation: OperationTypeApply
  Who:       ci-runner@github-actions
  Version:   1.5.7
  Created:   2024-01-15 10:30:00 +0000 UTC

También puedes consultar la tabla DynamoDB directamente:

aws dynamodb scan \
  --table-name terraform-state-locks \
  --region us-east-1 \
  --output json | jq '.Items'

Ejecutar force-unlock

# Terraform pedirá confirmación antes de proceder
terraform force-unlock f4a5c8b2-1234-5678-abcd-ef9012345678
Do you really want to force-unlock state?
  Terraform will remove the lock on the remote state.
  This will allow local Terraform commands to modify this state, even though it
  may be still be in use. Only 'yes' will be accepted to confirm.

  Enter a value: yes

Terraform state has been successfully unlocked!

The state has been unlocked, and Terraform commands should now be able to
obtain a new lock on the remote state.

Cuándo NO usar force-unlock

  • Cuando no estás seguro de si hay un proceso legítimo usando el lock.
  • Cuando el lock fue adquirido hace muy poco tiempo (podría ser un proceso que acaba de empezar).
  • Cuando el mensaje del lock muestra que la operación es un apply crítico en producción y podría estar a mitad.

El riesgo de force-unlock incorrecto: Si liberas el lock de un proceso que todavía está activo, ese proceso continuará ejecutándose y escribiendo al estado mientras tú también empiezas a ejecutar comandos. Esto puede llevar exactamente a la corrupción que el locking pretende evitar.

Protocolo recomendado antes de force-unlock

# 1. Identifica quién tiene el lock (del mensaje de error)
# 2. Contacta a esa persona o verifica en el sistema de CI/CD
# 3. Confirma que el proceso ha terminado
# 4. Solo entonces ejecuta force-unlock

# Puedes también verificar en DynamoDB si el lock sigue activo
aws dynamodb get-item \
  --table-name terraform-state-locks \
  --key '{"LockID": {"S": "mi-empresa-terraform-state-prod/produccion/terraform.tfstate"}}' \
  --region us-east-1

Desactivar el Locking Temporalmente

En casos de emergencia o desarrollo local, puedes desactivar el locking con la bandera -lock=false:

# USAR CON EXTREMA PRECAUCIÓN
terraform apply -lock=false
terraform plan -lock=false

Advertencia: Esta opción debe usarse ÚNICAMENTE cuando:

  • Estás trabajando solo en un entorno de desarrollo.
  • Estás seguro de que nadie más accederá al estado durante la operación.
  • El backend no soporta locking y lo aceptas conscientemente.

En entornos de equipo o producción, nunca uses -lock=false.


Ejercicio Práctico: Simular un Escenario de Bloqueo y Resolverlo

Objetivo

Simular un lock huérfano usando el backend local y resolverlo usando force-unlock.

Configuración del ejercicio

mkdir ejercicio-locking
cd ejercicio-locking
# main.tf
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.0"
    }
  }
}

resource "local_file" "archivo_test" {
  content  = "Archivo de prueba para ejercicio de locking"
  filename = "${path.module}/test.txt"
}
# Inicializar y aplicar la configuración base
terraform init
terraform apply -auto-approve

Paso 1: Simular un lock huérfano

En el backend local, el lock se representa con el archivo .terraform.tfstate.lock.info. Vamos a crearlo manualmente para simular un lock huérfano:

# Crear un lock artificial (simulando un proceso que quedó colgado)
cat > .terraform.tfstate.lock.info << 'EOF'
{
  "ID": "12345678-aaaa-bbbb-cccc-123456789012",
  "Operation": "OperationTypeApply",
  "Info": "",
  "Who": "proceso-fantasma@servidor-ci",
  "Version": "1.5.7",
  "Created": "2024-01-15T08:00:00.000000Z",
  "Path": "terraform.tfstate"
}
EOF

Paso 2: Intentar ejecutar terraform plan (debería fallar)

terraform plan

Salida esperada:

Acquiring state lock. This may take a few moments...

Error: Error acquiring the state lock

Error message: ...
Lock Info:
  ID:        12345678-aaaa-bbbb-cccc-123456789012
  Path:      terraform.tfstate
  Operation: OperationTypeApply
  Who:       proceso-fantasma@servidor-ci
  Version:   1.5.7
  Created:   2024-01-15 08:00:00.000000 +0000 UTC
  Info:

El plan fue bloqueado correctamente.

Paso 3: Verificar que no hay proceso real

# Verificar que no hay procesos terraform activos
ps aux | grep terraform | grep -v grep
# Si no aparece nada, el lock es huérfano

Paso 4: Resolver el lock huérfano con force-unlock

# Usar el ID del lock que apareció en el error
terraform force-unlock 12345678-aaaa-bbbb-cccc-123456789012

Salida esperada:

Do you really want to force-unlock state?
  Terraform will remove the lock on the remote state.
  This will allow local Terraform commands to modify this state, even though it
  may be still be in use. Only 'yes' will be accepted to confirm.

  Enter a value: yes

Terraform state has been successfully unlocked!

Paso 5: Verificar que el lock fue liberado

# El archivo de lock ya no debe existir
ls -la .terraform.tfstate.lock.info 2>/dev/null && echo "Lock existe" || echo "Lock liberado correctamente"

# Ahora terraform plan debe funcionar
terraform plan

Salida esperada de terraform plan:

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.

Variante: Simulación con dos terminales (locking real)

Para ver el locking funcionando con un proceso real:

# main.tf para ejercicio de locking real
terraform {
  required_providers {
    time = {
      source  = "hashicorp/time"
      version = "~> 0.9"
    }
  }
}

# Este recurso tarda en crearse/modificarse
resource "time_sleep" "espera" {
  create_duration = "30s"
}
# Terminal 1: Inicia el apply (tardará 30 segundos)
terraform init
terraform apply -auto-approve

# Terminal 2: Mientras el apply del Terminal 1 está en curso, intenta otro plan
terraform plan
# Verás el error de lock

Errores Comunes Relacionados con el Locking

Error 1: Lock por pipeline de CI/CD fallido

Síntoma:

Error acquiring the state lock
Who: runner@github-actions-xyz123

Causa: Un job de GitHub Actions falló a mitad de un apply y no liberó el lock. Solución:

# Verifica en GitHub Actions que el workflow ya terminó (con fallo)
# Luego libera el lock
terraform force-unlock <ID>

Prevención: Configura timeout en tus workflows de CI/CD:

# .github/workflows/terraform.yml
jobs:
  terraform:
    timeout-minutes: 30  # El workflow fallará si supera 30 minutos
    steps:
      - name: Terraform Apply
        run: terraform apply -auto-approve

Error 2: Múltiples entornos con la misma clave en DynamoDB

Error: Error acquiring the state lock
Who: usuario@maquina (entorno desarrollo)

Pero el error ocurre en producción.

Causa: Dos proyectos de Terraform diferentes están usando la misma ruta de estado en S3, por lo que comparten la misma clave de lock en DynamoDB. Solución: Cada entorno/proyecto debe tener una clave (key) única en la configuración del backend:

# MAL: misma clave para desarrollo y producción
backend "s3" {
  key = "terraform.tfstate"  # ← Demasiado genérico
}

# BIEN: claves únicas por entorno
backend "s3" {
  key = "produccion/aplicacion-web/terraform.tfstate"  # ← Específico
}

Error 3: Lock en estado de "pending" indefinidamente

Terraform reporta "Acquiring state lock..." pero nunca falla ni continúa.

Causa: Problemas de conectividad con DynamoDB o el sistema de locking del backend. Diagnóstico:

# Para DynamoDB
aws dynamodb describe-table --table-name terraform-state-locks --region us-east-1

# Verificar conectividad
aws sts get-caller-identity

Error 4: force-unlock falla

Error: Failed to unlock state: the state is not locked by this client

Causa: El ID del lock que proporcionaste no coincide con el lock actual. Solución: Obtén el ID correcto directamente de DynamoDB:

aws dynamodb scan \
  --table-name terraform-state-locks \
  --region us-east-1 \
  --query 'Items[*].LockID.S' \
  --output text

Buenas Prácticas para el Locking

  1. Siempre usa un backend con locking en entornos de equipo

Nunca uses el backend local ni backends sin soporte de locking cuando más de una persona (o un sistema de CI/CD) puede ejecutar Terraform.

  1. Implementa timeouts en CI/CD

# Ejemplo para GitHub Actions
- name: Terraform Apply
  run: terraform apply -auto-approve
  timeout-minutes: 30

  1. Serializa los runs en CI/CD

En lugar de permitir que múltiples pipelines ejecuten Terraform en paralelo, usa colas o concurrencia limitada:

# GitHub Actions: limitar concurrencia por entorno
concurrency:
  group: terraform-produccion
  cancel-in-progress: false  # No cancelar; esperar a que termine

  1. Monitoriza los locks activos

Crea una alarma o revisión periódica para detectar locks de larga duración:

# Script para detectar locks huérfanos (más de 1 hora)
aws dynamodb scan \
  --table-name terraform-state-locks \
  --region us-east-1 \
  --output json | \
  jq '.Items[] | select(.Created.S < (now - 3600 | strftime("%Y-%m-%dT%H:%M:%SZ")))'

  1. Documenta el procedimiento de desbloqueo

Asegúrate de que todo el equipo sabe qué hacer si encuentran un lock huérfano. Documenta el proceso en tu wiki o README:

# Procedimiento para desbloquear el estado de Terraform

1. Verifica que no hay ningún proceso Terraform ejecutándose actualmente.
2. Contacta al propietario del lock (campo "Who").
3. Si confirmas que el lock es huérfano: `terraform force-unlock <ID>`.
4. Nunca uses force-unlock sin verificar los pasos anteriores.

Resumen del Módulo 3: Gestión del Estado

Esta última lección del módulo 3 completa tu comprensión del sistema de estado de Terraform. Aquí tienes un resumen de lo que has aprendido en todo el módulo:

Resumen de la Lección 03-04

  • El locking evita la corrupción del estado cuando múltiples procesos intentan modificarlo simultáneamente.
  • Sin locking, las escrituras concurrentes pueden dejar el estado en un estado inconsistente o corrupto.
  • Terraform implementa locking mediante un mecanismo de "marcar antes de modificar" (check-and-set atómico).
  • No todos los backends soportan locking: el backend local no tiene locking; S3 necesita DynamoDB; Azure Blob y GCS tienen locking nativo.
  • DynamoDB implementa el locking para S3 mediante condiciones atómicas en la escritura de un ítem con la clave LockID.
  • Los locks pueden quedar huérfanos si un proceso de Terraform termina inesperadamente.
  • terraform force-unlock <ID> libera un lock huérfano, pero debe usarse con precaución, solo después de verificar que no hay ningún proceso legítimo usando el lock.
  • Usar -lock=false es peligroso y solo debe emplearse en situaciones muy específicas y con plena conciencia del riesgo.

Resumen completo del Módulo 3

Lección Conceptos Clave
03-01 Qué es el estado, mapeo de recursos, estructura de terraform.tfstate, comandos de inspección, drift y refresh
03-02 Estructura detallada del JSON de estado, terraform.tfstate.backup, terraform state mv, terraform state rm, terraform import, .terraform.lock.hcl, estrategias de backup
03-03 Por qué usar estado remoto, backends disponibles, configuración de S3+DynamoDB/Azure/Terraform Cloud, terraform_remote_state, migración de estado
03-04 Por qué es necesario el locking, cómo funciona, backends con locking, DynamoDB para S3, Terraform Cloud, locks huérfanos, terraform force-unlock

Próximo módulo (Módulo 4: Módulos de Terraform): Con un sólido entendimiento del estado, estás listo para aprender sobre los módulos: la forma de organizar, reutilizar y compartir configuraciones de Terraform. Los módulos son el mecanismo principal para construir infraestructura escalable y mantenible, y funcionan directamente con el sistema de estado que acabas de dominar.

© Copyright 2026. Todos los derechos reservados