🌐 Redes virtuales¶
1. Anatomía de una red virtual¶
rg-red-001
├── vnet-app 10.0.0.0/16 ─────────────────────── peering ──────┐
│ ├── snet-web 10.0.1.0/24 ◄── nsg-web (80/443 desde Internet)
│ └── snet-backend 10.0.2.0/24 ◄── nsg-backend (8080 solo desde snet-web; resto de la vnet denegado)
│ │
└── vnet-hub 10.1.0.0/16 ◄────────────────────── peering ──────┘
└── snet-shared 10.1.1.0/24
| Concepto | Qué es | Recurso Terraform | En Topaz |
|---|---|---|---|
| Red virtual (VNet) | Espacio de direcciones privado y aislado. Puede tener varios rangos (address_space es una lista) |
azurerm_virtual_network |
✅ |
| Subred | División del espacio de la vnet. Es la unidad a la que se asocian NSG, tablas de rutas y service endpoints | azurerm_subnet |
✅ |
| NSG | Lista de reglas permitir/denegar por prioridad, en capa 4. Se asocia a subredes o a NIC | azurerm_network_security_group + azurerm_subnet_network_security_group_association |
✅ solo reglas inline |
| Peering | Enlace privado entre dos vnets por la red troncal de Azure, sin gateways ni cifrado adicional. Siempre se declara en los dos sentidos | azurerm_virtual_network_peering ×2 |
⚠️ depende de la versión: interruptor desplegar_peering |
Planificar las direcciones¶
Tres reglas que ahorran rediseños:
- Azure reserva 5 direcciones por subred: las cuatro primeras (red, gateway, dos de DNS) y la última (broadcast). Un /24 da 251 IPs útiles; el mínimo permitido es /29 (3 útiles).
- Las vnets que vayan a conectarse por peering no pueden solaparse. Por eso aquí vnet-app usa 10.0.0.0/16 y vnet-hub usa 10.1.0.0/16. Reserva rangos por entorno o región desde el principio.
- Deja hueco. Con un /16 caben 256 subredes /24; usar solo dos hoy no es desperdicio, es margen.
Terraform tiene una función para calcular subredes sin errores de aritmética: cidrsubnet("10.0.0.0/16", 8, 1) devuelve 10.0.1.0/24 (añade 8 bits a la máscara y toma la subred número 1). Pruébala con terraform console.
2. Preparar el directorio y las variables¶
mkdir -p ~/tf-red && cd ~/tf-red
cp ~/tf-intro/providers.tf . # solo este archivo: fija azurerm ~> 4.0 y apunta a Topaz
ls # providers.tf
# variables.tf
variable "subredes" {
type = map(string)
description = "Subredes de vnet-app: nombre lógico => prefijo CIDR"
default = {
web = "10.0.1.0/24"
backend = "10.0.2.0/24"
}
validation {
condition = alltrue([for p in values(var.subredes) : can(cidrhost(p, 0))])
error_message = "Todos los valores deben ser CIDR válidos, como 10.0.1.0/24."
}
}
variable "desplegar_peering" {
type = bool
description = "Crear el peering entre vnet-app y vnet-hub (ponlo a false si tu Topaz no implementa virtualNetworkPeerings)"
default = true
}
Fíjate en que la clave del map (web, backend) es un nombre lógico, no el nombre del recurso en Azure. Ese nombre lo construiremos con "snet-${each.key}". La diferencia importa cuando llegue el momento de renombrar o borrar una subred sin tocar las demás.
3. Red virtual y subredes con for_each¶
# red.tf
resource "azurerm_resource_group" "red" {
name = "rg-red-001"
location = "eastus"
tags = { entorno = "lab", gestion = "terraform" }
lifecycle { ignore_changes = [tags] } # Topaz no devuelve las tags del grupo
}
resource "azurerm_virtual_network" "app" {
name = "vnet-app"
location = azurerm_resource_group.red.location
resource_group_name = azurerm_resource_group.red.name
address_space = ["10.0.0.0/16"]
lifecycle {
ignore_changes = [private_endpoint_vnet_policies] # Topaz no devuelve este atributo
}
}
resource "azurerm_subnet" "app" {
for_each = var.subredes # una instancia por clave del map
name = "snet-${each.key}"
resource_group_name = azurerm_resource_group.red.name
virtual_network_name = azurerm_virtual_network.app.name
address_prefixes = [each.value]
}
count |
for_each |
|
|---|---|---|
| Dirección de la instancia | azurerm_subnet.app[0], [1]… |
azurerm_subnet.app["web"], ["backend"] |
| Si borras el primer elemento | Todos los demás cambian de índice: Terraform los destruye y recrea | Solo desaparece esa clave; el resto no se toca |
| Cuándo usarlo | Interruptor 0/1 (como la VM de la página 2) o N copias idénticas | Colecciones con identidad propia: subredes, reglas, usuarios… |
4. Grupos de seguridad a nivel de subred¶
En la página 2 el NSG se asoció a la NIC: filtra una máquina. Asociado a la subred filtra todo lo que haya dentro, presente y futuro, y es la forma habitual de expresar "el nivel web acepta 80/443 de Internet; el nivel backend solo acepta 8080 desde el nivel web". Si hay NSG en ambos sitios, el tráfico entrante debe pasar los dos.
# nsg.tf
resource "azurerm_network_security_group" "web" {
name = "nsg-web"
location = azurerm_resource_group.red.location
resource_group_name = azurerm_resource_group.red.name
security_rule {
name = "Allow-Web-Inbound"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = ["80", "443"] # varios puertos en una regla
source_address_prefix = "Internet" # service tag: cualquier origen público
destination_address_prefix = "*"
}
}
resource "azurerm_network_security_group" "backend" {
name = "nsg-backend"
location = azurerm_resource_group.red.location
resource_group_name = azurerm_resource_group.red.name
security_rule {
name = "Allow-App-From-Web"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "8080"
source_address_prefix = var.subredes["web"] # solo desde 10.0.1.0/24
destination_address_prefix = "*"
}
security_rule {
name = "Deny-VNet-Other"
priority = 4000 # después de los Allow, antes de las reglas por defecto (65000+)
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "VirtualNetwork" # service tag: toda la vnet y las vnets con peering
destination_address_prefix = "*"
}
}
resource "azurerm_subnet_network_security_group_association" "web" {
subnet_id = azurerm_subnet.app["web"].id
network_security_group_id = azurerm_network_security_group.web.id
}
resource "azurerm_subnet_network_security_group_association" "backend" {
subnet_id = azurerm_subnet.app["backend"].id
network_security_group_id = azurerm_network_security_group.backend.id
}
⚠️ Sin la regla
Deny, el backend no está aislado. Todo NSG trae tres reglas por defecto de entrada: AllowVnetInBound (65000), AllowAzureLoadBalancerInBound (65001) y DenyAllInBound (65500). La primera permite cualquier tráfico desde la vnet, así que una regla que solo permite 8080 desde web no impide que web (o cualquier otra subred) llegue al 22 del backend. La reglaDeny-VNet-Othera prioridad 4000 es la que cierra realmente la puerta. Compruébalo en Azure real conaz network nic list-effective-nsg.
| Service tag | Significa |
|---|---|
Internet |
Todo lo que no es espacio privado de Azure. Preferible a "*" porque documenta la intención |
VirtualNetwork |
El espacio de la vnet, las vnets con peering y las redes locales conectadas |
AzureLoadBalancer |
Sondas de estado del balanceador (168.63.129.16). No lo bloquees |
Storage, Sql, AzureCloud… |
Rangos públicos de servicios de Azure, mantenidos por Microsoft. Lista completa: az network list-service-tags -l eastus |
🔷 En Topaz. Las reglas van inline porque el emulador no implementa
azurerm_network_security_rule(página 2). La asociación subred-NSG es unPUTsobre la propia subred, el mismo endpoint que la crea, así que funciona. El emulador guarda las reglas y las devuelve conaz network nsg show, pero no evalúa tráfico: la comprobación de que el backend está aislado solo puede hacerse en Azure real.
5. Peering entre redes virtuales¶
El original declaraba un peering hacia una vnet2 que no existía y en un solo sentido. Un peering es un acuerdo entre dos vnets: hasta que ambos lados están creados, su estado es Initiated y no pasa tráfico. Aquí se declara la segunda vnet y las dos direcciones.
# peering.tf
resource "azurerm_virtual_network" "hub" {
name = "vnet-hub"
location = azurerm_resource_group.red.location
resource_group_name = azurerm_resource_group.red.name
address_space = ["10.1.0.0/16"] # NO puede solaparse con 10.0.0.0/16
lifecycle {
ignore_changes = [private_endpoint_vnet_policies]
}
}
resource "azurerm_subnet" "shared" {
name = "snet-shared"
resource_group_name = azurerm_resource_group.red.name
virtual_network_name = azurerm_virtual_network.hub.name
address_prefixes = ["10.1.1.0/24"]
}
resource "azurerm_virtual_network_peering" "app_to_hub" {
count = var.desplegar_peering ? 1 : 0
name = "peer-app-to-hub"
resource_group_name = azurerm_resource_group.red.name
virtual_network_name = azurerm_virtual_network.app.name
remote_virtual_network_id = azurerm_virtual_network.hub.id
allow_virtual_network_access = true # las VMs de una vnet ven las de la otra
allow_forwarded_traffic = false # true solo si hay un NVA/firewall que reenvía
allow_gateway_transit = false # true en el hub si tiene VPN/ExpressRoute gateway
use_remote_gateways = false # true en los spokes que usan el gateway del hub
}
resource "azurerm_virtual_network_peering" "hub_to_app" {
count = var.desplegar_peering ? 1 : 0
name = "peer-hub-to-app"
resource_group_name = azurerm_resource_group.red.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 = false
allow_gateway_transit = false
use_remote_gateways = false
}
| Propiedad del peering | Consecuencia práctica |
|---|---|
| No es transitivo | Si A↔B y B↔C, A no ve a C. Por eso la topología hub-and-spoke pone un firewall o router en el hub y activa allow_forwarded_traffic |
| Sin solapamiento | Dos vnets con rangos que se cruzan no pueden emparejarse: VnetAddressSpaceOverlaps |
| Funciona entre regiones y suscripciones | Global peering entre regiones; entre suscripciones necesitas permisos en ambas |
| Tiene coste por GB | Pequeño en la misma región, mayor entre regiones. Crear el peering no cuesta; el tráfico sí |
🔷 En Topaz. La cobertura de
virtualNetworkPeeringsvaría según la versión del emulador. Si elapplydevuelve EndpointNotFound enPUT .../virtualNetworkPeerings/peer-app-to-hub, no es un error tuyo: pondesplegar_peering = falseenterraform.tfvars, vuelve a aplicar y anota la limitación. Elplancontruesigue siendo válido para revisar el código, igual que hicimos con la VM. Si el peering se crea pero cadaplanpropone cambios en él, añade el atributo señalado a unignore_changes.
6. Outputs¶
# outputs.tf
output "vnet_app_id" {
description = "ID de vnet-app (lo usarán otras páginas para desplegar dentro)"
value = azurerm_virtual_network.app.id
}
output "subredes" {
description = "Nombre en Azure y prefijo de cada subred de vnet-app"
value = {
for clave, s in azurerm_subnet.app : clave => {
nombre = s.name
prefijo = s.address_prefixes[0]
id = s.id
}
}
}
output "peerings" {
description = "IDs de los peerings, lista vacía si desplegar_peering = false"
value = concat(
azurerm_virtual_network_peering.app_to_hub[*].id,
azurerm_virtual_network_peering.hub_to_app[*].id
)
}
El output subredes recorre el recurso con for_each y construye un map nuevo: es el patrón para exponer colecciones sin enumerar cada elemento a mano. Cuando añadas una tercera subred (sección 3.7) aparecerá en el output sin tocar este archivo.
7. Despliegue en Topaz¶
ls # nsg.tf outputs.tf peering.tf providers.tf red.tf variables.tf
terraform init # debe instalar azurerm v4.x; si instala 5.x falta providers.tf
terraform validate
terraform plan
# Plan: 12 to add, 0 to change, 0 to destroy.
# grupo, vnet-app, 2 subredes, 2 NSG, 2 asociaciones, vnet-hub, snet-shared, 2 peerings
terraform apply -auto-approve
terraform output subredes
# {
# "backend" = { "id" = ".../snet-backend", "nombre" = "snet-backend", "prefijo" = "10.0.2.0/24" }
# "web" = { "id" = ".../snet-web", "nombre" = "snet-web", "prefijo" = "10.0.1.0/24" }
# }
# Verificar con la CLI
az network vnet list -g rg-red-001 --query "[].{vnet:name, espacio:addressSpace.addressPrefixes[0]}" -o table
az network vnet subnet list -g rg-red-001 --vnet-name vnet-app \
--query "[].{subred:name, prefijo:addressPrefix, nsg:networkSecurityGroup.id}" -o table
az network nsg show -g rg-red-001 -n nsg-backend \
--query "securityRules[].{regla:name, prio:priority, accion:access, origen:sourceAddressPrefix, puerto:destinationPortRange}" -o table
az network vnet peering list -g rg-red-001 --vnet-name vnet-app \
--query "[].{peering:name, estado:peeringState, remota:remoteVirtualNetwork.id}" -o table
terraform plan # No changes.
Si el último plan propone algún cambio, mira el atributo marcado con ~ o +. Con azurerm 4.x el candidato habitual en subredes es default_outbound_access_enabled: añádelo a un lifecycle { ignore_changes } del recurso azurerm_subnet.app y anótalo como diferencia del emulador.
Ejercicio: añadir una subred sin tocar las existentes¶
# terraform.tfvars
subredes = {
web = "10.0.1.0/24"
backend = "10.0.2.0/24"
datos = "10.0.3.0/24" # nueva
}
terraform plan
# + azurerm_subnet.app["datos"]
# Plan: 1 to add, 0 to change, 0 to destroy. ← web y backend intactas: esto es lo que aporta for_each
terraform apply -auto-approve
terraform output subredes # ya incluye "datos"
Ahora prueba la validación: cambia "10.0.3.0/24" por "10.0.3.0" y ejecuta plan. Falla antes de tocar el emulador con tu mensaje. Restaura el valor. Por último, quita la subred datos del tfvars: el plan mostrará 1 to destroy y nada más.
8. Qué cambia en Azure real¶
providers.tf: quitarmetadata_hostyresource_provider_registrations, poner tusubscription_id.- Quitar los
lifecycle { ignore_changes }del grupo y de las dos vnets: en Azure real esos atributos se devuelven y conviene vigilarlos. - El resto del código no cambia: reglas inline, asociaciones y peering funcionan igual. Si prefieres reglas como recursos independientes (
azurerm_network_security_rule), es una decisión de organización, no de compatibilidad; recuerda no mezclar las dos formas sobre el mismo NSG.
az login && az account set -s "<tu suscripción>"
terraform init -reconfigure
terraform apply -auto-approve # Plan: 12 to add; el peering pasa a "Connected" en segundos
az network vnet peering list -g rg-red-001 --vnet-name vnet-app --query "[].peeringState" -o tsv
# Connected
# Comprobar el aislamiento del backend (necesita una NIC dentro de snet-backend, p. ej. la VM de la página 2)
az network nic list-effective-nsg -g rg-red-001 -n <nic-en-backend> -o table
terraform destroy -auto-approve # las vnets no cuestan; el tráfico de peering sí
⚠️ No cambies
address_spacea la ligera. Ampliar el espacio de una vnet con peering activo obliga a resincronizar el peering (az network vnet peering sync), y reducirlo falla si alguna subred queda fuera. Terraform lo mostrará como actualización in-place, pero el cambio puede cortar conectividad unos segundos. Planifica los rangos con margen desde el principio (sección 3.1).
9. Errores comunes¶
⚠️ Solución de problemas
Mensaje o síntoma Causa y solución Finding latest version of hashicorp/azurerm e instala v5.xFalta providers.tf. Cópialo de~/tf-intro, borra.terraform/y.terraform.lock.hcl, repiteinitNetcfgInvalidSubnet / subnet is not within the address space El prefijo de la subred cae fuera del address_spacede la vnet (p. ej.10.1.xen10.0.0.0/16). Revisa elmapsubredesNetcfgSubnetRangesOverlap Dos subredes con rangos que se cruzan. Usa cidrsubnet()enterraform consolepara comprobarlosUnsupported attribute en azurerm_subnet.app.idCon for_eachse indexa por clave:azurerm_subnet.app["web"].idThe given key does not identify an element in this collection Referencias app["web"]pero la clave delmapse llama distinto (o la has borrado deltfvars). El nombre lógico debe coincidir exactamenteEndpointNotFound en PUT .../securityRules/<nombre>Has usado azurerm_network_security_rule. En Topaz las reglas van inline en el bloquesecurity_ruleEndpointNotFound en PUT .../virtualNetworkPeerings/<nombre>Tu versión de Topaz no implementa el peering: desplegar_peering = falseentfvars, vuelve a aplicar y anota la limitaciónSecurityRuleConflict / priority already exists Dos reglas del mismo NSG con la misma priorityydirection. Cada una debe ser únicaVnetAddressSpaceOverlaps al crear el peering Las dos vnets comparten rango. Cambia el address_spacede una de ellas antes de crear subredesPeering en estado Initiated y no Connected Falta el peering en el sentido contrario. Siempre son dos recursos El backend acepta SSH desde web aunque solo permitiste 8080 La regla por defecto AllowVnetInBound (65000) lo permite. Falta la regla Denyexplícita a prioridad menor que 65000Modifying… en vnet o subred en cada apply; elplannunca dice No changesAtributo que el emulador no devuelve ( private_endpoint_vnet_policies,default_outbound_access_enabled…). Añádelo alignore_changesdel recursoInUseSubnetCannotBeDeleted en destroyHay una NIC u otro recurso dentro de la subred creado fuera de este estado. Bórralo primero (o destruye el directorio que lo creó)
10. Autoevaluación¶
- ¿Cuántas direcciones útiles tiene una subred
/24en Azure y por qué? 251: de las 256, Azure reserva las cuatro primeras (red, gateway, dos DNS) y la última (broadcast). - ¿Por qué se usa
for_eachy nocountpara las subredes? Confor_eachcada subred se identifica por su clave; borrar o añadir una no altera las demás. Concount, eliminar el índice 0 desplaza el resto y Terraform las recrea. - ¿Qué diferencia hay entre asociar un NSG a una subred y a una NIC? A la subred filtra todo lo que contenga, presente y futuro; a la NIC filtra una sola máquina. Si hay ambos, el tráfico entrante debe pasar los dos.
- ¿Por qué el backend necesita una regla
Denyexplícita si solo has permitido 8080? La regla por defecto AllowVnetInBound (65000) permite todo el tráfico de la vnet. Sin unDenya prioridad menor, cualquier subred llega a cualquier puerto del backend. - ¿Qué es un service tag y qué ventaja tiene sobre un rango CIDR?
Un alias de rangos mantenido por Microsoft (
Internet,VirtualNetwork,Storage…). Se actualiza solo y documenta la intención de la regla. - ¿Por qué el peering se declara con dos recursos? Es un acuerdo entre dos vnets: cada lado declara el suyo. Con uno solo el estado queda en Initiated y no pasa tráfico.
- Si vnet-A tiene peering con vnet-B y vnet-B con vnet-C, ¿ve A a C?
No: el peering no es transitivo. Para ello se pone un dispositivo de red en B y se activa
allow_forwarded_traffic(topología hub-and-spoke). - ¿Para qué sirve
desplegar_peering? Mismo patrón quedesplegar_vm: hace opcional un recurso que el emulador puede no implementar, sin tocar el resto del código.
11. Referencias¶
azurerm_virtual_network,azurerm_subnet,azurerm_network_security_group,azurerm_subnet_network_security_group_association,azurerm_virtual_network_peering- Meta-argumento
for_eachy funcióncidrsubnet() - Redes virtuales de Azure y preguntas frecuentes (direcciones reservadas, límites)
- Grupos de seguridad de red: reglas por defecto y evaluación y service tags
- Peering de redes virtuales y topología hub-and-spoke
- Azure Local Emulator (Topaz)