Azure Virtual Network 4-Tier Design using Terraform

Terraform on Azure Cloud

Azure Virtual Network 4-Tier Design using Terraform

Azure Virtual Network Design

  • We are going to design the 4-Tier Azure Virtual Network here
  1. Azure Virtual Network

  2. WebTier Subnet + WebTier Network Security Group (Ports 80, 443)

  3. AppTier Subnet + AppTier Network Security Group (Ports 8080, 80, 443)

  4. DBTier Subnet + DBTier Network Security Group (Ports 3306, 1433, 5432)

  5. Bastion Subnet + Bastion Network Security Group (Ports 80, 3389)

  6. Terraform for_each Meta-Argument

Azure Resources created

  1. azurerm_resource_group

  2. azurerm_virtual_network

  3. azurerm_subnet

  4. azurerm_network_security_group

  5. azurerm_subnet_network_security_group_association

  6. azurerm_network_security_rule

Steps:

  1. Create folder Azure_Vnet_4-tier and inside it create a folder manifests; inside this manifests folder store all the configuration files.

  2. Create a 1_versions.tf file.

    In the 1_versions.tf file we have to implement "Terraform settings and providers block"

     # Terraform Settings Block
     terraform {
       required_providers {
         azurerm = {
           source = "hashicorp/azurerm"
           version = "3.110.0"
         }
       }
     }
    
     # Providers Block
     provider "azurerm" {
      features {}
     }
    
  3. In this step define generic input variables in 2_generic_input_variables.tf file.

     # --- Generic Input Variables ---
     # Business Division
     variable "business_division" {
       description = "Business Division is the large organization this Ifrastructure belongs"
       type = string
       default = "sap"
     }
    
     # Environment Variables
     variable "environment" {
       description = "Environment Variable used as a prefix"
       default = "dev"
     }
    
     # Azure Resource Group Name
     variable "resource_group_name" {
       description = "Resource Group Name"
       default = "rg_default"
     }
    
     # Azure Resource Location
     variable "resource_group_location" {
       description = "Region in which azure resources to be created"
       default = "eastus2"
     }
    
  4. Now define terraform local values inside the 3_locals.tf file.

     # Define Local Values
     locals {
       owners = var.business_division
       environment = var.environment
       resource_name_prefix = "${var.business_division}-${var.environment}"
       comman_tags = {
         owners = local.owners
         environment = local.environment
       }
     }
    
  5. In this step create a 4_random_resource.tf file for creating Random Strings and 5_resource_group.tf for resources group also add Random provider in 1_versions.tf file.

     # Terraform Settings Block
     terraform {
       required_providers {
         azurerm = {
           source = "hashicorp/azurerm"
           version = "3.110.0"
         }
          random = {
           source = "hashicorp/random"
           version = "3.6.2"
         }
       }
     }
    
     # Providers block
     provider "azurerm" {
      features {}
     }
    

     # Random string resource
     resource "random_string" "my-random" {
       length = 6
       upper = false
       special = false
     }
    

     # Resource-1 : Azure Resource Group
     resource "azurerm_resource_group" "rg" {
       name = "${local.resource_name_prefix}-${var.resource_group_name}-${random_string.my-random.id}"
       location = var.resource_group_location
       tags = local.comman_tags
     }
    
  6. Create VNET Input Variables and VNET Resources.

    Create File: 6_01_vnet_input_variables.tf

     # Virtual Network Name vnet_name
     variable "vnet_name" {
         description = "Virtual Network Name"
         type = string
         default = "vnet-default"
     }
    
     # Vnet Address space
     variable "vnet_address_space" {
         description = "Virtual Network Address Space"
         type = list(string)
         default = [ "10.0.0.0/16" ]
     }
    
     # Web Subnet Name web_subnet_name
     variable "web_subnet_name" {
         description = "Vnet web subnet name"
         type = string
         default = "websubnet"
     }
    
     # Web subnet address space
     variable "web_subnet_address" {
         description = "Vnet web subnet address space"
         type = list(string)
         default = ["10.0.11.0/24"]
     }
    
     # Database Subnet Name
     variable "db_subnet_name" {
         description = "Vnet Database subnet name"
         type = string
         default = "dbsubnet"
     }
    
     # Database Subnet Address space
     variable "db_subnet_address" {
         description = "Virtual Network Database subnet address space"
         type = list(string)
         default = [ "10.0.21.0/24" ]
     }
    
     # Bastion / Management Subnet Name
     variable "bastion_subnet_name" {
         description = "Virtual Network Bastion Subnet Name"
         type = string
         default = "bastionsubnet"
     }
    
     # Bastion / Management Subnet Address Space
     variable "bastion_subnet_address" {
         description = "Virtual Network Bastion Subnet Address"
         type = list(string)
         default = [ "10.0.100.0/24" ]
     }
    

    Create File: 6_02_virtual_network.tf

     # Create Virtual Netwok
     resource "azurerm_virtual_network" "vnet" {
         name = "${local.resource_name_prefix}-${var.vnet_name}"
         address_space = var.vnet_address_space
         location = azurerm_resource_group.rg.location
         resource_group_name = azurerm_resource_group.rg.name
         tags = local.comman_tags
     }
    

    This completes the creation of virtual network related input variables and the virtual network resource itself.

    Now we will create the web tier related subnet and its equivalent associated network security group and rules.

    Create File: 6_03_web_subnet_and_nsg.tf

     # Resource 1: Web-Tier subnet
     resource "azurerm_subnet" "websubnet" {
         name = "${azurerm_virtual_network.vnet.name}-${var.web_subnet_name}"
         resource_group_name = azurerm_resource_group.rg.name
         virtual_network_name = azurerm_virtual_network.vnet.name
         address_prefixes = var.web_subnet_address
     }
    
     # Resource 2: Network Security Group (NSG)
     resource "azurerm_network_security_group" "web_subnet_nsg" {
         name = "${azurerm_subnet.websubnet.name}-nsg"
         location = azurerm_resource_group.rg.location
         resource_group_name = azurerm_resource_group.rg.name
     }
    
     # Resource 3: Associate NSG and Subnet
     resource "azurerm_subnet_network_security_group_association" "web_subnet_nsg_association" {
         depends_on = [ azurerm_network_security_rule.web_nsg_rule_inbound ]
        subnet_id = azurerm_subnet.websubnet.id
        network_security_group_id = azurerm_network_security_group.web_subnet_nsg.id
     }
    
     # Resource 4: NSG rules
    
     ## Locals Block for security rules
     locals {
       web_inbound_ports_map = {
         "100" : "80", 
         "110" : "443",
         "120" : "22"
         # if the ley start with number you must use the colon syntax (:) instead of equal (=).
       }
     }
    
     # NSG rules
     resource "azurerm_network_security_rule" "web_nsg_rule_inbound" {
         for_each = local.web_inbound_ports_map
         name                        = "RulePort${each.value}"
         priority                    = each.key
         direction                   = "Inbound"
         access                      = "Allow"
         protocol                    = "Tcp"
         source_port_range           = "*"
         destination_port_range      = each.value
         source_address_prefix       = "*"
         destination_address_prefix  = "*"
         resource_group_name         = azurerm_resource_group.rg.name
         network_security_group_name = azurerm_network_security_group.web_subnet_nsg.name
     }
    

    Create file: 6_04_app_subnet_and_nsg.tf for creating the app tier related subnet and it's equivalent associated network security group and rules.

     # Resource-1: Create AppTier Subnet
     resource "azurerm_subnet" "appsubnet" {
       name                 = "${azurerm_virtual_network.vnet.name}-${var.app_subnet_name}"
       resource_group_name  = azurerm_resource_group.rg.name
       virtual_network_name = azurerm_virtual_network.vnet.name
       address_prefixes     = var.app_subnet_address  
     }
    
     # Resource-2: Create Network Security Group (NSG)
     resource "azurerm_network_security_group" "app_subnet_nsg" {
       name                = "${azurerm_subnet.appsubnet.name}-nsg"
       location            = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
     }
    
     # Resource-3: Associate NSG and Subnet
     resource "azurerm_subnet_network_security_group_association" "app_subnet_nsg_associate" {
       depends_on = [ azurerm_network_security_rule.app_nsg_rule_inbound]    
       subnet_id                 = azurerm_subnet.appsubnet.id
       network_security_group_id = azurerm_network_security_group.app_subnet_nsg.id
     }
    
     # Resource-4: Create NSG Rules
     ## Locals Block for Security Rules
     locals {
       app_inbound_ports_map = {
         "100" : "80", # If the key starts with a number, you must use the colon syntax ":" instead of "="
         "110" : "443",
         "120" : "8080",
         "130" : "22"
       } 
     }
     ## NSG Inbound Rule for AppTier Subnets
     resource "azurerm_network_security_rule" "app_nsg_rule_inbound" {
       for_each = local.app_inbound_ports_map
       name                        = "Rule-Port-${each.value}"
       priority                    = each.key
       direction                   = "Inbound"
       access                      = "Allow"
       protocol                    = "Tcp"
       source_port_range           = "*"
       destination_port_range      = each.value 
       source_address_prefix       = "*"
       destination_address_prefix  = "*"
       resource_group_name         = azurerm_resource_group.rg.name
       network_security_group_name = azurerm_network_security_group.app_subnet_nsg.name
     }
    

    Create file: 6_05_db_subnet_and_nsg.tf for creating the database tier related subnet and its equivalent associated network security group and rules.

     # Resource-1: Create DBTier Subnet
     resource "azurerm_subnet" "dbsubnet" {
       name                 = "${azurerm_virtual_network.vnet.name}-${var.db_subnet_name}"
       resource_group_name  = azurerm_resource_group.rg.name
       virtual_network_name = azurerm_virtual_network.vnet.name
       address_prefixes     = var.db_subnet_address  
     }
    
     # Resource-2: Create Network Security Group (NSG)
     resource "azurerm_network_security_group" "db_subnet_nsg" {
       name                = "${azurerm_subnet.dbsubnet.name}-nsg"
       location            = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
     }
    
     # Resource-3: Associate NSG and Subnet
     resource "azurerm_subnet_network_security_group_association" "db_subnet_nsg_associate" {
       depends_on = [ azurerm_network_security_rule.db_nsg_rule_inbound]    
       subnet_id                 = azurerm_subnet.dbsubnet.id
       network_security_group_id = azurerm_network_security_group.db_subnet_nsg.id
     }
    
     # Resource-4: Create NSG Rules
     ## Locals Block for Security Rules
     locals {
       db_inbound_ports_map = {
         "100" : "3306", # If the key starts with a number, you must use the colon syntax ":" instead of "="
         "110" : "1433",
         "120" : "5432"
       } 
     }
     ## NSG Inbound Rule for DBTier Subnets
     resource "azurerm_network_security_rule" "db_nsg_rule_inbound" {
       for_each = local.db_inbound_ports_map
       name                        = "Rule-Port-${each.value}"
       priority                    = each.key
       direction                   = "Inbound"
       access                      = "Allow"
       protocol                    = "Tcp"
       source_port_range           = "*"
       destination_port_range      = each.value 
       source_address_prefix       = "*"
       destination_address_prefix  = "*"
       resource_group_name         = azurerm_resource_group.rg.name
       network_security_group_name = azurerm_network_security_group.db_subnet_nsg.name
     }
    

    Create file: 6_06_bastion_subnet_and_nsg.tf for creating the bastion subnet and its equivalent associated network security group and rules.

     # Resource-1: Create Bastion / Management Subnet
     resource "azurerm_subnet" "bastionsubnet" {
       name                 = "${azurerm_virtual_network.vnet.name}-${var.bastion_subnet_name}"  
       resource_group_name  = azurerm_resource_group.rg.name
       virtual_network_name = azurerm_virtual_network.vnet.name
       address_prefixes     = var.bastion_subnet_address
     }
    
     # Resource-2: Create Network Security Group (NSG)
     resource "azurerm_network_security_group" "bastion_subnet_nsg" {
       name                = "${azurerm_subnet.bastionsubnet.name}-nsg"
       location            = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
     }
    
     # Resource-3: Associate NSG and Subnet
     resource "azurerm_subnet_network_security_group_association" "bastion_subnet_nsg_associate" {
       depends_on = [ azurerm_network_security_rule.bastion_nsg_rule_inbound]      
       subnet_id                 = azurerm_subnet.bastionsubnet.id
       network_security_group_id = azurerm_network_security_group.bastion_subnet_nsg.id
     }
    
     # Resource-4: Create NSG Rules
     ## Locals Block for Security Rules
     locals {
       bastion_inbound_ports_map = {
         "100" : "22", # If the key starts with a number, you must use the colon syntax ":" instead of "="
         "110" : "3389"
       } 
     }
     ## NSG Inbound Rule for Bastion / Management Subnets
     resource "azurerm_network_security_rule" "bastion_nsg_rule_inbound" {
       for_each = local.bastion_inbound_ports_map
       name                        = "Rule-Port-${each.value}"
       priority                    = each.key
       direction                   = "Inbound"
       access                      = "Allow"
       protocol                    = "Tcp"
       source_port_range           = "*"
       destination_port_range      = each.value 
       source_address_prefix       = "*"
       destination_address_prefix  = "*"
       resource_group_name         = azurerm_resource_group.rg.name
       network_security_group_name = azurerm_network_security_group.bastion_subnet_nsg.name
     }
    

    Create File: 6_07_vnet_outputs.tf for Virtual Network Outputs.

     # Virtual Network Outputs
    
     ## Vnet name -
     output "virtual_network_name" {
       description = "Virtual network name"
       value = azurerm_virtual_network.vnet.name
     }
    
     ## Vnet id -
     output "virtual_network_id" {
         description = "virtual network ID"
         value = azurerm_virtual_network.vnet.id
     }
    
     /* Subnet Outputs (We will write for one
      web subnet and rest all we will ignore for now) */
     ## Subnet Name 
     output "web_subnet_name" {
       description = "WebTier Subnet Name"
       value = azurerm_subnet.websubnet.name
     }
    
     ## Subnet ID 
     output "web_subnet_id" {
       description = "WebTier Subnet ID"
       value = azurerm_subnet.websubnet.id
     }
    
     # Network Security Outputs
     ## Web Subnet NSG Name 
     output "web_subnet_nsg_name" {
       description = "WebTier Subnet NSG Name"
       value = azurerm_network_security_group.web_subnet_nsg.name
     }
    
     ## Web Subnet NSG ID 
     output "web_subnet_nsg_id" {
       description = "WebTier Subnet NSG ID"
       value = azurerm_network_security_group.web_subnet_nsg.id
     }
    
  7. Create File: terraform.tfvars

    
     business_division = "hr"
     environment = "dev"
     resource_group_name = "rg"
     resource_group_location = "eastus"
    
     vnet_name = "vnet"
     vnet_address_space = ["10.1.0.0/16"]
    
     web_subnet_name = "websubnet"
     web_subnet_address = ["10.1.1.0/24"]
    
     app_subnet_name = "appsubnet"
     app_subnet_address = ["10.1.11.0/24"]
    
     db_subnet_name = "dbsubnet"
     db_subnet_address = ["10.1.21.0/24"]
    
     bastion_subnet_name = "bastionsubnet"
     bastion_subnet_address = ["10.1.100.0/24"]
    

    We are done with all our configuration files and its time to execute terrafrom commands and verify all resources.

  8. Execution

    -- terraform init command

    -- terraform validate command

    -- terrafrom plan command

    -- terrafrom apply command

    Enter yes.

  9. Go to Azure portal, check for resource group and verify that all the resources are get created or not.

    Here you can see vnet and all 4 subnets are created.

    Now lets go inside hr-dev-vnet.

    Go to Address space and verify that you have 10.1.0.0/16 which took it from terraform.tfvars file.

    Go to Subnets and verify subnets (Web, App, DB, Bastion).

    Next is to verify Azure Network Security Groups (Web, App, DB, Bastion)

    Now in Virtual Network View the topology.

    Now once you verified that all the resources are created you can go for destroying the resources.

  10. Delete Resources

    # Delete Resources
    terraform destroy 
    terraform apply -destroy
    
    # Clean-Up Files
    rm -rf .terraform* 
    rm -rf terraform.tfstate*
    

    -- terraform destroy command

    The deletion of security rules from Azure NSGs failed due to connection issues, suggesting a reset or lost connection to the Azure management API;

    -- rm -rf .terraform*

    -- rm -rf terraform.tfstate*

    We have completed the implementation of a 4-Tier Azure Virtual Network using Terraform. If you have any further questions, please drop them in the comments.

Github: github.com/DeoreRohit4/Azure-Virtual-Networ..

Linkedin: linkedin.com/in/rohitdeore


Keep Exploring...