Terraform para gente de datos: Infrastructure as Code en Microsoft Fabric
Qué es Infrastructure as Code y cómo funciona Terraform (HCL, providers, estado y plan), explicado con analogías de SQL y Delta Lake para Microsoft Fabric.
pasos_despliegue.docx. Y el _v2. Y el _FINAL. Y el _FINAL_FINAL2.
Dentro: 27 capturas de pantalla de una versión del portal que ya no existe, la captura 14 en inglés y el resto en español, y un paso que solo sabe hacer Jose. Y Jose está de vacaciones.
Seguro que tienes uno parecido en alguna carpeta. El problema de fondo no es el Word: es que montar un entorno depende de un procedimiento manual que cuesta repetir y que nadie mantiene al día.
En la NetCoreConf, Alfonso Ming y yo dimos la charla «Y yo aquí creando workspaces a mano», sobre cómo dejar de hacer esto con Terraform en Microsoft Fabric. Aquí te cuento la parte teórica, lo que me habría gustado tener claro antes de escribir la primera línea. La práctica, con el vídeo de la demo, la tienes en el siguiente artículo.
Hagamos la cuenta
Un proyecto normal: cuatro entornos (dev, test, QA y pro) y tres capas medallion (bronze, silver y gold). Son doce workspaces antes de crear un solo lakehouse. Con cuatro roles por workspace, 48 asignaciones de permisos.
Eso es una tarde haciendo clic, y es muy difícil llegar al final sin que se te escape algún rol.
Lo que pasa con el ClickOps
Así lo hacemos casi todos: abres el portal, configuras, haces capturas «para documentarlo luego» y funciona. Los clics no son el problema. El problema llega después.
Tres meses más tarde alguien pregunta por qué el workspace de QA no tiene el mismo rol que el de pro, y nadie lo sabe. Para responder necesitarías saber qué había antes, quién lo cambió y por qué, y el portal no te lo cuenta de forma cómoda. Si mañana hay que montar el mismo entorno para otro proyecto, dependes de la memoria de quien lo hizo la primera vez, y dos personas siguiendo el mismo Word acaban con dos configuraciones distintas. Y cuando llega una auditoría preguntando quién tiene acceso a qué, toca reconstruirlo a mano.
Tener la configuración en código y en Git resuelve buena parte de esto: los cambios se revisan en una pull request y queda un historial. Ojo, Git te dice qué se cambió en el código y quién lo aprobó; quién lo aplicó y cuándo lo sabrás si el apply lo lanza un pipeline y no cada persona desde su portátil.
Qué es Infrastructure as Code (IaC)
Infrastructure as Code es describir tu plataforma en ficheros de texto, guardarlos en Git y que una herramienta los aplique por ti. Los ficheros se pueden leer, comparar y copiar de un proyecto a otro, y en Git tienes historial, ramas, pull requests y la opción de volver atrás. Si Git todavía te da respeto, aquí tienes mi traductor Git para analistas de datos.
Imperativo o declarativo: esto ya lo dominas
Hay dos formas de automatizar. La imperativa consiste en decirle a la máquina cómo hacerlo, paso a paso, con un script donde tú escribes todos los if:
$ws = Get-Workspace -Name "Bronze-DEV"
if ($null -eq $ws) {
$ws = New-Workspace -Name "Bronze-DEV"
Set-Capacity -Id $cap -Workspace $ws
} else {
if ($ws.Capacity -ne $cap) { Set-Capacity -Id $cap -Workspace $ws }
}
¿Existe ya? ¿Tiene la capacity buena? Todos esos casos los controlas tú.
La declarativa consiste en describir cómo quieres que quede, y el motor calcula qué tiene que hacer para llegar ahí:
resource "fabric_workspace" "bronze_dev" {
display_name = "Bronze-DEV"
capacity_id = var.capacity_id
}
Si trabajas con datos, esto ya lo conoces: SQL también es declarativo. Escribes el SELECT que quieres y no le dices al motor si tiene que hacer un hash join o un nested loop. Terraform hace lo mismo con la plataforma.
Siempre me ha parecido curioso que el esquema de nuestras tablas viva en código desde hace años y la plataforma que las contiene no.
El cambio de mentalidad
Cuando adoptas Terraform, el repo pasa a ser la referencia de cómo tiene que estar la plataforma, y el portal es lo que resulta de aplicarlo. Cambiar algo a mano en el portal sigue siendo posible, pero ese cambio queda fuera del código y Terraform intentará deshacerlo la próxima vez (lo veremos más abajo, con el drift).
Qué es Terraform
Terraform tiene tres piezas. Una CLI, el ejecutable terraform, con la que vas a usar sobre todo init, plan, apply y destroy. Un lenguaje, HCL: ficheros .tf con bloques y pares clave = valor, bastante fácil de leer aunque no lo hayas visto nunca. Y los providers, que son los plugins que saben hablar con cada servicio.
Sobre la licencia: Terraform es gratis de usar, pero desde la versión 1.6, en agosto de 2023, se distribuye con licencia BSL, que no es open source. Si eso te importa, OpenTofu es el fork abierto y entiende el mismo código.
El provider es un conector
Power BI no sabe hablar con SAP; el conector de SAP sí. Con Terraform pasa lo mismo: no sabe hablar con Fabric, y el provider de Fabric sí. Tu código .tf describe lo que quieres, Terraform calcula el plan, el provider lo traduce a llamadas a la API REST de Fabric y es la API la que hace el trabajo.
En Fabric vas a usar tres:
| Provider | Para qué |
|---|---|
microsoft/fabric | Workspaces, roles, lakehouses, warehouses, notebooks, pipelines, dominios, integración con Git y más |
hashicorp/azuread | Los grupos de seguridad de Entra ID |
hashicorp/azurerm | La capacity de Fabric y la storage account donde guardas el estado |
Se combinan sin problema: en el mismo fichero creas un grupo en Entra ID y se lo asignas a un workspace.
Anatomía de un recurso
resource "fabric_workspace" "bronze_dev" {
display_name = "Bronze-DEV"
description = "Capa bronze, DEV"
capacity_id = var.capacity_id
}
resource indica que quieres que esto exista. "fabric_workspace" es el tipo de cosa, y lo define el provider. "bronze_dev" es el nombre con el que tú lo llamas dentro del código. Y lo que va entre llaves son los argumentos: cómo tiene que ser.
Aquí está el error más habitual al empezar: bronze_dev no es el nombre del workspace. El nombre que ves en el portal es display_name. Para referenciar el workspace desde otro recurso usas fabric_workspace.bronze_dev.id.
Los bloques de HCL que vas a ver
| Bloque | Qué es |
|---|---|
resource | Algo que Terraform crea y gestiona |
variable | Un parámetro de entrada: el entorno, la capacity |
output | Un valor que devuelve: el ID del workspace creado |
data | Algo que ya existe y solo quieres consultar |
locals | Valores calculados que reutilizas: nombres, tags |
module | Un paquete reutilizable: tu template empaquetado |
Para un primer ejemplo te bastan resource y variable. Los módulos llegan cuando empiezas a repetir el mismo patrón en varios proyectos.
Tu primer .tf para Microsoft Fabric
terraform {
required_providers {
fabric = {
source = "microsoft/fabric"
version = "~> 1.14"
}
}
}
provider "fabric" {} # autenticación por variables de entorno
variable "capacity_id" {
type = string
}
resource "fabric_workspace" "bronze_dev" {
display_name = "Bronze-DEV"
capacity_id = var.capacity_id
}
Son cuatro bloques: qué providers necesitas y en qué versión (Terraform los descarga en el init), cómo te conectas, qué cambia entre entornos y qué quieres que exista.
Cómo funciona por dentro
Si te quedas con una sola idea de este artículo, que sea esta. Terraform compara siempre tres cosas:
- Tu código (
*.tf): lo que quieres que exista. - El estado (
terraform.tfstate): lo que Terraform sabe que creó y gestiona. - La realidad (Microsoft Fabric): lo que hay de verdad ahora mismo.
Cada plan calcula las diferencias entre las tres y te propone qué hacer para que la realidad coincida con el código.
El estado se parece mucho al _delta_log
Para quien viene de datos, esta es la analogía que mejor me ha funcionado:
| Delta Lake | Terraform |
|---|---|
| Los parquet son los datos | Los workspaces son la realidad |
_delta_log dice qué ficheros forman la versión actual | terraform.tfstate dice qué recursos gestiona y con qué IDs |
| Sin el log: parquets sueltos y ninguna tabla | Sin el estado: workspaces sueltos que Terraform no sabe que son suyos |
El estado es lo que permite a Terraform relacionar cada recurso del código con su identificador real en Fabric. Si lo pierdes, el código no basta: Terraform no sabrá que esos workspaces son suyos e intentará crearlos de nuevo.
Por eso conviene tratarlo con cuidado desde el primer día. No lo subas a Git, porque guarda valores en claro, a veces secretos (*.tfstate va al .gitignore). Y en cuanto trabaje más de una persona, guárdalo en un backend remoto con bloqueo, por ejemplo una storage account de Azure, para que todos compartáis el mismo estado y dos apply no se pisen:
terraform {
backend "azurerm" {
resource_group_name = "rg-tfstate"
storage_account_name = "sttfstate001"
container_name = "tfstate"
key = "fabric.tfstate"
}
}
terraform plan es tu plan de ejecución
¿Lanzarías una query de 40 minutos sin mirar el plan de ejecución? Con Terraform, la regla es la misma: antes de cada apply, lee el plan.
Los símbolos que tienes que saber leer:
| Símbolo | Qué significa |
|---|---|
+ | Se va a crear algo nuevo |
~ | Se modifica sin destruir |
- | Se elimina |
-/+ | Se destruye y se vuelve a crear: lo que hubiera dentro se pierde |
Así se ve uno de verdad:
$ terraform plan
Terraform will perform the following actions:
# fabric_workspace.bronze_dev will be created
+ resource "fabric_workspace" "bronze_dev" {
+ display_name = "Bronze-DEV"
+ capacity_id = "f1a2b3c4-..."
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
plan no cambia nada, así que puedes lanzarlo tantas veces como quieras. Fíjate siempre en la última línea: si aparece algo en «to destroy» que no esperabas, para y revisa. Los valores (known after apply) son los que todavía no existen porque los genera Fabric al crear el recurso.
El ciclo de trabajo
| Comando | Qué hace |
|---|---|
terraform init | Descarga los providers y configura el backend |
terraform plan | Calcula la diferencia y te la enseña, sin tocar nada |
terraform apply | Ejecuta los cambios y actualiza el estado |
terraform destroy | Elimina todo lo que gestiona esa configuración |
Mientras desarrollas, lo que más vas a lanzar es plan y apply; init vuelve a hacer falta cuando cambias de provider o de backend. En un equipo, lo normal es que el plan salga en la pull request y el apply lo ejecute un pipeline al aprobarla.
Para la gente de datos hay una forma fácil de recordarlo: apply se comporta como un MERGE, no como un INSERT. Puedes lanzarlo diez veces y sigues teniendo los mismos workspaces.
¿Y si alguien toca el portal a mano?
Alguien entra al portal y quita un rol «un momentito, para probar». La próxima vez que haces plan, Terraform consulta la realidad, la compara con el estado y te enseña ese cambio como algo que va a devolver a su sitio.
A eso se le llama drift. En la demo lo provocamos a propósito quitando el grupo Viewer de ventas-silver-prod, porque es justo el tipo de cambio que sin Terraform nadie ve hasta que algo falla.
El provider de Terraform para Microsoft Fabric
El provider oficial de Microsoft es de código abierto y saca versiones a menudo, así que fija la versión con ~> para que no te cambie por sorpresa. Los recursos que están en preview necesitan preview = true en el bloque provider. Y algo que conviene saber antes de llevarlo a un cliente: según su propio README, no está cubierto por las políticas de soporte de Microsoft Fabric; los problemas se reportan como issues en su repositorio de GitHub.
Con él puedes crear estructura (workspaces, roles, carpetas, dominios y, con azurerm, la capacity), elementos de datos (lakehouse, warehouse, notebook, pipeline, modelo semántico, informe) y piezas de plataforma (integración con Git, deployment pipelines, environments de Spark, conexiones). La lista completa está en el Terraform Registry y cambia con cada versión.
La autenticación es donde más tiempo se pierde la primera vez. Necesitas un service principal en Entra ID, que es la identidad con la que Terraform se presenta. En el portal de administración de Fabric, el tenant setting Service principals can use Fabric APIs tiene que estar activado para un grupo en el que esté ese service principal; si no, todo devuelve 401. Para crear workspaces, además, ese grupo tiene que poder crearlos (Create workspaces); si no, el primer workspace falla con FeatureNotAvailable. Y en Azure y Entra ID, permisos sobre la suscripción para crear la capacity y permisos para crear grupos.
Los secretos del service principal no van en los .tf: variables de entorno o Key Vault.
Lo que sí y lo que no hacemos con Terraform
La pregunta que siempre llega en las charlas es si esto sustituye a los deployment pipelines. No: cubren cosas distintas.
Nosotros usamos Terraform para lo que sostiene el proyecto: workspaces y capacities, roles y grupos, carpetas, dominios y la conexión de cada workspace con Git. El contenido (notebooks, modelos semánticos, informes, pipelines) y su promoción entre entornos lo dejamos para fabric-cicd, los deployment pipelines o la integración con Git.
Lo que nos encontramos preparando la demo
Algunas cosas no estaban tan claras leyendo la documentación:
- El ID de la capacity.
azurermcrea la capacity y te devuelve su ID de recurso de Azure, pero un workspace de Fabric espera el GUID de Fabric. Me costó un plan fallido; lo resolví con una data source de Fabric que busca la capacity por nombre. Te lo enseño en la parte práctica. - La capacity pausada. Con la capacity en pausa, el provider de Fabric no deja hacer ni
plan, niapply, nidestroy. Hay que reanudarla y esperar a que esté Active antes de lanzar nada. - PowerShell 5.1. Un argumento como
-var-file=environments/ventas.tfvarssin comillas se parte en dos y Terraform responde con un «Too many command line arguments» que no ayuda nada.
Y unos cuantos errores típicos cuando empiezas: dejar el estado solo en tu portátil, no mirar los -/+ del plan (recrear un workspace es perder lo que tenía dentro) y renombrar un recurso en el código sin usar el bloque moved, que para Terraform equivale a borrar uno y crear otro.
Si quieres empezar
No intentes automatizar toda la plataforma de golpe. Instala Terraform (winget install HashiCorp.Terraform), crea un fichero .tf con un único workspace de prueba, como el de arriba, y lanza terraform plan antes de aplicar nada. Después haz el apply, cambia algo en el código, vuelve a mirar el plan y fíjate en cómo se actualiza el estado. Cuando termines, destroy sobre ese workspace de prueba (y solo sobre ese) deja todo como estaba.
Con un ejemplo pequeño se entiende el ciclo mucho mejor que copiando veinte recursos de golpe.
En el siguiente artículo lo vemos en marcha: una capacity F2, nueve workspaces, 36 grupos y 36 asignaciones de roles, 84 recursos en un solo apply, con el vídeo de la demo.
Pris, mi gata, lleva años aplicando infraestructura declarativa: ella declara que el sofá es suyo y la realidad converge sola.
Enlaces de interés
¿Te ha servido? Aplaude, sin cuenta. Puedes hacerlo varias veces.
Comentarios