Terraform Azure Multi-Region
Introduction
In today's cloud-first world, building applications that can withstand regional outages is essential for businesses that require high availability and disaster recovery capabilities. Azure's global infrastructure spans numerous regions worldwide, giving you the flexibility to deploy resources close to your users or to meet regulatory requirements.
Terraform makes it easy to define and deploy multi-region architectures in Azure with consistent, repeatable infrastructure as code (IaC). In this guide, we'll explore how to leverage Terraform to create robust multi-region deployments in Azure.
Why Deploy Across Multiple Regions?
Before diving into the implementation, let's understand why multi-region deployments are important:
- High Availability: Protect against regional outages by having redundant resources in different geographic locations
- Disaster Recovery: Maintain business continuity even if an entire region goes offline
- Performance: Reduce latency by placing resources closer to your users
- Regulatory Compliance: Meet data residency requirements by keeping data within specific geographic boundaries
Prerequisites
To follow along with this guide, you'll need:
- Terraform installed (version 1.0.0+)
- Azure CLI installed and configured
- An Azure subscription
- Basic knowledge of Terraform and Azure concepts
Setting Up Your Terraform Project
Let's start by organizing our Terraform project for multi-region deployment:
├── main.tf # Main configuration file
├── variables.tf # Input variables
├── outputs.tf # Output values
├── providers.tf # Provider configuration
└── modules/
└── web-app/ # Reusable module for web application
├── main.tf
├── variables.tf
└── outputs.tf
Provider Configuration
First, let's set up the Azure provider in providers.tf
:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
}
Variables Definition
Next, define the variables in variables.tf
:
variable "primary_location" {
description = "The Azure region for the primary deployment"
type = string
default = "eastus"
}
variable "secondary_location" {
description = "The Azure region for the secondary deployment"
type = string
default = "westus"
}
variable "project_name" {
description = "Name of the project"
type = string
default = "multiregion"
}
variable "environment" {
description = "Deployment environment"
type = string
default = "dev"
}
variable "regions" {
description = "Map of regions with their configurations"
type = map(object({
location = string
capacity = number
is_primary = bool
}))
default = {
primary = {
location = "eastus"
capacity = 2
is_primary = true
}
secondary = {
location = "westus"
capacity = 1
is_primary = false
}
}
}
Creating Basic Multi-Region Resources
In our main.tf
file, we'll create the basic resources needed in each region:
# Create resource groups for each region
resource "azurerm_resource_group" "rg" {
for_each = var.regions
name = "rg-${var.project_name}-${each.key}-${var.environment}"
location = each.value.location
tags = {
Environment = var.environment
Region = each.key
}
}
# Create a virtual network in each region
resource "azurerm_virtual_network" "vnet" {
for_each = var.regions
name = "vnet-${var.project_name}-${each.key}"
location = each.value.location
resource_group_name = azurerm_resource_group.rg[each.key].name
address_space = ["10.${each.key == "primary" ? 0 : 1}.0.0/16"]
tags = {
Environment = var.environment
Region = each.key
}
}
# Create a subnet in each virtual network
resource "azurerm_subnet" "subnet" {
for_each = var.regions
name = "subnet-${var.project_name}-${each.key}"
resource_group_name = azurerm_resource_group.rg[each.key].name
virtual_network_name = azurerm_virtual_network.vnet[each.key].name
address_prefixes = ["10.${each.key == "primary" ? 0 : 1}.1.0/24"]
}
Traffic Manager for Multi-Region Load Balancing
To distribute traffic across regions, we'll use Azure Traffic Manager:
# Create a Traffic Manager profile
resource "azurerm_traffic_manager_profile" "tm" {
name = "tm-${var.project_name}-${var.environment}"
resource_group_name = azurerm_resource_group.rg["primary"].name
traffic_routing_method = "Performance"
dns_config {
relative_name = "${var.project_name}-${var.environment}"
ttl = 60
}
monitor_config {
protocol = "HTTP"
port = 80
path = "/"
interval_in_seconds = 30
timeout_in_seconds = 10
tolerated_number_of_failures = 3
}
tags = {
Environment = var.environment
}
}
Creating a Reusable Web App Module
Let's create a reusable module for deploying web apps. In the modules/web-app/variables.tf
file:
variable "name" {
description = "Name of the web app"
type = string
}
variable "location" {
description = "Azure region for the web app"
type = string
}
variable "resource_group_name" {
description = "Resource group name"
type = string
}
variable "app_service_plan_id" {
description = "App Service Plan ID"
type = string
}
variable "region_key" {
description = "Key identifying the region (primary/secondary)"
type = string
}
variable "environment" {
description = "Deployment environment"
type = string
}
In the modules/web-app/main.tf
file:
resource "azurerm_app_service" "webapp" {
name = "app-${var.name}-${var.region_key}"
location = var.location
resource_group_name = var.resource_group_name
app_service_plan_id = var.app_service_plan_id
site_config {
dotnet_framework_version = "v4.0"
scm_type = "LocalGit"
}
app_settings = {
"WEBSITE_NODE_DEFAULT_VERSION" = "10.14.1"
"REGION" = var.region_key
"ENVIRONMENT" = var.environment
}
tags = {
Environment = var.environment
Region = var.region_key
}
}
In the modules/web-app/outputs.tf
file:
output "app_service_name" {
value = azurerm_app_service.webapp.name
}
output "app_service_default_hostname" {
value = azurerm_app_service.webapp.default_site_hostname
}
output "app_service_id" {
value = azurerm_app_service.webapp.id
}
Deploying Web Apps Across Regions
Now, let's deploy web apps in multiple regions using our module:
# Create App Service Plans for each region
resource "azurerm_app_service_plan" "asp" {
for_each = var.regions
name = "asp-${var.project_name}-${each.key}"
location = each.value.location
resource_group_name = azurerm_resource_group.rg[each.key].name
sku {
tier = "Standard"
size = "S1"
capacity = each.value.capacity
}
tags = {
Environment = var.environment
Region = each.key
}
}
# Deploy Web Apps using our module
module "webapp" {
source = "./modules/web-app"
for_each = var.regions
name = "${var.project_name}-${var.environment}"
location = each.value.location
resource_group_name = azurerm_resource_group.rg[each.key].name
app_service_plan_id = azurerm_app_service_plan.asp[each.key].id
region_key = each.key
environment = var.environment
}
# Add endpoints to Traffic Manager
resource "azurerm_traffic_manager_endpoint" "endpoint" {
for_each = var.regions
name = "${each.key}-endpoint"
resource_group_name = azurerm_resource_group.rg["primary"].name
profile_name = azurerm_traffic_manager_profile.tm.name
type = "azureEndpoints"
target_resource_id = module.webapp[each.key].app_service_id
priority = each.value.is_primary ? 1 : 2
weight = 100
}
Implementing Regional Failover with Traffic Manager
Let's configure our Traffic Manager for failover routing:
# Update Traffic Manager profile to use priority routing for failover
resource "azurerm_traffic_manager_profile" "tm_failover" {
name = "tm-failover-${var.project_name}-${var.environment}"
resource_group_name = azurerm_resource_group.rg["primary"].name
traffic_routing_method = "Priority"
dns_config {
relative_name = "failover-${var.project_name}-${var.environment}"
ttl = 60
}
monitor_config {
protocol = "HTTP"
port = 80
path = "/health"
interval_in_seconds = 30
timeout_in_seconds = 10
tolerated_number_of_failures = 3
}
tags = {
Environment = var.environment
}
}
# Add endpoints to failover Traffic Manager
resource "azurerm_traffic_manager_endpoint" "failover_endpoint" {
for_each = var.regions
name = "${each.key}-failover-endpoint"
resource_group_name = azurerm_resource_group.rg["primary"].name
profile_name = azurerm_traffic_manager_profile.tm_failover.name
type = "azureEndpoints"
target_resource_id = module.webapp[each.key].app_service_id
# Primary region gets priority 1, secondary gets priority 100
priority = each.value.is_primary ? 1 : 100
}
Cross-Region Data Replication
For applications that require data consistency across regions, we can use Azure Cosmos DB with multi-region support:
resource "azurerm_cosmosdb_account" "db" {
name = "cosmos-${var.project_name}-${var.environment}"
location = var.regions["primary"].location
resource_group_name = azurerm_resource_group.rg["primary"].name
offer_type = "Standard"
kind = "GlobalDocumentDB"
enable_automatic_failover = true
# Configure geo-replication
geo_location {
location = var.regions["primary"].location
failover_priority = 0
}
geo_location {
location = var.regions["secondary"].location
failover_priority = 1
}
consistency_policy {
consistency_level = "BoundedStaleness"
max_interval_in_seconds = 300
max_staleness_prefix = 100000
}
tags = {
Environment = var.environment
}
}
Implementing Region-Aware Networking with Virtual Network Peering
Let's connect our regional virtual networks using VNet peering:
resource "azurerm_virtual_network_peering" "primary_to_secondary" {
name = "peer-primary-to-secondary"
resource_group_name = azurerm_resource_group.rg["primary"].name
virtual_network_name = azurerm_virtual_network.vnet["primary"].name
remote_virtual_network_id = azurerm_virtual_network.vnet["secondary"].id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = false
}
resource "azurerm_virtual_network_peering" "secondary_to_primary" {
name = "peer-secondary-to-primary"
resource_group_name = azurerm_resource_group.rg["secondary"].name
virtual_network_name = azurerm_virtual_network.vnet["secondary"].name
remote_virtual_network_id = azurerm_virtual_network.vnet["primary"].id
allow_virtual_network_access = true
allow_forwarded_traffic = true
allow_gateway_transit = false
}
Architecture Visualization
Let's include a diagram to help visualize our multi-region architecture:
Outputs
Let's define the outputs in outputs.tf
:
output "resource_group_names" {
description = "Names of the resource groups"
value = {
for k, rg in azurerm_resource_group.rg : k => rg.name
}
}
output "webapp_urls" {
description = "URLs of the deployed web apps"
value = {
for k, webapp in module.webapp : k => "https://${webapp.app_service_default_hostname}"
}
}
output "traffic_manager_url" {
description = "URL of the Traffic Manager profile"
value = "http://${azurerm_traffic_manager_profile.tm.fqdn}"
}
output "failover_traffic_manager_url" {
description = "URL of the failover Traffic Manager profile"
value = "http://${azurerm_traffic_manager_profile.tm_failover.fqdn}"
}
output "cosmos_db_endpoint" {
description = "Endpoint for the Cosmos DB account"
value = azurerm_cosmosdb_account.db.endpoint
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)