Terraform Backend en Azure Storage: Setup completo
Resumen
Guardar el estado de Terraform en local es peligroso y no escala. Voy al grano: Azure Storage con state locking es la solución estándar para equipos. Aquí el setup completo en 5 minutos.
¿Por qué usar remote backend?
Problemas con backend local: - ❌ Estado se pierde si borras tu laptop - ❌ Imposible colaborar en equipo - ❌ No hay locking → corrupciones en despliegues simultáneos - ❌ Secretos en plaintext en disco local
Ventajas con Azure Storage: - ✅ Estado centralizado y versionado - ✅ Locking automático con blob lease - ✅ Encriptación at-rest - ✅ Acceso controlado con RBAC
Setup del backend
1. Crear Storage Account
# Variables
RG="terraform-state-rg"
LOCATION="westeurope"
STORAGE_ACCOUNT="tfstate$(date +%s)" # Nombre único
CONTAINER="tfstate"
# Crear resource group
az group create \
--name $RG \
--location $LOCATION
# Crear storage account
az storage account create \
--name $STORAGE_ACCOUNT \
--resource-group $RG \
--location $LOCATION \
--sku Standard_LRS \
--encryption-services blob \
--min-tls-version TLS1_2
# Crear container
az storage container create \
--name $CONTAINER \
--account-name $STORAGE_ACCOUNT \
--auth-mode login
Naming convention
Storage account solo acepta lowercase y números, máximo 24 caracteres. Usa un sufijo único para evitar colisiones.
2. Configurar backend en Terraform
backend.tf:
terraform {
backend "azurerm" {
resource_group_name = "terraform-state-rg"
storage_account_name = "tfstate1234567890"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
3. Inicializar
# Login a Azure
az login
# Inicializar backend
terraform init
# Migrar estado existente (si aplica)
terraform init -migrate-state
State locking
Azure usa blob leases para locking automático:
# Ver si hay lock activo
az storage blob show \
--container-name $CONTAINER \
--name prod.terraform.tfstate \
--account-name $STORAGE_ACCOUNT \
--query "properties.lease.status"
Si alguien está ejecutando terraform apply, verás:
Multi-entorno con workspaces
# Crear workspace por entorno
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Listar workspaces
terraform workspace list
# Cambiar entre entornos
terraform workspace select prod
Cada workspace crea su propio state file:
Seguridad: Managed Identity
Evita usar access keys en pipelines:
# Crear managed identity
az identity create \
--resource-group $RG \
--name terraform-identity
# Asignar rol Storage Blob Data Contributor
IDENTITY_ID=$(az identity show \
--resource-group $RG \
--name terraform-identity \
--query principalId -o tsv)
az role assignment create \
--role "Storage Blob Data Contributor" \
--assignee $IDENTITY_ID \
--scope /subscriptions/{sub-id}/resourceGroups/$RG/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT
Backend config con managed identity:
terraform {
backend "azurerm" {
resource_group_name = "terraform-state-rg"
storage_account_name = "tfstate1234567890"
container_name = "tfstate"
key = "prod.terraform.tfstate"
use_msi = true
subscription_id = "00000000-0000-0000-0000-000000000000"
tenant_id = "00000000-0000-0000-0000-000000000000"
}
}
Versionado del estado
# Habilitar versioning en el container
az storage blob service-properties update \
--account-name $STORAGE_ACCOUNT \
--enable-versioning true
# Ver versiones anteriores
az storage blob list \
--container-name $CONTAINER \
--account-name $STORAGE_ACCOUNT \
--include v \
--query "[?name=='prod.terraform.tfstate'].{Version:versionId, LastModified:properties.lastModified}"
Pipeline CI/CD con Azure DevOps
azure-pipelines.yml:
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- group: terraform-variables
stages:
- stage: Plan
jobs:
- job: TerraformPlan
steps:
- task: AzureCLI@2
displayName: 'Terraform Init & Plan'
inputs:
azureSubscription: 'Azure Service Connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
terraform init
terraform plan -out=tfplan
- publish: '$(System.DefaultWorkingDirectory)/tfplan'
artifact: tfplan
- stage: Apply
dependsOn: Plan
condition: succeeded()
jobs:
- deployment: TerraformApply
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: tfplan
- task: AzureCLI@2
displayName: 'Terraform Apply'
inputs:
azureSubscription: 'Azure Service Connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
terraform init
terraform apply tfplan
Troubleshooting
Error: "Failed to lock state"
# Forzar unlock (solo si estás seguro)
terraform force-unlock <LOCK_ID>
# O romper lease manualmente
az storage blob lease break \
--container-name $CONTAINER \
--blob-name prod.terraform.tfstate \
--account-name $STORAGE_ACCOUNT
Error: "storage account not found"
Buenas prácticas
- State file por proyecto: No mezcles infraestructuras diferentes
- Soft delete: Habilita soft delete en storage account
- Network security: Restringe acceso desde VNet específicas
- Audit logs: Habilita diagnostic settings para compliance
- Backup externo: Exporta estados críticos a otro storage account
Never commit state files
Añade *.tfstate* a .gitignore. El state contiene secretos en plaintext.