Una de las mejores formas de aprender Hub & Spoke es desplegar una versión pequeña, entender las relaciones y después agregar servicios centrales.

En esta guía utilizarás Terraform para crear:

  • una VNet Hub;
  • dos VNets Spoke;
  • subnets;
  • peerings bidireccionales;
  • outputs para validar el resultado.

No vamos a agregar Azure Firewall todavía. Primero construiremos correctamente la capa de conectividad.

Arquitectura Hub and Spoke oficial de Microsoft utilizada en su serie de Terraform.

Imagen: Microsoft Learn, serie oficial Hub & Spoke con Terraform.

Arquitectura del laboratorio

Crearemos:

              Hub
          10.60.0.0/16
          /           \
         /             \
   Spoke App         Spoke Data
 10.61.0.0/16       10.62.0.0/16

Peerings:

hub-to-app
app-to-hub

hub-to-data
data-to-hub

Importante: qué NO resuelve todavía este laboratorio

Tener los peerings no significa que:

Spoke App → Spoke Data

funcione automáticamente.

VNet Peering no es transitivo.

Para tráfico spoke-to-spoke necesitas posteriormente diseñar routing mediante Azure Firewall, NVA, Virtual WAN u otro patrón apropiado.

El propósito del laboratorio es separar:

topología

de:

routing centralizado

Paso 1. Prepara la carpeta

Crea:

hub-spoke-lab/
├── versions.tf
├── variables.tf
├── network.tf
└── outputs.tf

Paso 2. Configura Terraform y AzureRM

versions.tf:

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

provider "azurerm" {
  features {}
}

Antes de usar una restricción de versión en producción, valida la versión aprobada por tu organización.

Paso 3. Define variables

variables.tf:

variable "location" {
  description = "Azure region for the lab."
  type        = string
  default     = "mexicocentral"
}

variable "resource_group_name" {
  description = "Resource group name."
  type        = string
  default     = "rg-hub-spoke-lab"
}

Si tu suscripción no soporta el recurso requerido en esa región, utiliza otra región disponible.

Paso 4. Crea el Resource Group

En network.tf:

resource "azurerm_resource_group" "network" {
  name     = var.resource_group_name
  location = var.location
}

Paso 5. Crea la VNet Hub

resource "azurerm_virtual_network" "hub" {
  name                = "vnet-hub"
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name
  address_space       = ["10.60.0.0/16"]
}

resource "azurerm_subnet" "hub_shared" {
  name                 = "snet-shared"
  resource_group_name  = azurerm_resource_group.network.name
  virtual_network_name = azurerm_virtual_network.hub.name
  address_prefixes     = ["10.60.10.0/24"]
}

En una arquitectura real podrías añadir subnets dedicadas para:

  • Azure Firewall;
  • gateways;
  • DNS;
  • Bastion;
  • otros servicios compartidos.

No crees todas por adelantado sin un diseño.

Paso 6. Crea Spoke App

resource "azurerm_virtual_network" "app" {
  name                = "vnet-spoke-app"
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name
  address_space       = ["10.61.0.0/16"]
}

resource "azurerm_subnet" "app" {
  name                 = "snet-app"
  resource_group_name  = azurerm_resource_group.network.name
  virtual_network_name = azurerm_virtual_network.app.name
  address_prefixes     = ["10.61.10.0/24"]
}

Paso 7. Crea Spoke Data

resource "azurerm_virtual_network" "data" {
  name                = "vnet-spoke-data"
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name
  address_space       = ["10.62.0.0/16"]
}

resource "azurerm_subnet" "data" {
  name                 = "snet-data"
  resource_group_name  = azurerm_resource_group.network.name
  virtual_network_name = azurerm_virtual_network.data.name
  address_prefixes     = ["10.62.10.0/24"]
}

Paso 8. Crea los peerings Hub ↔ App

resource "azurerm_virtual_network_peering" "hub_to_app" {
  name                      = "hub-to-app"
  resource_group_name       = azurerm_resource_group.network.name
  virtual_network_name      = azurerm_virtual_network.hub.name
  remote_virtual_network_id = azurerm_virtual_network.app.id

  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
}

resource "azurerm_virtual_network_peering" "app_to_hub" {
  name                      = "app-to-hub"
  resource_group_name       = azurerm_resource_group.network.name
  virtual_network_name      = azurerm_virtual_network.app.name
  remote_virtual_network_id = azurerm_virtual_network.hub.id

  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
}

Habilitamos allow_forwarded_traffic porque esta topología está pensada para evolucionar hacia un hub con routing central.

Por sí solo, ese flag no convierte al hub en router.

Paso 9. Crea los peerings Hub ↔ Data

resource "azurerm_virtual_network_peering" "hub_to_data" {
  name                      = "hub-to-data"
  resource_group_name       = azurerm_resource_group.network.name
  virtual_network_name      = azurerm_virtual_network.hub.name
  remote_virtual_network_id = azurerm_virtual_network.data.id

  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
}

resource "azurerm_virtual_network_peering" "data_to_hub" {
  name                      = "data-to-hub"
  resource_group_name       = azurerm_resource_group.network.name
  virtual_network_name      = azurerm_virtual_network.data.name
  remote_virtual_network_id = azurerm_virtual_network.hub.id

  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
}

Paso 10. Agrega outputs

outputs.tf:

output "hub_id" {
  value = azurerm_virtual_network.hub.id
}

output "app_spoke_id" {
  value = azurerm_virtual_network.app.id
}

output "data_spoke_id" {
  value = azurerm_virtual_network.data.id
}

Paso 11. Formatea y valida

Ejecuta:

terraform fmt -recursive
terraform init
terraform validate

Después:

terraform plan

Revisa que Terraform cree únicamente lo esperado.

No ejecutes apply si aparecen reemplazos o eliminaciones que no comprendes.

Paso 12. Despliega

terraform apply

Confirma el plan.

Al terminar:

terraform output

Paso 13. Valida en Azure

En Portal:

Virtual networks → vnet-hub → Peerings

Debes ver:

hub-to-app  Connected
hub-to-data Connected

En cada spoke:

app-to-hub  Connected
data-to-hub Connected

Paso 14. Comprueba rutas

Si despliegas VMs de prueba posteriormente, las rutas efectivas de cada spoke incluirán el prefijo del hub.

Pero no verás automáticamente el prefijo del otro spoke como una ruta transitable a través del hub.

Eso es correcto para esta etapa.

Evolución 1: Azure Firewall

El siguiente diseño puede ser:

Spoke App
    │
    │ UDR
    ▼
Azure Firewall
    │
    ▼
Spoke Data / Internet / On-premises

Necesitarías agregar:

  • AzureFirewallSubnet;
  • Azure Firewall;
  • Firewall Policy;
  • route tables;
  • UDR;
  • reglas.

Evolución 2: conectividad híbrida

Si incorporas:

VPN Gateway

o:

ExpressRoute Gateway

debes revisar:

allow_gateway_transit
use_remote_gateways

además del routing de retorno.

Evolución 3: separar suscripciones

En una landing zone real puede existir:

Connectivity subscription
 └── Hub

Application subscription
 └── Spoke App

Data subscription
 └── Spoke Data

Terraform puede manejar recursos en distintas subscriptions mediante providers aliasados y una estrategia adecuada de identidad.

No confundas el laboratorio de una sola suscripción con el diseño enterprise final.

Remote State

No almacenes un state productivo únicamente en tu laptop.

Cuando conviertas este laboratorio en infraestructura compartida, utiliza un backend remoto y un flujo controlado por pipeline.

Puedes enlazar esta guía con tu contenido de Terraform Remote State en Azure.

Errores frecuentes

Crear un solo peering

Aunque el portal puede facilitar ambos sentidos, conceptualmente existen relaciones de peering en cada dirección.

CIDR superpuesto

Terraform puede crear muchos recursos, pero no arregla un mal plan de IPs.

Esperar transitividad

allow_forwarded_traffic = true no crea un router.

Un state para toda la empresa

Separa states por lifecycle, ownership y blast radius.

Ejecutar apply desde cualquier laptop

Para producción evoluciona hacia Pull Requests y CI/CD.

Checklist

  • Terraform fmt limpio.
  • validate exitoso.
  • Plan revisado.
  • Hub creado.
  • Dos spokes creados.
  • Cuatro peerings Connected.
  • CIDRs documentados.
  • Sin traslapes.
  • Routing central pendiente documentado.
  • State remoto considerado antes de producción.

Limpieza

Terraform facilita retirar el laboratorio:

terraform plan -destroy
terraform destroy

Antes de confirmar verifica que el state no administre recursos compartidos.

Fuentes oficiales