🔌 Configuración del provider azurerm¶
1. Sintaxis básica y configuración del curso¶
La configuración mínima contra Azure real es un bloque con features {}, obligatorio desde la versión 2.0 del provider, y, en azurerm 4.x, la suscripción:
Para el emulador se añaden dos argumentos. Este es el bloque que usarás en todo el curso:
# Topaz
provider "azurerm" {
features {}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
| Argumento | Qué hace | Azure real |
|---|---|---|
features {} |
Bloque anidado obligatorio; ajusta comportamientos globales (sección 3.3) | Igual |
metadata_host |
Host del que el provider descarga /metadata/endpoints para saber dónde están ARM, la autoridad de tokens, Storage, etc. Es lo que hace que Terraform hable con el emulador y no con Azure |
Se omite (usa management.azure.com) |
resource_provider_registrations |
Qué resource providers de ARM registra el provider al arrancar. Topaz no implementa ese registro, así que "none" |
Se omite (valor "core"). Sustituye a skip_provider_registration de la 3.x |
subscription_id |
Suscripción de trabajo. Obligatorio en 4.x | Tu suscripción |
🚨 ⚠️ Error común. Omitir
features {}produce, literalmente: Error: Insufficient features blocks — At least 1 "features" blocks are required. Material antiguo cita missing required argument "features"; el texto cambió, la causa es la misma.🔷 En Topaz. Azure CLI usa exactamente el mismo mecanismo: cuando registraste la nube con
az cloud register --name Topaz --endpoint-resource-manager ...(o desde la URL de metadatos), le diste la misma información quemetadata_hostle da al provider. Dos clientes, un solo punto de descubrimiento:curl -sk https://topaz.local.dev:8899/metadata/endpoints?api-version=2022-09-01 | jqte muestra lo que ambos leen.
2. Métodos de autenticación¶
El provider decide cómo autenticarse según lo que encuentre configurado, con esta prioridad: argumentos explícitos del bloque, variables de entorno ARM_*, y, si nada indica otra cosa, la sesión de Azure CLI. Los métodos disponibles son cuatro:
| Método | Para | Topaz |
|---|---|---|
Azure CLI (az login) |
Desarrollo, aprendizaje | ✅ El método del curso |
| Entidad de servicio (secreto, certificado u OIDC) | Pipelines CI/CD, producción | ❌ No hay Entra ID en el emulador |
| Identidad administrada | Terraform ejecutándose dentro de Azure | ❌ Requiere un recurso real de Azure |
Variables ARM_* |
Cualquier método, sin tocar el .tf |
✅ Para metadata_host y compañía |
2.1. Azure CLI (el método del laboratorio)¶
✅ Pasos
El provider reutiliza el token de la CLI: no hay que escribir ninguna credencial en elaz cloud set --name Topaz # Azure real: az cloud set --name AzureCloud az login --use-device-code # usuario topazadmin az account show --query '{cloud:environmentName, sub:id, user:user.name}' -o json # Esperado: "cloud": "Topaz", "sub": "00000000-0000-0000-0000-000000000001".tf. Si tienes varias suscripciones en Azure real,az account set --subscription "<nombre-o-id>"antes de trabajar.🔷 En Topaz. Los tokens del emulador caducan pronto. Si un
planque ayer funcionaba falla con401o obtaining Authorization Token from the Azure CLI, no es tu código: repiteaz login. Y si recreaste el contenedor, la sesión anterior no vale: certificado →az login→ Terraform, en ese orden.
2.2. Entidad de servicio (Azure real)¶
Una identidad de aplicación con permisos acotados y sin interacción humana. Es el estándar en pipelines. El bloque provider no cambia; las credenciales entran por el entorno:
# Crear la entidad (una vez, con permisos de administrador)
az ad sp create-for-rbac --name sp-terraform-curso --role Contributor \
--scopes /subscriptions/<id-suscripcion>
# En el runner del pipeline (variables protegidas, nunca en el repositorio)
export ARM_SUBSCRIPTION_ID="..."
export ARM_TENANT_ID="..."
export ARM_CLIENT_ID="..."
export ARM_CLIENT_SECRET="..." # o ARM_CLIENT_CERTIFICATE_PATH, o ARM_USE_OIDC=true
🚨 ⚠️ Seguridad.
ARM_CLIENT_SECRETnunca va en un.tf, un.tfvarsni en Git. Usa los secretos protegidos de tu plataforma CI/CD, Azure Key Vault o, mejor aún, OIDC (federación de identidad): sin secreto que rotar ni filtrar. En Topaz nada de esto aplica: el usuariotopazadminlo puede todo y no existen entidades de servicio.
2.3. Identidad administrada (Azure real)¶
Cuando Terraform corre dentro de un recurso de Azure con identidad asignada (VM, App Service, agente de DevOps en Azure, Cloud Shell), no hace falta ninguna credencial:
export ARM_USE_MSI=true
export ARM_SUBSCRIPTION_ID="..."
# Requisito: la identidad del recurso tiene un rol (p. ej. Contributor) en el ámbito donde despliega
az role assignment create --assignee <principal-id> --role Contributor --scope /subscriptions/<id>
2.4. Variables ARM_*: el mismo código en Topaz y en Azure¶
Todo argumento del provider tiene equivalente en variable de entorno. Si exportas las de Topaz, el bloque queda idéntico al de producción:
# ~/.bashrc del laboratorio
export ARM_METADATA_HOSTNAME=topaz.local.dev:8899
export ARM_RESOURCE_PROVIDER_REGISTRATIONS=none
export ARM_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000001
Los argumentos explícitos en el bloque ganan a las variables de entorno, así que si mantienes las tres líneas en el .tf las variables no tienen efecto: elige un estilo y sé consistente.
3. Configuración avanzada¶
3.1. Nubes distintas de la pública: environment y metadata_host¶
El provider soporta dos formas de apuntar a algo que no sea Azure público:
# Nubes soberanas conocidas: environment
provider "azurerm" {
features {}
environment = "usgovernment" # o "china". Azure Alemania cerró en 2021
subscription_id = "..."
}
# Nubes personalizadas (Azure Stack Hub, emuladores): metadata_host
provider "azurerm" {
features {}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
environment selecciona un conjunto de endpoints precargado; metadata_host los descubre en tiempo de ejecución. Son excluyentes: si defines metadata_host, no pongas environment. Topaz es, a efectos del provider, una nube más: por eso el resto de la configuración es idéntica.
3.2. Varios providers: alias¶
Para desplegar en varias suscripciones o nubes desde una misma configuración. En Topaz solo hay una suscripción, pero la mecánica se practica igual:
provider "azurerm" { # por defecto (sin alias)
features {}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
provider "azurerm" {
alias = "compartido" # en Azure real: otra subscription_id (hub, conectividad...)
features {}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
resource "azurerm_resource_group" "app" {
name = "rg-alias-app"
location = "eastus" # usa el provider por defecto
}
resource "azurerm_resource_group" "hub" {
provider = azurerm.compartido # TIPO.ALIAS, sin comillas
name = "rg-alias-hub"
location = "eastus"
}
Un alias no puede configurarse con variables de entorno distintas por instancia: si dos alias necesitan credenciales diferentes en Azure real, esas diferencias van en el bloque (o en un módulo con providers = { azurerm = azurerm.compartido }).
3.3. El bloque features no está vacío¶
Contiene sub-bloques que cambian el comportamiento global de familias de recursos. Estos son los más útiles, con los valores por defecto comentados:
provider "azurerm" {
features {
resource_group {
# false: destroy borra el grupo aunque contenga recursos creados fuera de Terraform
prevent_deletion_if_contains_resources = false # defecto: true
}
key_vault {
# Key Vault tiene borrado suave; estas dos hacen que destroy lo elimine del todo
purge_soft_delete_on_destroy = true # defecto: true
recover_soft_deleted_key_vaults = true # defecto: true
}
virtual_machine {
delete_os_disk_on_deletion = true # defecto: true
}
template_deployment {
delete_nested_items_during_deletion = true # defecto: true
}
}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
🔷 En Topaz.
prevent_deletion_if_contains_resources = falsees práctico en el laboratorio: si creaste algo conazdentro de un grupo gestionado por Terraform,destroyno se detendrá. Los dekey_vaultdependen de que el emulador implemente el borrado suave; si undestroyde Key Vault falla en Topaz, prueba conpurge_soft_delete_on_destroy = false.
3.4. Otros argumentos que conviene conocer¶
storage_use_azuread = true: el provider accede al plano de datos de Storage con el token de Entra ID en vez de con las claves de la cuenta. En Topaz déjalo por defecto.partner_id: GUID de atribución para partners de Microsoft. Sin efecto en el emulador.disable_terraform_partner_id = true: evita que el provider añada su propio identificador a las peticiones.
4. Relación con terraform y resource¶
4.1. El bloque terraform dice qué versión; el bloque provider dice cómo conectar¶
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0" # >= 4.0.0 y < 5.0.0; metadata_host requiere 4.x
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "azurerm" {
features {}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
El nombre local azurerm de required_providers es el que enlaza con la etiqueta provider "azurerm" y con el prefijo azurerm_ de cada recurso. Tras init, terraform providers muestra qué versión exacta se instaló y .terraform.lock.hcl la fija para todo el equipo.
🚨 ⚠️ Versiones. Sin restricción,
initinstala la última versión; un salto de mayor (3.x → 4.x) cambió argumentos del propio provider (skip_provider_registrationdesapareció,subscription_idpasó a ser obligatorio). Usa siempre~>y actualiza de forma consciente conterraform init -upgrade.
4.2. Los recursos usan el provider implícita o explícitamente¶
# Un solo provider: uso implícito
resource "azurerm_resource_group" "app" {
name = "rg-app-dev-001"
location = "eastus"
}
# Varios providers: el meta-argumento provider elige
resource "azurerm_resource_group" "hub" {
provider = azurerm.compartido
name = "rg-hub-dev-001"
location = "eastus"
}
5. LAB: verificar el provider¶
Un proyecto mínimo para comprobar que el provider autentica, descubre los endpoints del emulador y crea un recurso. Usa random_string para el nombre único: la función timestamp() que aparece en algunos tutoriales cambia en cada ejecución y obliga a reemplazar el recurso en cada apply.
Paso 1. Proyecto¶
# archivo: main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
random = { source = "hashicorp/random", version = "~> 3.6" }
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}
metadata_host = "topaz.local.dev:8899"
resource_provider_registrations = "none"
subscription_id = "00000000-0000-0000-0000-000000000001"
}
variable "ubicacion" {
type = string
default = "eastus"
}
resource "random_string" "sufijo" {
length = 4
upper = false
special = false
}
resource "azurerm_resource_group" "verificacion" {
name = "rg-provider-lab-${random_string.sufijo.result}"
location = var.ubicacion
tags = {
entorno = "verificacion"
proposito = "provider-config"
}
lifecycle {
ignore_changes = [tags] # Topaz no devuelve las tags del grupo
}
}
output "grupo" {
value = azurerm_resource_group.verificacion.name
}
output "id_grupo" {
value = azurerm_resource_group.verificacion.id
}
Paso 2. init y comprobación de versiones¶
terraform init
terraform providers
# Providers required by configuration:
# ├── provider[registry.terraform.io/hashicorp/azurerm] ~> 4.0
# └── provider[registry.terraform.io/hashicorp/random] ~> 3.6
grep -A1 'hashicorp/azurerm' .terraform.lock.hcl # versión exacta instalada
init no toca el emulador: si falla aquí, es Internet o el bloque terraform.
Paso 3. plan: primer contacto con Topaz¶
Si el plan llega a listar los recursos, el provider ha autenticado y ha leído los endpoints del emulador. Para verlo con tus propios ojos:
TF_LOG=DEBUG terraform plan 2>&1 | grep -m3 -iE 'metadata|topaz.local.dev'
# Verás la petición a https://topaz.local.dev:8899/metadata/endpoints y las llamadas a ARM del emulador
Paso 4. apply y verificación cruzada¶
terraform apply -auto-approve
# Apply complete! Resources: 2 added...
# grupo = "rg-provider-lab-k3x9"
RG=$(terraform output -raw grupo)
az group show -n $RG --query '{nombre:name, ubicacion:location, estado:properties.provisioningState}' -o json
terraform output -raw id_grupo # empieza por /subscriptions/00000000-0000-0000-0000-000000000001/
terraform plan # No changes.
🔷 En Topaz no hay Portal. La verificación independiente es Azure CLI: otro cliente, misma API. El
idcon la suscripción...0001es la prueba de que no has tocado Azure real.
Paso 5. Provoca los tres errores clásicos¶
# a) Comenta features {...} → plan: Insufficient features blocks
# b) Comenta metadata_host → plan: SubscriptionNotFound (¡estás hablando con Azure real!)
# c) Comenta subscription_id → plan: subscription_id is a required provider property
# Restaura el archivo tras cada prueba: terraform plan debe volver a decir "No changes."
Paso 6. Limpiar¶
terraform destroy -auto-approve
az group list --query "[?starts_with(name,'rg-provider-lab')].name" -o tsv # vacío
6. Preguntas frecuentes¶
¿Qué pasa si olvido
features {}?
Insufficient features blocks: At least 1 "features" blocks are required. Es obligatorio desde la 2.0, aunque esté vacío.¿Cómo sé a qué nube y suscripción apunta mi configuración?
Antes de ejecutar:az account show --query '{cloud:environmentName, sub:id}'y elmetadata_host/subscription_iddel bloque (oenv | grep ARM_). Tras unapply, cualquieridde recurso lleva la suscripción.terraform planno la muestra por sí mismo.¿Por qué
SubscriptionNotFoundsi la suscripción está bien escrita?
Faltametadata_host: Terraform ha ido a Azure real, donde la suscripción...0001no existe. Es el error más frecuente al copiar un provider de un tutorial.¿Puedo tener varios providers azurerm?
Sí, conalias, y eligiéndolo en cada recurso conprovider = azurerm.<alias>. En Topaz todos apuntan a la misma suscripción; en Azure real es la forma habitual de desplegar en varias.¿Es seguro poner
client_secreten un.tfvars?
No. Los.tfvarsacaban en Git con la misma facilidad que los.tf. Variables protegidas del pipeline, Key Vault u OIDC.¿Qué versión uso en producción?
~> 4.0(o más ajustada,~> 4.20), con el.terraform.lock.hclversionado. Nunca sin restricción.¿Qué cambia cuando pase de Topaz a Azure real?
Quitarmetadata_hostyresource_provider_registrations, poner tusubscription_idy haceraz cloud set --name AzureCloud && az login. El resto del código no se toca.
7. Buenas prácticas y errores comunes¶
✅ Buenas prácticas -
features {}siempre; úsalo para fijar comportamientos globales de forma explícita. - Provider enversions.tf: lo único que cambia entre Topaz y Azure real vive en un solo archivo. - Configuración del entorno por variablesARM_*cuando quieras un.tfportable. - Versión restringida (~>) y lock file en Git;init -upgradede forma deliberada. - En Azure real: entidad de servicio con mínimo privilegio, preferiblemente OIDC; secretos solo en el almacén del pipeline. - Documenta en un comentario qué método de autenticación usa cada entorno.🚨 ⚠️ Errores comunes - Sin
features→ Insufficient features blocks. - Sinmetadata_hosten Topaz → SubscriptionNotFound: has hablado con Azure real. - Sinsubscription_iden 4.x → subscription_id is a required provider property. -skip_provider_registrationcopiado de un ejemplo 3.x → Unsupported argument; ahora esresource_provider_registrations. - Sesión deazen otra nube →401; compruebaenvironmentName. - Certificado obsoleto tras recrear el contenedor → x509: certificate signed by unknown authority. - Secretos en el código: en Topaz no hay, pero el hábito de no escribirlos se adquiere ahora. -version = "latest"no existe; omitir la versión equivale a "la que toque hoy".
8. Referencias¶
- Provider azurerm: referencia de argumentos (
metadata_host,features,resource_provider_registrations) - Guía del bloque
features: todos los sub-bloques y sus valores por defecto - Autenticación con Azure CLI
- Autenticación con entidad de servicio (secreto) y con OIDC
- Autenticación con identidad administrada
- Guía de migración a azurerm 4.0 (
subscription_idobligatorio,resource_provider_registrations) - Configuración de providers y
alias(documentación de Terraform) - Restricciones de versión y
.terraform.lock.hcl - Recurso
random_string - Azure Local Emulator (Topaz): endpoints de metadatos y servicios soportados