Qué vas a conseguir
Que la infraestructura de una sede o de un servicio salga de un repositorio revisado y no de clics en el portal. Un módulo reutilizable, un estado compartido que nadie pisa, un pipeline donde el cambio se lee antes de aplicarse y una forma ordenada de tratar lo que se cambió a mano o lo que ya existía antes de Terraform.
Antes de empezar
- Terraform 1.10 o posterior y el proveedor azurerm 4.x.
- Un repositorio Git con ramas protegidas: a
mainsolo se llega por pull request revisado (guía de Git). - Una cuenta de almacenamiento para el estado, creada aparte, con versionado de blobs y eliminación temporal activados.
- Una identidad para el pipeline: identidad administrada o aplicación de Entra ID con federación de identidades de carga de trabajo (OIDC), con rol Contributor solo sobre las suscripciones que gestiona. Sin secretos de cliente guardados.
1. El módulo: grupo de recursos, red y VM
Módulos en modules/ y una carpeta por entorno (envs/prod, envs/pruebas) que los usa con sus valores.
cat envs/prod/main.tfterraform { required_version = ">= 1.10" required_providers { azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" } } backend "azurerm" { resource_group_name = "rg-tfstate" storage_account_name = "sttfstatecontoso01" container_name = "tfstate" key = "prod/sede-vm.tfstate" use_azuread_auth = true }} provider "azurerm" { features {} subscription_id = "00000000-0000-0000-0000-000000000000"} module "sede_madrid" { source = "../../modules/sede-vm" nombre = "mad" ubicacion = "spaincentral" rango_red = "10.50.0.0/16" tamano_vm = "Standard_B2ms" admin_password = var.admin_password etiquetas = { entorno = "prod", responsable = "laura.prado" }}- El módulo
sede-vmcontieneazurerm_resource_group,azurerm_virtual_network,azurerm_subnet,azurerm_network_security_group,azurerm_network_interfaceyazurerm_windows_virtual_machine, con sus variables y salidas. Todo llega por variable. - Versiones acotadas (
~> 4.0) y el.terraform.lock.hclen el repositorio, para que todos usen el mismo proveedor.
2. Estado remoto con bloqueo
El estado dice qué recurso real corresponde a cada bloque. En el portátil de alguien no sirve para un equipo.
- azurerm: el estado vive en un blob y el bloqueo usa el lease del blob, sin nada más que configurar. Con
use_azuread_auth = trueel acceso es con identidad de Entra ID (rol Storage Blob Data Contributor), no con la clave de la cuenta. - S3 (en AWS): desde Terraform 1.10,
use_lockfile = truebloquea con un fichero en el propio bucket; la tabla de DynamoDB queda obsoleta. - Una clave de estado por entorno y componente (
prod/sede-vm.tfstate,prod/red-core.tfstate). Un estado gigante hace que un error alcance a todo.
Si un apply se corta, el bloqueo puede quedarse puesto. terraform force-unlock <ID> solo tras confirmar que no hay otro apply en marcha: dos procesos escribiendo corrompen el estado.
3. Plan y apply en el pipeline, con revisión
Nadie ejecuta apply desde su equipo contra producción. El flujo:
- Pull request → el pipeline ejecuta
terraform fmt -check,terraform validateyterraform plan -out=tfplan, y publica el plan como comentario o artefacto. - Un compañero lee el plan, no solo el código. Lo que importa es qué se crea, qué cambia y, sobre todo, qué se destruye.
- Merge a
main→ el pipeline aplica ese mismo plan guardado (terraform apply tfplan) tras la aprobación del entorno (environments de Azure DevOps o de GitHub).
terraform init -input=falseterraform plan -input=false -out=tfplanTerraform used the selected providers to generate the following executionplan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.sede_madrid.azurerm_network_security_group.vm will be updated in-place ~ resource "azurerm_network_security_group" "vm" { id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sede-mad/providers/Microsoft.Network/networkSecurityGroups/nsg-srv-mad01" name = "nsg-srv-mad01" ~ security_rule = [ + { + name = "allow-https-oficina" + source_address_prefix = "192.168.10.0/24" + priority = 200 # (10 unchanged attributes hidden) }, ] } Plan: 0 to add, 1 to change, 0 to destroy.Una regla útil en la revisión: cualquier plan con destroy o con -/+ (replace) en un recurso con datos (VM, disco, base de datos) se para y se habla.
4. "Alguien lo cambió en el portal": la deriva
Un viernes por la noche alguien amplió la VM desde el portal para salir del paso. El siguiente plan lo detecta:
terraform plan -refresh-onlyNote: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since thelast "terraform apply" which may have affected this plan: # module.sede_madrid.azurerm_windows_virtual_machine.app has changed ~ resource "azurerm_windows_virtual_machine" "app" { id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sede-mad/providers/Microsoft.Compute/virtualMachines/srv-mad01" name = "srv-mad01" ~ size = "Standard_B2ms" -> "Standard_D4s_v5" tags = { "entorno" = "prod" "responsable" = "laura.prado" } }Hay dos salidas, y se decide con el responsable del servicio, no en solitario:
- El cambio era necesario: se lleva al código (
tamano_vm = "Standard_D4s_v5") por pull request. El plan siguiente queda vacío. - No lo era: un plan normal propone devolverla a
Standard_B2ms, y se aplica en una ventana (cambiar el tamaño reinicia la VM).
Un plan programado cada noche contra cada entorno, que avisa si sale algo distinto de No changes, convierte la deriva en un aviso en lugar de una sorpresa. Y sin rol Contributor permanente para las personas en producción (lectura más PIM), la deriva es la excepción.
5. Importar lo que ya existe
El servidor de impresión se creó a mano hace años. Desde Terraform 1.5, la importación se declara en el código y pasa por el mismo plan revisado:
cat import.tfimport { to = azurerm_windows_virtual_machine.impresion id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sede-mad/providers/Microsoft.Compute/virtualMachines/srv-print01"}terraform plan -generate-config-out=generado.tfazurerm_windows_virtual_machine.impresion: Preparing import... [id=/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-sede-mad/providers/Microsoft.Compute/virtualMachines/srv-print01] Terraform will perform the following actions: # azurerm_windows_virtual_machine.impresion will be imported # (config will be generated) Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.generado.tf es un punto de partida, no código final: se limpia (fuera atributos por defecto, valores a variables), se repite el plan hasta que diga 1 to import, 0 to change y se aplica.
6. Higiene del estado y secretos
- Mover sin destruir: al renombrar un recurso o meterlo en un módulo, un bloque
moved { from = ... to = ... }evita que Terraform lo destruya y lo cree de nuevo. Para dejar de gestionar algo sin borrarlo, un bloqueremoved(1.7+). Ambos pasan por revisión, a diferencia deterraform state mvostate rm. - El estado no se edita a mano. Si algo se rompe, el versionado del blob permite volver a la versión anterior.
- Secretos fuera del código: nada de contraseñas en
.tfvarsdentro del repositorio. La contraseña de administración se genera conrandom_passwordy se guarda en Key Vault, o llega como variablesensitivedesde el almacén de secretos del pipeline. - Ojo:
sensitiveoculta el valor en la salida, pero el estado lo guarda en claro. El acceso al contenedor del estado se restringe como el de Key Vault; los argumentos write-only (Terraform 1.11+) evitan que lleguen al estado cuando el proveedor los admite.
7. Lo que revisas cada mes
- El plan nocturno de deriva: entornos con cambios pendientes y por qué.
- Quién tiene permisos de escritura sobre el contenedor del estado y sobre las suscripciones, además del pipeline.
- Recursos en el portal que no están en ningún estado (Azure Resource Graph por etiqueta
gestionado-por): candidatos a importar o a borrar.
Siguientes pasos
- La guía de Amazon AWS: la misma disciplina con el backend S3 y el proveedor
aws. - La guía de Jenkins (CI/CD) para montar el pipeline si no usáis Azure DevOps o GitHub Actions, y la de Ansible para configurar dentro de las VM lo que Terraform crea.
- La guía de Kubernetes para crear el clúster AKS con este mismo flujo.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.
Apúntate y llévate las 33 ideas →