Managing Azure ExpressRoute with Terraform
Learn how to set up and manage Azure ExpressRoute using Terraform, including circuit and peering configurations
Azure ExpressRoute provides a private, dedicated, and high-bandwidth connection between your on-premises network and Azure. This guide demonstrates how to set up and manage ExpressRoute using Terraform.
Video Tutorial
Learn more about managing Azure ExpressRoute with Terraform in this comprehensive video tutorial:
Prerequisites
- Azure CLI configured with appropriate permissions
- Terraform installed (version 1.0.0 or later)
- Resource group created
- Understanding of networking concepts
- ExpressRoute service provider
Project Structure
terraform-azure-expressroute/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│ └── expressroute/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── configs/
└── peering.json
ExpressRoute Configuration
Create modules/expressroute/main.tf:
# ExpressRoute Circuit
resource "azurerm_express_route_circuit" "main" {
name = "${var.project_name}-circuit"
resource_group_name = var.resource_group_name
location = var.location
service_provider_name = "Equinix"
peering_location = "Silicon Valley"
bandwidth_in_mbps = 1000
sku {
tier = "Premium"
family = "MeteredData"
}
allow_classic_operations = false
tags = var.tags
}
# ExpressRoute Circuit Authorization
resource "azurerm_express_route_circuit_authorization" "main" {
name = "${var.project_name}-auth"
express_route_circuit_name = azurerm_express_route_circuit.main.name
resource_group_name = var.resource_group_name
}
# Private Peering
# microsoft_peering_config and route_filter_id only apply to
# MicrosoftPeering (see the "microsoft" peering below) - AzurePrivatePeering
# has no Microsoft peering config or route filter.
resource "azurerm_express_route_circuit_peering" "private" {
peering_type = "AzurePrivatePeering"
express_route_circuit_name = azurerm_express_route_circuit.main.name
resource_group_name = var.resource_group_name
primary_peer_address_prefix = "192.168.1.0/30"
secondary_peer_address_prefix = "192.168.2.0/30"
vlan_id = 100
shared_key = var.peering_shared_key
peer_asn = 65001
}
# Microsoft Peering
resource "azurerm_express_route_circuit_peering" "microsoft" {
peering_type = "MicrosoftPeering"
express_route_circuit_name = azurerm_express_route_circuit.main.name
resource_group_name = var.resource_group_name
primary_peer_address_prefix = "192.168.3.0/30"
secondary_peer_address_prefix = "192.168.4.0/30"
vlan_id = 200
shared_key = var.peering_shared_key
peer_asn = 65001
microsoft_peering_config {
advertised_public_prefixes = ["123.0.0.0/24"]
customer_asn = 65001
routing_registry_name = "ARIN"
}
route_filter_id = azurerm_express_route_route_filter.main.id
}
# Route Filter
resource "azurerm_express_route_route_filter" "main" {
name = "${var.project_name}-filter"
resource_group_name = var.resource_group_name
location = var.location
rule {
name = "allow-o365"
access = "Allow"
rule_type = "Community"
communities = ["12076:5010", "12076:5020"]
}
tags = var.tags
}
# ExpressRoute Gateway
resource "azurerm_virtual_network_gateway" "expressroute" {
name = "${var.project_name}-ergw"
location = var.location
resource_group_name = var.resource_group_name
type = "ExpressRoute"
sku = "ErGw2AZ"
ip_configuration {
name = "default"
public_ip_address_id = azurerm_public_ip.expressroute.id
private_ip_address_allocation = "Dynamic"
subnet_id = azurerm_subnet.gateway.id
}
tags = var.tags
}
# ExpressRoute Connection
resource "azurerm_virtual_network_gateway_connection" "expressroute" {
name = "${var.project_name}-connection"
location = var.location
resource_group_name = var.resource_group_name
type = "ExpressRoute"
virtual_network_gateway_id = azurerm_virtual_network_gateway.expressroute.id
express_route_circuit_id = azurerm_express_route_circuit.main.id
routing_weight = 10
enable_bgp = true
# FastPath bypasses the ExpressRoute gateway for supported SKUs (like the
# ErGw2AZ gateway above) - it's a flag on the connection, not a separate
# circuit connection resource.
express_route_gateway_bypass = true
tags = var.tags
}
# Global Reach
resource "azurerm_express_route_circuit_connection" "global" {
name = "${var.project_name}-global"
peering_id = azurerm_express_route_circuit_peering.private.id
peer_peering_id = var.peer_circuit_peering_id
address_prefix = "192.168.101.0/29"
}
Network Configuration
- Virtual Network Setup
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
location = var.location
resource_group_name = var.resource_group_name
address_space = ["10.0.0.0/16"]
tags = var.tags
}
# The inline subnet block on azurerm_virtual_network is deprecated - use a
# standalone azurerm_subnet resource instead. The gateway subnet must be
# named exactly "GatewaySubnet" - Azure requires this literal name.
resource "azurerm_subnet" "gateway" {
name = "GatewaySubnet"
resource_group_name = var.resource_group_name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.255.0/24"]
}
resource "azurerm_public_ip" "expressroute" {
name = "${var.project_name}-ergw-pip"
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Static"
sku = "Standard"
zones = ["1", "2", "3"]
tags = var.tags
}
Route Configuration
- Route Table
resource "azurerm_route_table" "main" {
name = "${var.project_name}-rt"
location = var.location
resource_group_name = var.resource_group_name
route {
name = "ToOnPremise"
address_prefix = "192.168.0.0/16"
next_hop_type = "VirtualNetworkGateway"
}
tags = var.tags
}
# Associate the route table with the workload subnet whose traffic should
# route back on-premises via ExpressRoute (not the GatewaySubnet itself -
# Azure manages that subnet's routing directly). Point this at whichever
# subnet in your VNet actually needs the route.
resource "azurerm_subnet_route_table_association" "main" {
subnet_id = var.workload_subnet_id
route_table_id = azurerm_route_table.main.id
}
Monitoring Configuration
- Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "expressroute" {
name = "${var.project_name}-diag"
target_resource_id = azurerm_express_route_circuit.main.id
log_analytics_workspace_id = var.log_analytics_workspace_id
log {
category = "PeeringRouteLog"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
metric {
category = "AllMetrics"
enabled = true
retention_policy {
enabled = true
days = 30
}
}
}
resource "azurerm_monitor_metric_alert" "expressroute" {
name = "${var.project_name}-bandwidth-alert"
resource_group_name = var.resource_group_name
scopes = [azurerm_express_route_circuit.main.id]
description = "Alert when bandwidth utilization is high"
criteria {
metric_namespace = "Microsoft.Network/expressRouteCircuits"
metric_name = "BitsInPerSecond"
aggregation = "Average"
operator = "GreaterThan"
threshold = 800000000
}
action {
action_group_id = var.action_group_id
}
}
Best Practices
-
Performance
- Use appropriate SKU
- Enable FastPath
- Configure BGP
- Optimize routing
-
High Availability
- Use zone-redundant gateways
- Configure redundant circuits
- Implement failover
- Monitor connections
-
Security
- Use private peering
- Implement encryption
- Configure authentication
- Monitor traffic
-
Cost Optimization
- Choose appropriate bandwidth
- Monitor usage
- Optimize connections
- Use appropriate tier
Conclusion
You’ve learned how to set up and manage Azure ExpressRoute using Terraform. This setup provides:
- Private connectivity
- High bandwidth
- Global reach
- Reliable performance
Remember to:
- Monitor circuit status
- Review peering settings
- Update route filters
- Maintain connections