Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this quickstart, you use Terraform to create an Azure ExpressRoute circuit and its associated infrastructure. The Terraform template creates a complete ExpressRoute setup including a virtual network, ExpressRoute gateway, circuit configuration, and private peering. All resources are deployed with configurable parameters that allow you to customize the deployment for your specific requirements.
Terraform enables the definition, preview, and deployment of cloud infrastructure. Using Terraform, you create configuration files using HCL syntax. The HCL syntax allows you to specify the cloud provider - such as Azure - and the elements that make up your cloud infrastructure. After you create your configuration files, you create an execution plan that allows you to preview your infrastructure changes before they're deployed. Once you verify the changes, you apply the execution plan to deploy the infrastructure.
In this article, you learn how to:
- Create an Azure resource group with a unique name
- Create a virtual network with a subnet for the gateway
- Create an ExpressRoute gateway with configurable SKU
- Create an ExpressRoute circuit with configurable service provider settings
- Configure private peering for the ExpressRoute circuit
- Output key resource identifiers and configuration details
Prerequisites
Create an Azure account with an active subscription. You can create an account for free.
Implement the Terraform code
Note
The sample code for this article is located in the Azure Terraform GitHub repo. You can view the log file containing the test results from current and previous versions of Terraform.
See more articles and sample code showing how to use Terraform to manage Azure resources.
Create a directory in which to test and run the sample Terraform code, and make it the current directory.
Create a file named
main.tf, and insert the following code:# Create Resource Group resource "random_pet" "rg_name" { prefix = var.resource_group_name_prefix } resource "azurerm_resource_group" "rg" { location = var.resource_group_location name = random_pet.rg_name.id tags = var.tags } # Random String for unique naming resource "random_string" "name" { length = 8 special = false upper = false lower = true numeric = false } # Create Virtual Network resource "azurerm_virtual_network" "vnet" { name = "vnet-${random_string.name.result}" address_space = var.virtual_network_address_space location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name tags = var.tags } # Create ExpressRoute Gateway using Azure Verified Module with HOBO module "expressroute_gateway" { source = "Azure/avm-ptn-vnetgateway/azurerm" version = "~> 0.10.0" # Basic Configuration location = azurerm_resource_group.rg.location name = "vgw-${random_string.name.result}" parent_id = azurerm_resource_group.rg.id # ExpressRoute Gateway Configuration type = "ExpressRoute" sku = var.gateway_sku hosted_on_behalf_of_public_ip_enabled = var.enable_hosted_on_behalf_of_public_ip # Enable Azure-managed public IP (HOBO) # Virtual Network Configuration virtual_network_id = azurerm_virtual_network.vnet.id subnet_address_prefix = var.gateway_subnet_address_prefix # GatewaySubnet CIDR # Optional: Enable telemetry for Azure Verified Module enable_telemetry = true # Express Route Circuit Connection (if circuit is provided) express_route_circuits = var.express_route_circuit_id != null ? { "primary" = { id = var.express_route_circuit_id connection = { authorization_key = var.express_route_authorization_key } } } : {} tags = merge(var.tags, { environment = "production" project = "expressroute-hobo" gateway_type = "ExpressRoute" deployment = "azure-verified-module" }) depends_on = [azurerm_virtual_network.vnet] } # Create ExpressRoute Circuit (if enabled) resource "azurerm_express_route_circuit" "circuit" { count = var.create_express_route_circuit ? 1 : 0 name = "erc-${random_string.name.result}" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location service_provider_name = var.service_provider_name peering_location = var.peering_location bandwidth_in_mbps = var.bandwidth_in_mbps sku { tier = var.circuit_sku_tier family = var.circuit_sku_family } tags = merge(var.tags, { environment = "production" project = "expressroute-hobo" }) } # Create ExpressRoute Circuit Peering (if circuit is created) resource "azurerm_express_route_circuit_peering" "private" { count = var.create_express_route_circuit && var.create_private_peering ? 1 : 0 peering_type = "AzurePrivatePeering" express_route_circuit_name = azurerm_express_route_circuit.circuit[0].name resource_group_name = azurerm_resource_group.rg.name primary_peer_address_prefix = var.primary_peer_address_prefix secondary_peer_address_prefix = var.secondary_peer_address_prefix vlan_id = var.vlan_id peer_asn = var.peer_asn }Create a file named
outputs.tf, and insert the following code:output "resource_group_name" { description = "Name of the resource group" value = azurerm_resource_group.rg.name } output "express_route_circuit_id" { description = "ID of the ExpressRoute circuit (if created)" value = var.create_express_route_circuit ? azurerm_express_route_circuit.circuit[0].id : null } output "express_route_circuit_service_key" { description = "Service key for the ExpressRoute circuit (if created)" value = var.create_express_route_circuit ? azurerm_express_route_circuit.circuit[0].service_key : null sensitive = true } output "gateway_id" { description = "ID of the ExpressRoute Virtual Network Gateway" value = module.expressroute_gateway.virtual_network_gateway.id } output "gateway_name" { description = "Name of the ExpressRoute Virtual Network Gateway" value = module.expressroute_gateway.virtual_network_gateway.name } output "gateway_subnet_id" { description = "ID of the GatewaySubnet created by the module" value = module.expressroute_gateway.subnet.id } output "hosted_on_behalf_of_public_ip_note" { description = "Information about the Azure-managed public IP for ExpressRoute gateway" value = "This ExpressRoute Virtual Network Gateway uses an Azure-managed public IP address. The public IP is automatically provisioned and managed by Azure, and is not visible in your subscription's public IP resources. This feature is only available for ExpressRoute gateways, not VPN gateways." } output "public_ip_addresses" { description = "Public IP addresses created by the module (empty when using HOBO for ExpressRoute)" value = module.expressroute_gateway.public_ip_addresses } output "virtual_network_id" { description = "ID of the virtual network" value = azurerm_virtual_network.vnet.id } output "virtual_network_gateway_connections" { description = "Virtual Network Gateway Connections created by the module" value = module.expressroute_gateway.virtual_network_gateway_connections }Create a file named
providers.tf, and insert the following code:terraform { required_version = ">= 1.3" required_providers { azapi = { source = "Azure/azapi" version = "~> 2.4" } azurerm = { source = "hashicorp/azurerm" version = "~> 4.0" } random = { source = "hashicorp/random" version = "~> 3.5" } } } provider "azurerm" { features {} }Create a file named
variables.tf, and insert the following code:variable "resource_group_location" { type = string default = "eastus" description = "Location of the resource group." } variable "resource_group_name_prefix" { type = string default = "rg" description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription." } variable "tags" { type = map(string) default = {} description = "A map of tags to assign to all resources." } # Virtual Network Configuration variable "virtual_network_address_space" { type = list(string) default = ["10.0.0.0/16"] description = "The address space that is used by the virtual network." validation { condition = length(var.virtual_network_address_space) > 0 error_message = "At least one address space must be provided." } } variable "gateway_subnet_address_prefix" { type = string default = "10.0.0.0/24" description = "The address prefix for the GatewaySubnet. Must be at least /29." validation { condition = can(cidrhost(var.gateway_subnet_address_prefix, 0)) && tonumber(split("/", var.gateway_subnet_address_prefix)[1]) <= 29 error_message = "gateway_subnet_address_prefix must be a valid CIDR block with at least /29 prefix." } } # ExpressRoute Gateway Configuration variable "gateway_sku" { type = string default = "ErGw1AZ" description = "The SKU of the ExpressRoute Virtual Network Gateway. Valid values: ErGw1AZ, ErGw2AZ, ErGw3AZ, ErGwScale, HighPerformance, Standard, UltraPerformance." validation { condition = contains(["ErGw1AZ", "ErGw2AZ", "ErGw3AZ", "ErGwScale", "HighPerformance", "Standard", "UltraPerformance"], var.gateway_sku) error_message = "gateway_sku must be one of: ErGw1AZ, ErGw2AZ, ErGw3AZ, ErGwScale, HighPerformance, Standard, UltraPerformance." } } variable "enable_hosted_on_behalf_of_public_ip" { type = bool default = true description = "Enable Azure-managed public IP for the ExpressRoute gateway (HOBO feature). When enabled, Azure manages the public IP internally and it won't appear in your subscription." } # ExpressRoute Circuit Configuration variable "create_express_route_circuit" { type = bool default = true description = "Whether to create an ExpressRoute circuit. Set to false if you want to connect to an existing circuit." } variable "express_route_circuit_id" { type = string default = null description = "ID of an existing ExpressRoute circuit to connect to. Only used if create_express_route_circuit is false." } variable "express_route_authorization_key" { type = string default = null description = "Authorization key for connecting to an existing ExpressRoute circuit." sensitive = true } variable "service_provider_name" { type = string default = "Equinix" description = "The name of the ExpressRoute circuit service provider." } variable "peering_location" { type = string default = "Washington DC" description = "The name of the peering location and not the Azure resource location." } variable "bandwidth_in_mbps" { type = number default = 50 description = "The bandwidth in Mbps of the ExpressRoute circuit." validation { condition = contains([50, 100, 200, 500, 1000, 2000, 5000, 10000], var.bandwidth_in_mbps) error_message = "bandwidth_in_mbps must be one of: 50, 100, 200, 500, 1000, 2000, 5000, 10000." } } variable "circuit_sku_tier" { type = string default = "Standard" description = "The service tier of the ExpressRoute circuit SKU." validation { condition = contains(["Basic", "Local", "Standard", "Premium"], var.circuit_sku_tier) error_message = "circuit_sku_tier must be one of: Basic, Local, Standard, Premium." } } variable "circuit_sku_family" { type = string default = "MeteredData" description = "The billing mode for the ExpressRoute circuit SKU." validation { condition = contains(["MeteredData", "UnlimitedData"], var.circuit_sku_family) error_message = "circuit_sku_family must be either MeteredData or UnlimitedData." } } # ExpressRoute Private Peering Configuration variable "create_private_peering" { type = bool default = true description = "Whether to create Azure Private Peering for the ExpressRoute circuit." } variable "primary_peer_address_prefix" { type = string default = "192.168.10.16/30" description = "A /30 subnet for the primary link." validation { condition = can(cidrhost(var.primary_peer_address_prefix, 0)) error_message = "primary_peer_address_prefix must be a valid CIDR block." } } variable "secondary_peer_address_prefix" { type = string default = "192.168.10.20/30" description = "A /30 subnet for the secondary link." validation { condition = can(cidrhost(var.secondary_peer_address_prefix, 0)) error_message = "secondary_peer_address_prefix must be a valid CIDR block." } } variable "vlan_id" { type = number default = 200 description = "A valid VLAN ID to establish this peering on." validation { condition = var.vlan_id >= 1 && var.vlan_id <= 4094 error_message = "vlan_id must be between 1 and 4094." } } variable "peer_asn" { type = number default = 65001 description = "A valid private ASN for the customer side BGP session." validation { condition = (var.peer_asn >= 64512 && var.peer_asn <= 65534) || (var.peer_asn >= 4200000000 && var.peer_asn <= 4294967294) error_message = "peer_asn must be a valid private ASN (64512-65534 or 4200000000-4294967294)." } }
Initialize Terraform
Run terraform init to initialize the Terraform deployment. This command downloads the Azure provider required to manage your Azure resources.
terraform init -upgrade
Key points:
- The
-upgradeparameter upgrades the necessary provider plugins to the newest version that complies with the configuration's version constraints.
Create a Terraform execution plan
Run terraform plan to create an execution plan.
terraform plan -out main.tfplan
Key points:
- The
terraform plancommand creates an execution plan, but doesn't execute it. Instead, it determines what actions are necessary to create the configuration specified in your configuration files. This pattern allows you to verify whether the execution plan matches your expectations before making any changes to actual resources. - The optional
-outparameter allows you to specify an output file for the plan. Using the-outparameter ensures that the plan you reviewed is exactly what is applied.
Apply a Terraform execution plan
Run terraform apply to apply the execution plan to your cloud infrastructure.
terraform apply main.tfplan
Key points:
- The example
terraform applycommand assumes you previously ranterraform plan -out main.tfplan. - If you specified a different filename for the
-outparameter, use that same filename in the call toterraform apply. - If you didn't use the
-outparameter, callterraform applywithout any parameters.
Verify the results
Get the Azure resource group name.
resource_group_name=$(terraform output -raw resource_group_name)Get the ExpressRoute circuit name.
circuit_name=$(terraform output -raw expressroute_circuit_name)Get the gateway name.
gateway_name=$(terraform output -raw gateway_name)Run
az network express-route showto view the ExpressRoute circuit.az network express-route show --name $circuit_name --resource-group $resource_group_nameRun
az network vnet-gateway showto view the Azure virtual network gateway.az network vnet-gateway show --name $gateway_name --resource-group $resource_group_name
Clean up resources
When you no longer need the resources created via Terraform, do the following steps:
Run terraform plan and specify the
destroyflag.terraform plan -destroy -out main.destroy.tfplanKey points:
- The
terraform plancommand creates an execution plan, but doesn't execute it. Instead, it determines what actions are necessary to create the configuration specified in your configuration files. This pattern allows you to verify whether the execution plan matches your expectations before making any changes to actual resources. - The optional
-outparameter allows you to specify an output file for the plan. Using the-outparameter ensures that the plan you reviewed is exactly what is applied.
- The
Run terraform apply to apply the execution plan.
terraform apply main.destroy.tfplan
Troubleshoot Terraform on Azure
Troubleshoot common problems when using Terraform on Azure.
Next steps
See more articles about Azure virtual network gateway.
To learn how to link a virtual network to a circuit, continue to the ExpressRoute tutorials.