A bank has dozens of teams, each with its own VNet, and one firewall, one link to the office network and one DNS setup everyone must share. Connecting every VNet to every other one would be a tangle nobody can secure. So almost every enterprise Azure network - and certainly a bank's - is hub and spoke: one central VNet with the shared services, every team's VNet connected only to it.
What you need to know already: 23.1 (VNets, peering is not transitive, NVA, on-prem, VPN/ExpressRoute gateways), 23.7 (route tables, next hop VirtualAppliance, asymmetric routing), 23.3 (NSGs), 23.10 (private DNS zones), 12.1 (Terraform resources).
on-prem (VPN / ExpressRoute)
|
+---------+---------+
| vnet-hub | 10.30.0.0/16
| Azure Firewall | 10.30.254.4
| VPN gateway |
+----+--------+-----+
peering | | peering
+-------------+ +-------------+
| vnet-spoke-a| | vnet-spoke-b|
| 10.31.0.0/16| | 10.32.0.0/16|
+-------------+ +-------------+
The hub holds shared services (firewall, gateways, DNS resolvers - servers that answer DNS questions for the spokes - and Bastion); each workload or environment gets a spoke. Spokes are peered only with the hub.
Peering
az network vnet peering create: create one side of a peering; --vnet-name the VNet it belongs to; -n its name; --remote-vnet the VNet on the other side; --allow-vnet-access let the two address spaces talk; --allow-forwarded-traffic accept packets that did not start in the remote VNet but were forwarded through it (by a firewall, say).
az network vnet peering create -g rg-hub --vnet-name vnet-spoke-a -n spoke-a-to-hub \
--remote-vnet vnet-hub --allow-vnet-access --allow-forwarded-traffic
az network vnet peering create -g rg-hub --vnet-name vnet-hub -n hub-to-spoke-a \
--remote-vnet vnet-spoke-a --allow-vnet-access --allow-forwarded-traffic
A peering is two objects, one on each side. Until both exist the state is Initiated; with both it is Connected:
# rg-hub = a platform team's hub (the hub-spoke mission builds one)
az network vnet peering list -g rg-hub --vnet-name vnet-hub -o table
AllowForwardedTraffic AllowGatewayTransit AllowVirtualNetworkAccess Name PeeringState
----------------------- --------------------- --------------------------- -------------- ------------
True False True hub-to-spoke-a Connected
True False True hub-to-spoke-b Initiated
Initiated means half a peering - no traffic flows. It is the first thing to look for when "the peering exists but nothing works".
Peered address spaces must not overlap:
ERROR: (VnetAddressSpacesOverlap) Cannot create or update peering ... Virtual networks ... cannot
be peered because their address spaces overlap. Overlapping address prefixes: 10.31.0.0/16.
This is why the VNet lesson told you to plan ranges globally.
Not transitive
spoke-a <-> hub and hub <-> spoke-b does not give spoke-a <-> spoke-b. A peering only carries traffic between the two VNets it joins. For spoke-to-spoke:
- a UDR in spoke-a:
10.32.0.0/16 -> VirtualAppliance 10.30.254.4(the hub firewall) - the firewall allows and forwards it
- spoke-b accepts forwarded traffic on its peering to the hub (
allowForwardedTraffic) - traffic arriving from the firewall did not originate in the hub - a UDR in spoke-b for the way back:
10.31.0.0/16 -> 10.30.254.4. Without it replies go... nowhere (no peering to spoke-a) - or, with other routes in place, around the firewall, which drops the asymmetric half-flow.
Every one of these four is required. The incident after this lesson is missing two of them.
Gateway transit
Transit here means passing through. --allow-gateway-transit on the hub side and --use-remote-gateways on the spoke side let spokes use the hub's VPN/ExpressRoute gateway (23.1), so on-prem reaches every spoke through one gateway.
Alternatives
Azure Virtual WAN is Microsoft's managed hub (routing between spokes is built in, with a secured hub firewall). AVNM (Virtual Network Manager) can create mesh connectivity and push security admin rules centrally. The underlying rule - one peering, two VNets, no transit - still explains why things do or do not work.
Terraform shape
resource "azurerm_virtual_network_peering" "spoke_to_hub" {
name = "spoke-a-to-hub"
resource_group_name = "rg-hub"
virtual_network_name = azurerm_virtual_network.spoke_a.name
remote_virtual_network_id = azurerm_virtual_network.hub.id
allow_forwarded_traffic = true
use_remote_gateways = true
}
(and its twin in the hub). Spoke teams often do not have rights on the hub VNet - the hub side is created by the platform team's own Terraform, run separately, which is where "half-peered" states come from.
Diagnosing spoke-to-spoke, in order
1. az network vnet peering list (both spokes and the hub) all Connected?
2. allowForwardedTraffic on the DESTINATION spoke's peering traffic from the firewall accepted?
3. route table on the SOURCE subnet: dest prefix -> firewall goes to the hub at all?
4. route table on the DESTINATION subnet: source prefix -> fw comes back the same way?
5. firewall network/application rule for the flow allowed, and logged?
6. NSGs on both subnets allowed at both ends?
Steps 1-4 are the ones this chapter's incident breaks. Step 5 lives in the firewall's logs. Step 6 is the NSG lesson.
Later (Ch 24): the firewall's logs land in Log Analytics (
AZFWNetworkRule/AzureDiagnostics), where KQL shows each allowed or denied flow.
DNS in a hub
Private DNS zones are linked to every spoke (or to the hub, with spokes using a DNS resolver in the hub). If spoke B's service is behind a private endpoint, spoke A must be able to resolve its privatelink name too - "the network works, the name does not" is the other half of every hub-and-spoke ticket.
What you can now do
- Draw a hub and spoke and say what lives in the hub.
- Build a peering (both sides) and recognise a half-built one (
Initiated). - List the four pieces spoke-to-spoke traffic needs through the hub firewall.