Deploy Azure Virtual Network Peering with a Terraform Module

In this blog post I'll take you through this Terraform module I created which you can use to deploy VNet peerings in your Azure tenant.

Consider a typical hub and spoke network, the Virtual Networks may be spread out across your tenant in multiple subscriptions. Terraform needs a way to create these VNets in multiple subscriptions, which is where providers come in.

Terraform providers adds a set of resource types and/or data sources that it can manage. Every resource type is implemented by a provider.

Best practices call for keeping our code DRY (Don't Repeat Yourself) in order to reduce maintenance overhead and allow our code to be less susceptible to errors.

With the above in mind, I will walk through my implementation so you can configure and implement it in your environment.

The issue

As of writing this article, it is not possible to iterate over a single provider definition and so in order to deploy peerings across a number of subscriptions, we need to have multiple definitions of the Provider block and make use of the alias meta-argument.

For VNet peering, you need to define two resource blocks; one for the VNet initiating the peering to the VNet it wants to peer with; and vice versa.

Rather than defining multiple resource blocks for various subscriptions, we can make use of Terraform modules.

Implementation

Here's the VNet peering module I have come up with:

init.tf

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      configuration_aliases = [azurerm.initiator, azurerm.target]
    }
  }
}

main.tf

resource "azurerm_virtual_network_peering" "initiator_to_target" {
  provider = azurerm.initiator

  name                         = var.peerings["source"]["name"]
  resource_group_name          = var.peerings["source"]["resource_group"]
  virtual_network_name         = var.peerings["source"]["vnet"]
  remote_virtual_network_id    = data.azurerm_virtual_network.target.id
  allow_virtual_network_access = var.allow_virtual_network_access
  allow_forwarded_traffic      = var.allow_forwarded_traffic
}

resource "azurerm_virtual_network_peering" "target_to_initiator" {
  provider = azurerm.target

  name                         = var.peerings["target"]["name"]
  resource_group_name          = var.peerings["target"]["resource_group"]
  virtual_network_name         = var.peerings["target"]["vnet"]
  remote_virtual_network_id    = data.azurerm_virtual_network.initiator.id
  allow_virtual_network_access = var.allow_virtual_network_access
  allow_forwarded_traffic      = var.allow_forwarded_traffic
}

data.tf

data "azurerm_virtual_network" "initiator" {
  provider = azurerm.initiator

  name                = var.peerings["source"]["vnet"]
  resource_group_name = var.peerings["source"]["resource_group"]
}

data "azurerm_virtual_network" "target" {
  provider = azurerm.target

  name                = var.peerings["target"]["vnet"]
  resource_group_name = var.peerings["target"]["resource_group"]
}

inputs-required.tf

variable "peerings" {
  type        = map
  description = "Map of peerings to be created"
}

inputs-optional.tf

variable "allow_virtual_network_access" {
  description = "Controls if the VMs in the remote virtual network can access VMs in the local virtual network"
  default     = true
}

variable "allow_forwarded_traffic" {
  description = "Controls if forwarded traffic from VMs in the remote virtual network is allowed"
  default     = true
}

The peerings input variable will be a map of maps and I was able to access each value the same way you access values in an object literal by using bracket ([]) notation.

peerings is a required input, so will need to be passed when calling the module. I've parameterised allow_virtual_network_access and allow_forwarded_traffic such that they will be set to true if not overridden in module call.

With the module set-up, here's an example usage:

init.tf

provider "azurerm" {
  subscription_id = "ab123c4d-5678-9012-3ef4-5ghi6jk78901"
  skip_provider_registration = "true"
  features {}
  alias = "example_spoke"
}

peering.tf

module "example_hub_example_spoke" {
  source = "./vnet_peering"

  peerings = {
    source = {
      name           = "example-spoke-vnet"
      vnet           = "example-hub-vnet"
      resource_group = "hub-rg"
    }
    target = {
      name           = "example-hub-vnet"
      vnet           = "example-spoke-vnet"
      resource_group = "spoke-rg"
    }
  }

  providers = {
    azurerm.initiator = azurerm
    azurerm.target    = azurerm.example_spoke
  }
}

Add the subscription you'd like to peer with in a provider block and alias it, then call the module and include the alias in the provider meta-argument

I like to keep things simple when creating peerings; the name of the peering should be the remote VNet name to peer with.

While it is still a chunky bit of code, it certainly is better than defining multiple azurerm_virtual_network_peering resources thereby duplicating code.

Hope this is helpful, check out the module on my GitHub! -> github.com/rahman124/aqibrahmancom-examples..