Automated configuration is faster than manual configuration. So in this article, we'll be setting up the Azure Landing zone using Terraform. What is Terraform? Terraform is a multi-cloud Infrastructure as a Code (IaC) tool used for building, managing, and scaling cloud infrastructure. It is an open-source tool developed by HashiCorp and uses a language called HashiCorp Configuration Language (HCL), which is easy to learn and use for automating tasks. This tool supports cloud service providers such as Azure, AWS, and Google Cloud, as well as private cloud providers such as VMware, CloudStack, and OpenStack.
Infrastructure as Code (IaC) is frequently referred to as programmable infrastructure. Versioning is the core of IaC, enabling reuse and sharing across environments for faster and efficient deployments.
The primary use of Terraform in Microsoft Azure is to automate the process of infrastructure provisioning and management. Using Terraform, we can simplify complex tasks, efficiently ensure the desired state configuration, and support future modifications.
Prerequisites:
- Install Terraform and the Azure CLI.
- Follow the detailed steps below for a perfect setup on Windows environment.
Install Terraform on your Windows Environment
- Download the latest version from here: https://developer.hashicorp.com/terraform/downloads.
- Unzip the downloaded package to a directory of your choice.
- Open Control Panel > System and Security > System > Advanced system settings > Environment Variables.
- In the System variables section, find the Path variable and double-click it.
- Click New and add the directory containing the Terraform binary to the PATH variable.
- Click OK to save your changes.
You have now successfully installed Terraform on Windows. Next, install the Azure CLI.
Install Azure CLI on your Windows Environment
Step 1: Open Windows PowerShell Run as administrator.
Step 2: Create a new file called installAzureCLI.ps1 and copy and insert the following script into it:
Invoke-WebRequest -Uri https://aka.ms/installazurecliwindows -OutFile .\AzureCLI.msi;
Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /quiet';
rm .\AzureCLI.msi
Step 3: Run the installAzureCLI.ps1 file and wait for the installation to complete.
Step 4: Verify the installation by running the following command:
az -h
That's it! You have now successfully configured Terraform and Azure CLI.
Setup Azure Landing Zone:
Contents to Setup Azure Landing Zone:
- Create Azure Resource Group
- Create Azure Virtual Network
- Create Subnet in Virtual Network
- Create Azure Network Security Group
- Configure Network Security Rules
- Associate Network Security Group to Virtual Network Subnet
- Create a Route Table
- Creating the Route in Route Table which is present in Connectivity Subscription
- Creating Storage Account
- Creating and Configuring Private DNS Zone
- Virtual Network Peeing to the Connectivity Subscription
- Create IP Groups
Step 1: Create a new folder for Azure Landing Zone Setup
In folder create three files namely
- main.tf
- variable.tf
- locals.tf
Step 2: Copy the below codes and add it to their respective files.
Add the code to File: main.tf
/*=== Configure Azure Resource Providers ===*/
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 2.65"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
features {}
}
provider "azurerm" {
alias = "connectivity"
subscription_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
features {}
}
provider "azurerm" {
alias = "identity"
subscription_id = "xxxx-xxxx-xxxx-xxxx-xxxx"
features {}
}
data "azurerm_resource_group" "rgeusconnectivity" {
provider = azurerm.connectivity
name = "prd-az-connectivity-eus-rg"
}
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
tags = {
environment = "Production"
}
}
/*=== Create and Configure Azure Virtual Network ===*/
resource "azurerm_virtual_network" "vnet" {
name = var.vnetName
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
address_space = var.addressSpaceVnet
}
resource "azurerm_subnet" "subnet" {
name = var.SubnetName
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.addressPrefixesSnet
}
/*=== Create and Configure Azure Network Security Group Rules ===*/
resource "azurerm_network_security_group" "nsg" {
name = var.nsgName
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_network_security_rule" "nsgrules" {
for_each = local.nsgrules
name = each.key
direction = each.value.direction
access = each.value.access
priority = each.value.priority
protocol = each.value.protocol
source_port_range = each.value.source_port_range
destination_port_range = each.value.destination_port_range
source_address_prefix = each.value.source_address_prefix
destination_address_prefix = each.value.destination_address_prefix
resource_group_name = azurerm_resource_group.rg.name
network_security_group_name = azurerm_network_security_group.nsg.name
}
resource "azurerm_subnet_network_security_group_association" "nsgsnetassociation" {
subnet_id = azurerm_subnet.subnet.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
/*=== Create Azure Route Table ===*/
resource "azurerm_route_table" "routetable" {
name = var.routetableInfo
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
disable_bgp_route_propagation = false
route {
name = "to-firewall"
address_prefix = "0.0.0.0/0"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address = var.nextHopinIPaddress
}
}
/*=== Adding of Route to Connectivity ===*/
data "azurerm_route_table" "routeTable" {
provider = azurerm.connectivity
name = "prd-az-gateway-eus-route"
resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
}
resource "azurerm_route" "route" {
provider = azurerm.connectivity
name = "to-Terraform"
resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
route_table_name = data.azurerm_route_table.routeTable.name
address_prefix = "0.0.0.0/0"
next_hop_type = "VirtualAppliance"
next_hop_in_ip_address ="10.1.0.6"
}
/*=== Create Azure Storage Account ===*/
resource "azurerm_storage_account" "storageAccount" {
name = var.storageAccountName
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = var.account_tier
account_replication_type = var.account_replication_type
access_tier = var.accessTier
min_tls_version = var.minTlsVersion
account_kind = var.accountKind
}
/*==== Creation and Configuration of Private DNS Zone ===*/
data "azurerm_resource_group" "rgidentityeus" {
provider = azurerm.identity
name = "prd-azure-identity-eus-rg"
}
data "azurerm_resource_group" "rgidentityweu" {
provider = azurerm.identity
name = "prd-az-identity-weu-rg"
}
data "azurerm_virtual_network" "vnetidentityeus" {
provider = azurerm.identity
name = "prd-az-identity-eus-vnet"
resource_group_name = data.azurerm_resource_group.rgidentityeus.name
}
data "azurerm_virtual_network" "vnetidentityweu" {
provider = azurerm.identity
name = "prd-az-identity-weu-vnet"
resource_group_name = data.azurerm_resource_group.rgidentityweu.name
}
resource "azurerm_private_dns_zone" "privateDNSzone" {
name = var.privateDNSzoneName
resource_group_name = azurerm_resource_group.rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "privateDNSzoneVNetLink1" {
name = "link-to-identity-eus"
resource_group_name = data.azurerm_resource_group.rgidentityeus.name
private_dns_zone_name = azurerm_private_dns_zone.privateDNSzone.name
virtual_network_id = data.azurerm_virtual_network.vnetidentityeus.id
registration_enabled = false
}
resource "azurerm_private_dns_zone_virtual_network_link" "privateDNSzoneVNetLink2" {
name = "link-to-identity-weu"
resource_group_name = data.azurerm_resource_group.rgidentityweu.name
private_dns_zone_name = azurerm_private_dns_zone.privateDNSzone.name
virtual_network_id = data.azurerm_virtual_network.vnetidentityweu.id
registration_enabled = false
}
resource "azurerm_private_dns_zone_virtual_network_link" "privateDNSzoneVNetLink3" {
name = "link-to-subscription"
resource_group_name = azurerm_resource_group.rg.name
private_dns_zone_name = azurerm_private_dns_zone.privateDNSzone.name
virtual_network_id = azurerm_virtual_network.vnet.id
registration_enabled = true
}
/*=== Azure VNet Peering to Azure Connectivity Hub ===*/
data "azurerm_virtual_network" "vneteusconnectivity" {
provider = azurerm.connectivity
name = "prd-az-connectivity-eus-vnet"
resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
}
resource "azurerm_virtual_network_peering" "vnet1-2" {
provider = azurerm.connectivity
name = var.vnet1-2PeeringName"peer1to2"
resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
virtual_network_name = data.azurerm_virtual_network.vneteusconnectivity.name
remote_virtual_network_id = azurerm_virtual_network.vnet.id
allow_forwarded_traffic = true
allow_gateway_transit = true
use_remote_gateways = false
}
resource "azurerm_virtual_network_peering" "vnet2-1" {
name = var.vnet2-1PeeringName
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
remote_virtual_network_id = data.azurerm_virtual_network.vneteusconnectivity.name
allow_forwarded_traffic = true
allow_gateway_transit = false
use_remote_gateways = false
}
/*=== Create Azure IP Group ===*/
resource "azurerm_ip_group" "ipgroup" {
provider = azurerm.connectivity
name = var.ipgroupName
location = data.azurerm_resource_group.rgeusconnectivity.location
resource_group_name = data.azurerm_resource_group.rgeusconnectivity.name
cidrs = var.ipgroupcidrs
}
Add the code to File: variable.tf
variable "resource_group_name" {
type = string
default = "prd-az-terrform-eus-rg"
}
variable "location" {
type = string
default = "eastus"
}
variable "vnetName" {
type = string
default = "prd-az-terraform-eus-vnet"
}
variable "addressSpaceVnet" {
type = string
default = ["10.2.0.0/16"]
}
variable "SubnetName" {
default = "prd-az-terraform-eus-subnet"
}
variable "addressPrefixesSnet" {
type = string
default = ["10.2.4.0/29"]
}
variable "nsgName" {
type = string
default = "prd-az-terraform-eus-nsg"
}
variable "routetableInfo" {
type = string
default = "prd-az-terraform-eus-routetable"
}
variable "nextHopinIPaddress" {
type = string
default = "10.4.0.0"
}
variable "storageAccountName" {
type = string
default = "prdazterraformeusst01"
}
variable "account_tier" {
type = string
default = "Standard"
}
variable "account_replication_type" {
type = string
default = "LRS"
}
variable "accessTier" {
type = string
default = "Hot"
}
variable "minTlsVersion" {
type = string
default = "TLS1_2"
}
variable "accountKind" {
type = string
default = "StorageV2"
}
variable "privateDNSzoneName" {
type = string
default = "prd.eus.azure.cloud.ads"
}
variable "vnet1-2PeeringName" {
type = string
default = "peer1to2"
}
variable "vnet2-1PeeringName" {
type = string
default = "peer2to1"
}
variable "ipgroupName" {
type = string
default = "prd-az-Terraform-eus-ipgroup"
}
variable "ipgroupcidrs" {
type = list
default = ["10.3.0.0/29", "10.4.1.8"]
}
Add the code to File: locals.tf
locals {
nsgrules = {
AllowRDPInBound = {
name = "AllowRDPInBound"
priority = 300
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
}
AllowSSHInBound = {
name = "AllowSSHInBound"
priority = 400
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
}
AllowPort80InBound = {
name = "AllowPort80InBound"
priority = 500
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
}
AllowPort443InBound = {
name = "AllowPort443InBound"
priority = 600
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
}
DenyAllInBound = {
name = "DenyAllInBound"
priority = 4096
direction = "Inbound"
access = "Deny"
protocol = "*"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "VirtualNetwork"
}
}
}
Step 3: Run the PowerShell to the new folder location
Step 4: Now authenticate to Azure using azure CLI, for that run the command az login. You will be redirected to the new browser. Log in to Microsoft by providing your credentials. Once the login is completed go back to the PowerShell session
Step 5: The list of the subscriptions associated with your Microsoft account will get listed, select the subscription in which you want to perform Landing Zone Setup by running the following command:
az account set --subscription <name or id>
Step 6: Once you set the subscription, run the command terraform init, the terraform init command is used to initialize a working directory containing Terraform configuration files.
Step 7: Next run the command terraform validate, the terraform validate command validates the configuration files in a directory and it verifies whether a configuration is systematically valid.
Step 8: Run the command terraform plan, the terraform plan command creates an execution plan, which lets you preview the changes that Terraform plans to make to your infrastructure.
Step 9: At last, run the command terraform apply, the terraform apply command executes the actions proposed in a Terraform plan. Provide the value as Yes, if you want to apply the configuration.
Terraform CLI Cheat Sheet List:
This table provides an extensive list of Terraform CLI commands along with their brief descriptions. You can refer to this cheat sheet as a quick reference for various Terraform operations.
Command | Description |
---|---|
terraform init |
Initialize a Terraform configuration in the current directory. |
terraform apply |
Create or update infrastructure according to Terraform configuration. |
terraform destroy |
Destroy the Terraform-managed infrastructure. |
terraform plan |
Show the execution plan for the Terraform configuration. |
terraform validate |
Validate the Terraform configuration files. |
terraform refresh |
Refresh the Terraform state. |
terraform output |
Output the Terraform state or a specific output value. |
terraform output <name> |
Output the value of a specific output from the Terraform state. |
terraform import |
Import existing infrastructure into Terraform state. |
terraform import <address> |
Import a specific resource into the Terraform state. |
terraform taint |
Mark a resource as tainted, triggering recreation on the next apply . |
terraform untaint |
Remove the tainted state from a resource. |
terraform fmt |
Format and update the Terraform code to follow best practices. |
terraform show |
Show the execution plan in a human-readable format with colors. |
terraform show -no-color |
Show the execution plan in a human-readable format without colors. |
terraform state show |
Show detailed information about a resource in the Terraform state. |
terraform state list |
List resources in the Terraform state. |
terraform state mv |
Move a resource from one address to another in the Terraform state. |
terraform state rm |
Remove a resource from the Terraform state. |
terraform workspace |
Workspace management commands. |
terraform variable |
Manage Terraform variables and their values. |
# Check Version of Terraform
terraform -version
# For Terraform Help
terraform -help
# Show proposed changes that an apply would perform
terraform plan
# Apply all terraform changes
terraform apply
# Apply a specific terraform resource
terraform apply -target=<resource>
# Use a variables file with your terraform apply/plan
terraform apply -var-file=<path_to_vars_file>
# Destroy all terraform resources
terraform destroy
# Destroy select terraform resources
terraform destroy -target=<resource>
# Dry run terraform destroy
terraform plan -destroy
# Mark a resource as tainted and force a destroy/recreate
terraform taint <resource>
# Mark a tainted resource as clean
terraform untaint <resource> Modules/Providers
# Initialize Terraform backend & download specified plugins/modules
terraform init Debug
# Clear out old modules/plugins & re-initialize
terraform rm -rf ./.terraform/ && terraform init
# Show current terraform resources
terraform show
# Pull modules into your .terraform directory
terraform get-update=true
# Validate your terraform code
terraform validate
# Show terraform providers
terraform providers
# Show all terraform outputs
terraform output
# Test resource interpolation (uses your state file)
echo '<resource_expression>' | terraform console
# Pull the remote state
terraform state pull > terraform.tfstate
# Visualize Terraform Dependency Graph (requires graphviz)
terraform graph | dot -Tsvg> graph.svg
# Push local state to remote state (uses file: terraform.tfstate)
terraform state push Workspaces
# Update local state file against real resources
terraform refresh
# Create a terraform workspace
terraform workspace new <workspace>
# Tell Terraform a resource has been moved into a module
terraform state mv <resource> <module>
# Use a terraform workspace
terraform workspace select <workspace>
# Import an existing resource into
terraform state
# Note that this is different syntax for every resource
terraform import <resource>
# List terraform workspaces
terraform workspace list
# Show current terraform workspace
terraform workspace show"