Implementing a hub & spoke topology - a practical guide
Today I'm going to dive into some practical aspects of implementing a hub & spoke network design in Azure.
As the name implies, what we're trying to achieve is a topology with one network segment "in the middle" that serves as a routing hub for the other segments.
As you can see in the image from the official reference design (ref link 1), all traffic from spoke 1 to spoke 2 will be routed through a hub segment. The hub virtual network also has an Azure firewall deployed in it. This can also be implemented using an appliance from a third party vendor (Check Point, Palo Alto, Fortinet, .... choose your poison). At its core the firewall component is not mandatory, but in most cases it's highly recommended.
If you have a more complex environment you might still require it to get some advanced routing scenarios up and running
You may have noted that that I call the hub a "segment" and not a virtual network as drawn in the reference design. The reason is because this could also perfectly work with 1 big vnet that you divide in multiple subnets to achieve the same network flows.
When deciding between multiple vnets or one big vnet, ask yourself the following questions:
Is there a difference in roles and responsibilities between the teams that manage the spokes and the hub?
Do you have internal billing / chargeback and if so, how are you organized on this?
What can your operations team handle?
To actually get all network flows go as desired, you need to use route tables with user defined routes.
In most examples you would find on the internet , you would need to create following route tables:
Sound easy right? Things get a bit more extensive if you've got a lot of spokes and if you've got to handle special scenarios, routing gets a lot more complex.
Two real life situations that I've come across:
Nested virtualization -> How to route the the Ips of the nested environment correctly?
VPN tunnels on the firewall appliance -> How to make sure all subnets on the other side of
When creating your route tables, learn the order of priorities of the routes: (ref link 2)
With 1. being the highest priority:
1. User-defined route (UDR)
2. BGP route
3. System route
There are 2 very important things to add to this:
All routes end up as one big route table, so system routes that get overridden by a user defined route will still show up in the effective route table, but have the tag "invalid"
Longest prefix match is always valid (meaning the most specific route counts)
To elaborate this a bit further with an example:
What would be the next hop to reach 10.0.0.5 ? A common misconception is that the next hop would be the virtual appliance as you've created a user defined route that has the highest priority. This is however not the case as the /24 route is still valid in the route table, and because it has a more specific route than the /8 route, the next hop would be the virtual network.
If you would create a specific /24 to override the system route, you would get the next hop as desired:
To keep things manageable, I maintain the following practices:
Use a dedicated route table for each subnet. Do not assign the same route table to multiple subnets
Override all subnets that exists explicitly, meaning that if you've got N subnets, you would have N-1 routes.
Do not put routes on the subnet that contains the firewall appliance
What will you do with your 0.0.0.0/0 route? Are VMs allowed to go to the internet directly? If not, make sure you don't block all outbound access to the internet or you will break things
To get actually deploy this I like to use a PowerShell script (combined with Azure CLI). ARM templates are of course also an option, but as you would have a N route tables to manage with at least N-1 routes each + some exceptions, things can get quite messy and error prone if you need to add a subnet later on.
ExpressRoute and BGP
One final thing would like to discuss is how to implement your hub & spoke topology is how to work with rout tables in an environment that uses BGP.
Typically when using BGP you advertise only very specific subnet prefixes to Azure. Because of this, you have to be very careful that the advertised prefixes do not mess up your hub and spoke configuration.
We can use the exact same routetable again as example:
The GatewaySubnet most likely has something like this:
Again, traffic from spoke 3 to 10.0.0.5 will not have your appliance as next hop, but it will be routed immediately to the Azure edge.
On the other hand, the route from GatewaySubnet with destination Spoke3 has the virtual appliance as next hop, so say hello to asymmetric routing and all the joys that come with it.
You could override all BGP routes on the spokes with exactly the same routes that are advertised, but that will be a pain to manage.
Gladly there's this nice property on the route tables called "Propagate gateway routes" that will effectively remove all BGP routes on the subnet it has been assigned to.
You can find this setting in the configuration blade of the route table:
When setting this to "no", the route table on Spoke3 for 10.0.0.5 would then look like this:
Pro tip: When configuring the "propagate gateway routes" setting for your virtual appliance subnet, don't select "no" or it won't be able to reach the ranges you advertised.
References & reading material