Azure Hub and Spoke architecture: How to painlessly transit from on-prem to cloud.

Adam Obrebski
Jit Team
Published in
8 min readMar 16, 2023

Azure Hub and Spoke architecture is a design pattern used to connect multiple virtual networks in Azure, providing a central hub for network connectivity and security. This architecture is becoming increasingly popular as organizations look to transition from on-premises infrastructure to the cloud. In this article, we will explore the benefits of Azure Hub and Spoke architecture and provide a step-by-step guide on how to painlessly transition from on-premises to the cloud. Tutorial will be provided in Terraform code. Terraform is a tool used to provide infrastructure as a code, which is the most beneficial way to provide cloud infrastructure nowadays. Using Terraform it is possible to apply sample infrastructure.

This model is mainly recommended for relatively big companies.

Main concepts

Hub: a hub is a central element in the network architecture. It is responsible for managing and inspecting traffic between on-prem, cloud, internet and spokes. All shared resource should live in the hub, which is a center point of every business connectivity.

Typical Azure resources that lives in the hub virtual network

· Azure firewall — this resource is used to block any unwanted traffic in the network topology.

· Application gateway — Using this resource it is possible to safely expose applications that lives in spokes. App gateway often comes with public IP — in some architecture this is only resource with public IP address.

· DNS severs / dns zones

· VPN virtual network gateway or ExpressRoute gateway — connection between hub and a on premise site.

Spokes: Role of a spoke is to deliver some kind of logical piece of workload. Spoke virtual network connects to the hub via network peering. Spoke should isolate workload — containing only resources that are necessary to host application or being a part of the system. Spokes are individual components that can be peered also. It is a good practice to process network traffic through azure firewall in hub. This can improve security. In spokes it is possible to create private endpoints for various Azure resources and have them secured from public internet traffic.

On-premise connection:

Connection between on premise and cloud can be obtained by using an express route or vpn gateway.Using this approach, it is not necessary to transit fully from own servers to the cloud at once. It is possible to adjust existing architecture by adding new parts of the system in the cloud. Possibility of connecting old services to freshly developed in the cloud is a crucial step forward to new cloud world.

Benefits of transitioning to Azure Hub and Spoke architecture

Cost savings

Usage of shared resources in hub significantly reduce the cost of infrastructure. Spokes containing various environments like dev, test or UAT can share among themselves DNS servers or Firewall instead of hosting every single resource per environment. It is also easier for maintain a cost-optimal infrastructure by monitoring which spoke is costly.

Improved security

The Azure Hub and Spoke architecture allows for centralized management of network security policies and access controls. With the hub as a central point for network security, you can set up policies to all the spokes, ensuring that all the environments are secure.

Scalability

The Hub and Spoke architecture is highly scalable, allowing organizations to add new spokes as their needs evolve. This means that you can quickly spin new environments for development, testing, or production without having to create an entirely new infrastructure each time.

Ease of management

Centralized management is one of the primary benefits of the Azure Hub and Spoke architecture. You can easily manage and monitor the entire network from a single location, which makes it easier to troubleshoot issues, monitor performance, and make changes when necessary.

How to painlessly transition from on-premises to Azure Hub and Spoke architecture

Transitioning to Azure Hub and Spoke architecture can seem daunting, but with careful planning and execution, the process can be painless. Here are the steps to follow:

  1. Create file and name it hub.tf. In that file copy following code.
locals {
hub-location = "West Europe"
hub-resource-group = "hub-vnet-rg"
}

resource "azurerm_resource_group" "hub-rg" {
name = local.hub-resource-group
location = local.hub-location
}

resource "azurerm_virtual_network" "hub_vnet" {
name = "hub-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.hub-rg.location
resource_group_name = azurerm_resource_group.hub-rg.name
}

resource "azurerm_subnet" "fw_subnet" {
name = "AzureFirewallSubnet"
resource_group_name = azurerm_resource_group.hub-rg.name
virtual_network_name = azurerm_virtual_network.hub_vnet.name
address_prefixes = ["10.0.2.0/24"]
}

resource "azurerm_virtual_network_peering" "hub-to-spoke1" {
name = "hub-to-spoke1"
resource_group_name = azurerm_resource_group.hub-rg.name
virtual_network_name = azurerm_virtual_network.hub_vnet.name
remote_virtual_network_id = azurerm_virtual_network.spoke1_vnet.id
allow_forwarded_traffic = true
}

resource "azurerm_virtual_network_peering" "hub-to-spoke2" {
name = "hub-to-spoke2"
resource_group_name = azurerm_resource_group.hub-rg.name
virtual_network_name = azurerm_virtual_network.hub_vnet.name
remote_virtual_network_id = azurerm_virtual_network.spoke2_vnet.id
allow_forwarded_traffic = true
}

resource "azurerm_public_ip" "example" {
name = "testpip"
location = azurerm_resource_group.hub-rg.location
resource_group_name = azurerm_resource_group.hub-rg.name
allocation_method = "Static"
sku = "Standard"
}


resource "azurerm_firewall" "fw" {
name = "hubfirewall"
location = azurerm_resource_group.hub-rg.location
resource_group_name = azurerm_resource_group.hub-rg.name
sku_name = "AZFW_VNet"
sku_tier = "Standard"

ip_configuration {
name = "configuration"
subnet_id = azurerm_subnet.fw_subnet.id
public_ip_address_id = azurerm_public_ip.example.id
}
}

resource "azurerm_firewall_network_rule_collection" "example" {
name = "spoke1-to-spoke2"
azure_firewall_name = azurerm_firewall.fw.name
resource_group_name = azurerm_resource_group.hub-rg.name
priority = 100
action = "Deny"

rule {
name = "testrule"

source_addresses = [
azurerm_subnet.vm1_subnet.address_prefixes[0]
]

destination_ports = [
"80",
]

destination_addresses = [
azurerm_subnet.vm2_subnet.address_prefixes[0]
]

protocols = [
"TCP"
]
}
}

The following file contains declarations of various Azure resources. All of them are creating a hub component in hub-and-spokes architecture. Explanation of resources created in this file:

- Locals: Variables used in rest of the file to avoid unnecessary repetitions.

- Resource group: container for every resource in hub.

- Virtual network: representation of the whole network solution in Azure.

- Subnet: Segment of a VNet — range of IP addresses which can be attached to some network resource.

- Vnet peering: connection between two Vnets in Azure.

- Public IP: Simply, public IP needed for Azure Firewall.

- Firewall: cloud native firewall security resource used to secure network traffic.

- Network rule collection: Rule to pass traffic from vm1 to vm2.

2. Create file and name it spoke1.tf. In that file copy following code.

locals {
spoke1-location = "West Europe"
spoke1-resource-group = "spoke1-rg"
}

resource "azurerm_resource_group" "spoke1_rg" {
name = local.spoke1-resource-group
location = local.spoke1-location
}

resource "azurerm_virtual_network" "spoke1_vnet" {
name = "spoke1-network"
address_space = ["10.1.0.0/16"]
location = azurerm_resource_group.spoke1_rg.location
resource_group_name = azurerm_resource_group.spoke1_rg.name
}

resource "azurerm_virtual_network_peering" "spoke1-to-hub" {
name = "spoke1-to-hub"
resource_group_name = azurerm_resource_group.spoke1_rg.name
virtual_network_name = azurerm_virtual_network.spoke1_vnet.name
remote_virtual_network_id = azurerm_virtual_network.hub_vnet.id
allow_forwarded_traffic = true
}

resource "azurerm_subnet" "vm1_subnet" {
name = "vm1"
resource_group_name = azurerm_resource_group.spoke1_rg.name
virtual_network_name = azurerm_virtual_network.spoke1_vnet.name
address_prefixes = ["10.1.2.0/24"]
}

resource "azurerm_route_table" "spoke1_rt" {
name = "spoke1_rt"
location = azurerm_resource_group.spoke2_rg.location
resource_group_name = azurerm_resource_group.spoke2_rg.name

route {
name = "spoke2"
address_prefix = azurerm_subnet.vm2_subnet.address_prefixes[0]
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = azurerm_firewall.fw.ip_configuration[0].private_ip_address
}
}

resource "azurerm_subnet_route_table_association" "spoke1_as" {
subnet_id = azurerm_subnet.vm1_subnet.id
route_table_id = azurerm_route_table.spoke1_rt.id
}

resource "azurerm_public_ip" "public_ip_vm1" {
name = "vm1_pip"
resource_group_name = azurerm_resource_group.spoke1_rg.name
location = azurerm_resource_group.spoke1_rg.location
allocation_method = "Dynamic"
}

resource "azurerm_network_interface" "vm1_nic" {
name = "vm1-nic"
location = azurerm_resource_group.spoke1_rg.location
resource_group_name = azurerm_resource_group.spoke1_rg.name

ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.vm1_subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.public_ip_vm1.id
}
}

resource "azurerm_linux_virtual_machine" "example" {
name = "example-machine-vm1"
resource_group_name = azurerm_resource_group.spoke1_rg.name
location = azurerm_resource_group.spoke1_rg.location
size = "Standard_B1s"
disable_password_authentication = false
admin_username = "adminuser"
admin_password = "Testpassword12345!"
network_interface_ids = [
azurerm_network_interface.vm1_nic.id,
]

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}

The explanation of resources created in this file:

- Route table: resource that provide management of traffic in Azure network. In this case this is used to pass traffic through Azure Firewall.

- Route table association: Adding route table resource to subnet where Virtual Machine lives.

- Network interface: Virtual representation of network interface used to add private and public IP to a VM.

- Linux virtual machine: Sample VM to check if traffic is passed correctly.

3. Create file and name it spoke1.tf. In that file copy following code.

locals {
spoke2-location = "West Europe"
spoke2-resource-group = "spoke2-rg"
}

resource "azurerm_resource_group" "spoke2_rg" {
name = local.spoke2-resource-group
location = local.spoke2-location
}

resource "azurerm_virtual_network" "spoke2_vnet" {
name = "spoke2-network"
address_space = ["10.2.0.0/16"]
location = azurerm_resource_group.spoke2_rg.location
resource_group_name = azurerm_resource_group.spoke2_rg.name
}

resource "azurerm_virtual_network_peering" "spoke2-to-hub" {
name = "spoke2-to-hub"
resource_group_name = azurerm_resource_group.spoke2_rg.name
virtual_network_name = azurerm_virtual_network.spoke2_vnet.name
remote_virtual_network_id = azurerm_virtual_network.hub_vnet.id
allow_forwarded_traffic = true
}

resource "azurerm_subnet" "vm2_subnet" {
name = "vm2"
resource_group_name = azurerm_resource_group.spoke2_rg.name
virtual_network_name = azurerm_virtual_network.spoke2_vnet.name
address_prefixes = ["10.2.2.0/24"]
}

resource "azurerm_route_table" "spoke2_rt" {
name = "spoke2_rt"
location = azurerm_resource_group.spoke2_rg.location
resource_group_name = azurerm_resource_group.spoke2_rg.name

route {
name = "spoke1"
address_prefix = azurerm_subnet.vm1_subnet.address_prefixes[0]
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = azurerm_firewall.fw.ip_configuration[0].private_ip_address
}
}

resource "azurerm_subnet_route_table_association" "spoke2_as" {
subnet_id = azurerm_subnet.vm2_subnet.id
route_table_id = azurerm_route_table.spoke2_rt.id
}


resource "azurerm_public_ip" "public_ip_vm2" {
name = "vm2_pip"
resource_group_name = azurerm_resource_group.spoke2_rg.name
location = azurerm_resource_group.spoke2_rg.location
allocation_method = "Dynamic"
}

resource "azurerm_network_interface" "vm2_nic" {
name = "vm2-nic"
location = azurerm_resource_group.spoke2_rg.location
resource_group_name = azurerm_resource_group.spoke2_rg.name

ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.vm2_subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.public_ip_vm2.id
}
}

resource "azurerm_linux_virtual_machine" "vm2" {
name = "example-machine-vm2"
resource_group_name = azurerm_resource_group.spoke2_rg.name
location = azurerm_resource_group.spoke2_rg.location
size = "Standard_B1s"
disable_password_authentication = false
admin_username = "adminuser"
admin_password = "Testpassword12345!"

network_interface_ids = [
azurerm_network_interface.vm2_nic.id,
]

os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}

source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "16.04-LTS"
version = "latest"
}
}

This file is almost the same as the spoke1.

4. Create file and name it provider.tf. In that file copy following code.

provider “azurerm” { 
features {}
subscription_id = “XXXX-XXXX-XXXX-XXXX-XXXX”
}

Here, provide your subscription id used to deploy all resources.

5. Use terraform plan command in CLI to check if every resource will be created correctly:

terraform plan

You will obtain something like this.

6. Use terraform apply command to create every resource in Azure:

terraform apply

7. Connect by ssh to Azure VM2 using public IP.

ssh adminuser@<public_ip_address>

8. Install nginx:

sudo apt install nginx 

9. Check if nginx default site is avaiable using cli:

 curl localhost:80/ 

10. Connect by ssh to Azure VM1 using public IP.

11. Run this command

curl <private_ip_vm2>:80/

12. You should see something like this:

Conclusion

From my experience I can tell that every big company, I worked with, benefited from using Hub and spokes architecture. It was very helpful to develop new parts of the system in the cloud but at the same time, the old on premise system was constantly in use. This network topology is also very secure, as it is possible to have every resource connected via private endpoints and expose only application gateway to the Internet.

--

--