Introducción
Hasta ahora hemos trabajado con el estado de Terraform en su forma más simple: un archivo local en el directorio de trabajo. Sin embargo, en el mundo real, los proyectos de infraestructura son trabajo de equipo. Varios ingenieros necesitan colaborar, los pipelines de CI/CD necesitan acceso al estado, y la infraestructura de producción no puede depender de un archivo en el portátil de alguien.
La solución es el estado remoto: almacenar el archivo de estado en un sistema compartido, accesible y seguro. En esta lección aprenderás a configurar los backends remotos más populares y a sacar el máximo partido del estado remoto.
¿Por Qué Usar Estado Remoto?
El estado local presenta limitaciones críticas en entornos reales:
Problemas del estado local en equipos
-
Sin colaboración: El archivo
terraform.tfstateestá en el disco de un desarrollador. El resto del equipo no puede ver el estado actual de la infraestructura. -
Sin locking: Si dos personas ejecutan
terraform applyal mismo tiempo, ambas leerán el mismo estado, calcularán los cambios de forma independiente y escribirán resultados inconsistentes. El estado queda corrupto. -
Sin historial: Solo existe el estado actual y el backup del estado anterior. Si necesitas ver el estado de hace una semana, no tienes forma de recuperarlo.
-
Sin seguridad: El archivo puede contener contraseñas y tokens en texto plano. Si alguien hace
git pushpor error, esos secretos quedan expuestos. -
Frágil: Si el desarrollador borra el directorio o pierde el portátil, se pierde el estado y el control sobre la infraestructura.
Ventajas del estado remoto
| Ventaja | Descripción |
|---|---|
| Compartido | Todo el equipo y los sistemas de CI/CD acceden al mismo estado |
| Locking | Evita escrituras concurrentes que corrompen el estado |
| Versionado | Historial completo de cambios con capacidad de rollback |
| Seguro | Cifrado en tránsito y en reposo; control de acceso con IAM/RBAC |
| Resiliencia | Almacenado en sistemas de alta disponibilidad (S3, Azure Blob, etc.) |
| Separación | El estado no está mezclado con el código fuente |
Backends Disponibles en Terraform
Un backend es el sistema que Terraform usa para almacenar el estado y ejecutar operaciones. Hay dos categorías principales:
Backends estándar (solo almacenamiento)
Almacenan el estado pero requieren que las operaciones de Terraform se ejecuten localmente o en CI/CD:
- local: El valor por defecto. Estado en disco local.
- s3: Amazon S3 (con locking opcional vía DynamoDB).
- azurerm: Azure Blob Storage.
- gcs: Google Cloud Storage.
- http: Cualquier servidor HTTP que implemente la API de estado de Terraform.
- kubernetes: Secretos de Kubernetes.
- consul: HashiCorp Consul.
- pg: PostgreSQL.
Backend especial: Terraform Cloud / Enterprise
Terraform Cloud es el backend recomendado por HashiCorp. Además de almacenar el estado, puede ejecutar los planes y applies en la nube, gestionar variables y secretos, proporcionar aprobaciones manuales y mucho más.
Tabla Comparativa de Backends Más Populares
| Backend | Locking | Cifrado en Reposo | Versionado | Coste | Dificultad de Configuración |
|---|---|---|---|---|---|
| local | No | No | No (solo backup) | Gratis | Ninguna |
| S3 + DynamoDB | Sí (DynamoDB) | Sí (KMS) | Sí (S3 versioning) | Bajo (AWS) | Media |
| Azure Blob | Sí (nativo) | Sí (Azure) | Sí (blob versioning) | Bajo (Azure) | Media |
| GCS | Sí (nativo) | Sí (Google) | Sí (object versioning) | Bajo (GCP) | Media |
| Terraform Cloud | Sí (nativo) | Sí | Sí (ilimitado) | Gratis/Pago | Baja |
| Consul | Sí (nativo) | Depende | No | Gratis/Pago | Alta |
| PostgreSQL | Sí (nativo) | Depende | No | Variable | Alta |
Configurar Backend de S3 con DynamoDB para Locking
Esta es la configuración más común en entornos AWS. Combina S3 (para almacenar el estado) con DynamoDB (para el locking).
Paso 1: Crear la infraestructura del backend (manualmente o con Terraform)
Primero necesitas crear el bucket S3 y la tabla DynamoDB. Un truco habitual es usar un proyecto Terraform separado ("bootstrap") para crear el backend del proyecto principal:
# bootstrap/main.tf
# Este archivo se aplica UNA SOLA VEZ con estado local para crear el backend
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
# El bucket S3 para almacenar el estado
resource "aws_s3_bucket" "terraform_state" {
bucket = "mi-empresa-terraform-state-prod"
# Previene la eliminación accidental del bucket
lifecycle {
prevent_destroy = true
}
}
# Activar versionado del bucket (crucial para el historial del estado)
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Cifrado del bucket con KMS
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
# Bloquear acceso público al bucket de estado
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Tabla DynamoDB para el locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-state-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
# LockID es el único atributo que necesita DynamoDB para el locking
attribute {
name = "LockID"
type = "S"
}
}
# Outputs útiles para configurar el backend en otros proyectos
output "nombre_bucket" {
value = aws_s3_bucket.terraform_state.bucket
}
output "nombre_tabla_dynamodb" {
value = aws_dynamodb_table.terraform_locks.name
}Paso 2: Configurar el backend en el proyecto principal
# backend.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
# Nombre del bucket donde se almacenará el estado
bucket = "mi-empresa-terraform-state-prod"
# Ruta dentro del bucket (como un "directorio")
# Convención recomendada: entorno/proyecto/terraform.tfstate
key = "produccion/mi-proyecto/terraform.tfstate"
# Región donde está el bucket
region = "us-east-1"
# Cifrado del estado en tránsito
encrypt = true
# Tabla DynamoDB para el locking
dynamodb_table = "terraform-state-locks"
}
}
provider "aws" {
region = "us-east-1"
}Paso 3: Inicializar con el nuevo backend
Salida esperada:
Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency lock file - Using previously-installed hashicorp/aws v5.23.0 Terraform has been successfully initialized!
Estructura de claves S3 recomendada para múltiples entornos
mi-empresa-terraform-state-prod/
├── global/
│ └── iam/
│ └── terraform.tfstate
├── desarrollo/
│ ├── red/
│ │ └── terraform.tfstate
│ └── aplicacion/
│ └── terraform.tfstate
├── staging/
│ ├── red/
│ │ └── terraform.tfstate
│ └── aplicacion/
│ └── terraform.tfstate
└── produccion/
├── red/
│ └── terraform.tfstate
└── aplicacion/
└── terraform.tfstateConfigurar Backend de Azure Blob Storage
Para proyectos que usan Microsoft Azure:
Paso 1: Crear el recurso de almacenamiento (Azure CLI)
# Variables de configuración
RESOURCE_GROUP="rg-terraform-state"
STORAGE_ACCOUNT="tfstatemipresa$(date +%s)" # Nombre único
CONTAINER_NAME="tfstate"
LOCATION="westeurope"
# Crear el grupo de recursos
az group create \
--name $RESOURCE_GROUP \
--location $LOCATION
# Crear la cuenta de almacenamiento
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--sku Standard_LRS \
--encryption-services blob \
--min-tls-version TLS1_2
# Crear el contenedor de blobs
az storage container create \
--name $CONTAINER_NAME \
--account-name $STORAGE_ACCOUNT
# Habilitar versionado de blobs
az storage account blob-service-properties update \
--account-name $STORAGE_ACCOUNT \
--resource-group $RESOURCE_GROUP \
--enable-versioning true
echo "Storage Account: $STORAGE_ACCOUNT"Paso 2: Configurar el backend en Terraform
# backend.tf
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
backend "azurerm" {
# Grupo de recursos donde está la cuenta de almacenamiento
resource_group_name = "rg-terraform-state"
# Nombre de la cuenta de almacenamiento
storage_account_name = "tfstatemipresa1234567890"
# Nombre del contenedor de blobs
container_name = "tfstate"
# Nombre del blob (el "archivo" dentro del contenedor)
key = "produccion/mi-proyecto.tfstate"
}
}
provider "azurerm" {
features {}
}Azure Blob Storage tiene locking nativo: usa la característica de "lease" de blobs, por lo que no necesitas una tabla adicional como DynamoDB.
Configurar Terraform Cloud como Backend
Terraform Cloud (cloud.hashicorp.com) es la opción más completa y la más fácil de configurar para equipos.
Paso 1: Crear una cuenta y organización en Terraform Cloud
- Ve a https://app.terraform.io y crea una cuenta gratuita.
- Crea una organización (por ejemplo,
mi-empresa). - Crea un workspace (por ejemplo,
mi-proyecto-produccion).
Paso 2: Generar un token de API
Esto abre el navegador y guarda automáticamente el token en ~/.terraform.d/credentials.tfrc.json.
Paso 3: Configurar el backend
# backend.tf
terraform {
required_version = ">= 1.1"
cloud {
# Nombre de tu organización en Terraform Cloud
organization = "mi-empresa"
workspaces {
# Nombre del workspace en Terraform Cloud
name = "mi-proyecto-produccion"
}
}
}O usando el backend remote (forma más antigua pero compatible con más versiones):
terraform {
backend "remote" {
organization = "mi-empresa"
workspaces {
name = "mi-proyecto-produccion"
}
}
}Paso 4: Inicializar
Salida esperada:
Initializing Terraform Cloud... Initializing provider plugins... Terraform Cloud has been successfully initialized! You may now begin working with Terraform Cloud. Try running "terraform plan" to see any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or Terraform Settings, run "terraform init" again to reinitialize your working directory.
A partir de este punto, cada terraform plan y terraform apply se ejecuta en los servidores de Terraform Cloud, no en tu máquina local.
Acceder al Estado Remoto de Otro Workspace
En proyectos grandes, es común dividir la infraestructura en múltiples proyectos de Terraform (por ejemplo, uno para la red y otro para las aplicaciones). El proyecto de aplicación necesita conocer los IDs de los recursos creados por el proyecto de red (como el ID de la VPC o la subred).
Esto se resuelve con el data source terraform_remote_state:
Proyecto de red (quien comparte el estado)
# red/main.tf
resource "aws_vpc" "principal" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "publica" {
vpc_id = aws_vpc.principal.id
cidr_block = "10.0.1.0/24"
}
# IMPORTANTE: Los valores que quieres compartir deben ser outputs
output "vpc_id" {
value = aws_vpc.principal.id
description = "ID de la VPC principal"
}
output "subnet_publica_id" {
value = aws_subnet.publica.id
description = "ID de la subred pública"
}# red/backend.tf
terraform {
backend "s3" {
bucket = "mi-empresa-terraform-state-prod"
key = "produccion/red/terraform.tfstate"
region = "us-east-1"
}
}Proyecto de aplicación (quien lee el estado)
# aplicacion/main.tf
# Leer el estado del proyecto de red
data "terraform_remote_state" "red" {
backend = "s3"
config = {
bucket = "mi-empresa-terraform-state-prod"
key = "produccion/red/terraform.tfstate"
region = "us-east-1"
}
}
# Usar los valores del estado remoto
resource "aws_instance" "servidor_app" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
# Usando el output del proyecto de red
subnet_id = data.terraform_remote_state.red.outputs.subnet_publica_id
tags = {
Name = "ServidorApp"
VPC = data.terraform_remote_state.red.outputs.vpc_id
}
}Importante: Solo puedes acceder a los valores declarados como output en el otro proyecto. No puedes acceder directamente a los atributos de los recursos del otro estado. Esto es intencional: los outputs son la "API pública" de un proyecto Terraform.
Migrar Estado Local a Estado Remoto
Cuando comienzas un proyecto con estado local y luego quieres migrarlo a un backend remoto, Terraform lo hace automáticamente con terraform init -migrate-state.
Proceso de migración
Situación inicial: Tienes un proyecto con estado local y decides migrar a S3.
# Antes de migrar, verifica que tienes estado local
ls -la terraform.tfstate
# Modifica tu backend.tf para configurar el backend S3
# (o créalo si no existía)# backend.tf (nuevo o modificado)
terraform {
backend "s3" {
bucket = "mi-empresa-terraform-state-prod"
key = "produccion/mi-proyecto/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-locks"
encrypt = true
}
}Terraform te preguntará confirmación:
Initializing the backend... Backend configuration changed! Terraform has detected that the configuration specified for the backend has changed. Terraform will now check for existing state in the newly configured backend... Acquiring state lock. This may take a few moments... Do you want to copy existing state to the new backend? Pre-existing state was found while migrating the previous "local" backend to the newly configured "s3" backend. No existing state was found in the newly configured "s3" backend. Do you want to copy this state to the new backend? Enter a value:
Escribe yes y pulsa Enter.
Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes.
Después de la migración, el estado local ya no es relevante. Puedes eliminarlo:
Migrar entre backends remotos
Si necesitas migrar de un backend a otro (por ejemplo, de S3 a Terraform Cloud):
# Simplemente actualiza la configuración del backend en backend.tf
# y luego ejecuta:
terraform init -migrate-stateTerraform descargará el estado del backend antiguo y lo subirá al nuevo automáticamente.
Permisos Necesarios para el Backend
Permisos AWS para S3 + DynamoDB
El usuario o rol de IAM que ejecuta Terraform necesita los siguientes permisos:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::mi-empresa-terraform-state-prod",
"arn:aws:s3:::mi-empresa-terraform-state-prod/*"
]
},
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:*:table/terraform-state-locks"
}
]
}Permisos Azure para Azure Blob Storage
El principal de servicio necesita el rol Storage Blob Data Contributor en la cuenta de almacenamiento:
# Asignar rol al principal de servicio
az role assignment create \
--assignee "<client-id-del-principal-de-servicio>" \
--role "Storage Blob Data Contributor" \
--scope "/subscriptions/<id>/resourceGroups/rg-terraform-state/providers/Microsoft.Storage/storageAccounts/tfstatemipresa1234567890"Ejercicio Práctico: Configurar un Backend Remoto Usando S3
Objetivo
Configurar un backend S3 funcional. Si no tienes acceso a AWS, seguirás los pasos con LocalStack (un emulador de AWS local).
Opción A: Con LocalStack (sin credenciales AWS reales)
# Instalar LocalStack (requiere Docker)
pip install localstack
localstack start -d
# Configurar credenciales ficticias para LocalStack
export AWS_ACCESS_KEY_ID="test"
export AWS_SECRET_ACCESS_KEY="test"
export AWS_DEFAULT_REGION="us-east-1"# main.tf para LocalStack
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "mi-terraform-state-local"
key = "ejercicio/terraform.tfstate"
region = "us-east-1"
access_key = "test"
secret_key = "test"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
force_path_style = true
endpoints = {
s3 = "http://localhost:4566"
dynamodb = "http://localhost:4566"
}
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = "us-east-1"
access_key = "test"
secret_key = "test"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
force_path_style = true
endpoints {
s3 = "http://localhost:4566"
dynamodb = "http://localhost:4566"
}
}# Crear el bucket y la tabla en LocalStack
aws --endpoint-url=http://localhost:4566 s3 mb s3://mi-terraform-state-local
aws --endpoint-url=http://localhost:4566 dynamodb create-table \
--table-name terraform-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
# Inicializar Terraform con el backend S3
terraform init
# Verificar que el estado se guarda remotamente
terraform apply -auto-approve
aws --endpoint-url=http://localhost:4566 s3 ls s3://mi-terraform-state-local/ejercicio/Opción B: Con AWS real
Si tienes acceso a AWS con las credenciales configuradas:
# Configurar credenciales (si aún no lo has hecho)
aws configure
# Crear el bucket
aws s3 mb s3://mi-empresa-terraform-state-ejercicio --region us-east-1
# Activar versionado
aws s3api put-bucket-versioning \
--bucket mi-empresa-terraform-state-ejercicio \
--versioning-configuration Status=Enabled
# Crear tabla DynamoDB
aws dynamodb create-table \
--table-name terraform-state-locks-ejercicio \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region us-east-1# backend.tf
terraform {
backend "s3" {
bucket = "mi-empresa-terraform-state-ejercicio"
key = "ejercicio/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-locks-ejercicio"
}
}# Inicializar con migración si ya tenías estado local
terraform init -migrate-state
# Verificar que el estado está en S3
aws s3 ls s3://mi-empresa-terraform-state-ejercicio/ejercicio/Verificación del ejercicio
# 1. Confirma que no hay terraform.tfstate local
ls -la terraform.tfstate 2>/dev/null || echo "Correcto: no hay estado local"
# 2. Descarga el estado remoto para inspeccionarlo
terraform state pull
# 3. Lista los recursos en el estado remoto
terraform state list
# 4. Verifica que el locking funciona intentando
# ejecutar dos terraform plan en paralelo (abre dos terminales)
# Terminal 1:
terraform plan
# Terminal 2 (mientras el primero está en ejecución):
terraform plan
# Deberías ver: "Error: Error acquiring the state lock"Errores Comunes con Backends Remotos
Error 1: Credenciales insuficientes para acceder al backend
Causa: El usuario o rol de IAM no tiene permisos suficientes sobre el bucket S3. Solución: Revisa y actualiza la política de IAM según los permisos documentados anteriormente.
Error 2: El bucket S3 no existe
Causa: El bucket especificado en la configuración del backend no existe todavía.
Solución: Crea el bucket antes de ejecutar terraform init.
Error 3: Tabla DynamoDB no encontrada
Causa: La tabla DynamoDB para locking no existe.
Solución: Crea la tabla DynamoDB con el atributo LockID de tipo String.
Error 4: Estado bloqueado por un proceso anterior
Error: Error acquiring the state lock Lock Info: ID: abc123... Operation: OperationTypeApply Who: usuario@ci-server Created: 2024-01-15 10:30:00
Causa: Un proceso de CI/CD anterior falló y no liberó el lock.
Solución: Usa terraform force-unlock <ID> con el ID del lock mostrado en el error. Esto se verá en detalle en la siguiente lección.
Error 5: Conflicto de lineage al migrar
Error: Backend configuration changed The "lineage" value "abc123..." of the existing state does not match the "lineage" value "def456..." of the migrated state.
Causa: Estás intentando sobrescribir un estado existente en el backend remoto con un estado diferente (de otro proyecto).
Solución: Verifica que la key en la configuración del backend es la correcta para este proyecto.
Resumen
En esta lección has aprendido a trasladar el estado de Terraform de un archivo local a sistemas remotos y seguros:
- El estado remoto es esencial para equipos: permite colaboración, locking, historial y seguridad.
- Los backends más populares son S3+DynamoDB (AWS), Azure Blob Storage y Terraform Cloud, cada uno con sus propias características de locking y versionado.
- Para S3, necesitas crear el bucket (con versionado y cifrado) y una tabla DynamoDB para el locking. Los permisos de IAM son esenciales.
- Para Azure Blob, el locking es nativo y no necesitas recursos adicionales.
- Terraform Cloud es la opción más completa: almacena el estado, ejecuta los planes en la nube y gestiona secretos.
- El data source
terraform_remote_statepermite que un proyecto de Terraform acceda a los outputs de otro, facilitando la arquitectura modular de infraestructura. - La migración de estado local a remoto se hace con
terraform init -migrate-state.
En la siguiente lección (03-04-state-locking.md) profundizaremos específicamente en el mecanismo de bloqueo del estado: cómo funciona, qué backends lo soportan, qué hacer cuando un lock queda huérfano y cómo gestionar situaciones de emergencia con terraform force-unlock.
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
