We are going to create the following Azure Resources
azurerm_resource_group
azurerm_virtual_network
azurerm_subnet
azurerm_network_security_group
azurerm_subnet_network_security_group_association
azurerm_network_security_rule
azurerm_public_ip
azurerm_network_interface
azurerm_network_security_group
azurerm_network_interface_security_group_association
Terraform Local Block for Security Rule Ports
Terraform
for_each
Meta-argumentazurerm_network_security_rule
Terraform Local Block for defining custom data to Azure Linux Virtual Machine
azurerm_linux_virtual_machine
Terraform Outputs for the listed Azured Resources
Terraform Functions
Azure Resources and Topology
Steps:
Create folder Azure_Vnet_4-tier and inside it create a folder manifests; inside this manifests folder store all the configuration files.
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 {} }
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" }
Now define terraform local values inside the 3_locals.tffile.
# 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 } }
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 }
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.tffor 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.tffor 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 }
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"]
Create SSH key for Azure Linux VM
# Create Folder cd terraform-manifests/ mkdir ssh-keys # Create SSH Key cd ssh-ekys ssh-keygen \ -m PEM \ -t rsa \ -b 4096 \ -C "azureuser@myserver" \ -f terraform-azure.pem Important Note: If you give passphrase during generation, during everytime you login to VM, you also need to provide passphrase. # List Files ls -lrt ssh-keys/ # Files Generated after above command Public Key: terraform-azure.pem.pub -> Rename as terraform-azure.pub Private Key: terraform-azure.pem # Permissions for Pem file chmod 400 terraform-azure.pem
Keys were created here, one is terraform-azure.pem which is private key and the other one is terraform-azure.pem.pub which is public key associated with azure VM.
Provide 400 permissions for terraform-azure.pem file.
Create tf file for Web Linux-VM Input Variables. ( 7_01_web_linuxvm_input_variables.tf ) This file will be a placeholder for you to add input variables in production I am not going to variablize anything so we are going to hardcode and write directly.
To access our VM via the internet we need a Public IP associated with that VM. (Create file: 7_02_web_linuxvm_publicip.tf)
#Resorce 1: Public-IP resource "azurerm_public_ip" "web_linuxvm_publicip" { name = "${local.resource_name_prefix}-web_linuxvm_publicip" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location allocation_method = "Static" sku = "Standard" domain_name_label = "app1-vm-${random_string.my-random.id}" tags = local.comman_tags }
Now implement the network interface (NIC) for Web LinuxVM (7_03_web_linuxvm_network_interface.tf)
#Resource:Network Interface {NIC} resource "azurerm_network_interface" "web_linuxvm_nic" { name = "${local.resource_name_prefix}-web_linuxvm_nic" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name ip_configuration { name = "web-linuxvm-ip-1" subnet_id = azurerm_subnet.websubnet.id private_ip_address_allocation = "Dynamic" public_ip_address_id = azurerm_public_ip.web_linuxvm_publicip.id } }
Create an NSG for Web LinuxVM and Associate the NSG with Web LinuxVM NIC.
( create file:7_04_web_linuxvm_network_security_group.tf )
# Resource (Optional): Create Network Security Group and Associate to Linux VM Network Interface # Resource-1: Network Security Group (NSG) resource "azurerm_network_security_group" "web_vmnic_nsg" { name = "${azurerm_network_interface.web_linuxvm_nic.name}-nsg" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name } # Resource-2: Associate NSG and Linux VM NIC resource "azurerm_network_interface_security_group_association" "web_vmnic_nsg_associate" { depends_on = [ azurerm_network_security_rule.web_vmnic_nsg_rule_inbound] network_interface_id = azurerm_network_interface.web_linuxvm_nic.id network_security_group_id = azurerm_network_security_group.web_vmnic_nsg.id } # Resource-3: NSG Rules ## Locals Block for Security Rules locals { web_vmnic_inbound_ports_map = { "100" : "80", # If the key starts with a number, you must use the colon syntax ":" instead of "=" "110" : "443", "120" : "22" } } ## NSG Inbound Rule for WebTier Subnets resource "azurerm_network_security_rule" "web_vmnic_nsg_rule_inbound" { for_each = local.web_vmnic_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.web_vmnic_nsg.name }
Create an Azure Linux VM and its resources.
( Create file: 7_05_web_linuxvm_resource.tf )
# Locals Block for custom data locals { webvm_custom_data = <<CUSTOM_DATA #!/bin/sh #!/bin/sh #sudo yum update -y sudo yum install -y httpd sudo systemctl enable httpd sudo systemctl start httpd sudo systemctl stop firewalld sudo systemctl disable firewalld sudo chmod -R 777 /var/www/html sudo echo "Welcome to stacksimplify - WebVM App1 - VM Hostname: $(hostname)" > /var/www/html/index.html sudo mkdir /var/www/html/app1 sudo echo "Welcome to stacksimplify - WebVM App1 - VM Hostname: $(hostname)" > /var/www/html/app1/hostname.html sudo echo "Welcome to stacksimplify - WebVM App1 - App Status Page" > /var/www/html/app1/status.html sudo echo '<!DOCTYPE html> <html> <body style="background-color:rgb(250, 210, 210);"> <h1>Welcome to Stack Simplify - WebVM APP-1 </h1> <p>Terraform Demo</p> <p>Application Version: V1</p> </body></html>' | sudo tee /var/www/html/app1/index.html sudo curl -H "Metadata:true" --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2020-09-01" -o /var/www/html/app1/metadata.html CUSTOM_DATA } # Resource: Azure Linux Virtual Machine resource "azurerm_linux_virtual_machine" "web_linuxvm" { name = "${local.resource_name_prefix}-web-linuxvm" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location size = "Standard_DS1_v2" admin_username = "azureuser" network_interface_ids = [ azurerm_network_interface.web_linuxvm_nic.id ] admin_ssh_key { username = "azureuser" public_key = file("/home/rohit/Documents/terraform_with_azure/Project-TF-AZ/azure_tf_project/manifests/ssh-keys/terraform-azure.pub") } os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "RedHat" offer = "RHEL" sku = "83-gen2" version = "latest" } custom_data = base64encode(local.webvm_custom_data) }
Output values for Linux VM (Create File: 7_06_web_linuxvm_outputs.tf)
```bash
Public IP Outputs
Public IP Address
output "web_linuxvm_public_ip" { description = "Web Linux VM Public Address" value = azurerm_public_ip.web_linuxvm_publicip.ip_address }
Network Interface Outputs
Network Interface ID
output "web_linuxvm_network_interface_id" { description = "Web Linux VM Network Interface ID" value = azurerm_network_interface.web_linuxvm_nic.id }
Network Interface Private IP Addresses
output "web_linuxvm_network_interface_private_ip_addresses" { description = "Web Linux VM Private IP Addresses" value = [azurerm_network_interface.web_linuxvm_nic.private_ip_addresses] }
Linux VM Outputs
Virtual Machine Public IP
output "web_linuxvm_public_ip_address" { description = "Web Linux Virtual Machine Public IP" value = azurerm_linux_virtual_machine.web_linuxvm.public_ip_address }
Virtual Machine Private IP
output "web_linuxvm_private_ip_address" { description = "Web Linux Virtual Machine Private IP" value = azurerm_linux_virtual_machine.web_linuxvm.private_ip_address }
Virtual Machine 128-bit ID
output "web_linuxvm_virtual_machine_id_128bit" { description = "Web Linux Virtual Machine ID - 128-bit identifier" value = azurerm_linux_virtual_machine.web_linuxvm.virtual_machine_id }
Virtual Machine ID
output "web_linuxvm_virtual_machine_id" { description = "Web Linux Virtual Machine ID " value = azurerm_linux_virtual_machine.web_linuxvm.id }
13. Execute Terraform Commands
```bash
# Terraform Initialize
terraform init
# Terraform Validate
terraform validate
# Terraform Plan
terraform plan
# Terraform Apply
terraform apply -auto-approve
Verify Resources
1. Azure Resource Group 2. Azure Virtual Network 3. Azure Subnets (Web, App, DB, Bastion) 4. Azure Network Security Groups (Web, App, DB, Bastion) 5. View the topology 6. Verify Terraform Outputs in Terraform CLI # Verify Resources - Web Linux VM 1. Verify Public IP created for Web Linux VM 2. Verify Network Interface created for Web Linux VM 3. Verify Web Linux VM 4. Verify Network Security Groups associated with VM (web Subnet NSG and NIC NSG) 5. View Topology at Web Linux VM -> Networking 6. Connect to Web Linux VM ssh -i ssh-keys/terraform-azure.pem azureuser@<Web-LinuxVM-PublicIP> sudo su - cd /var/log tail -100f cloud-init-output.log cd /var/www/html ls -lrt cd /var/www/html/app1 ls -lrt exit exit 7. Access Sample Application http://<PUBLIC-IP>/ http://<PUBLIC-IP>/app1/index.html http://<PUBLIC-IP>/app1/hostname.html http://<PUBLIC-IP>/app1/status.html http://<PUBLIC-IP>/app1/metadata.html
-- Azure Resource Group
-- Azure Virtual Network
-- Azure Subnets (Web, App, DB, Bastion)
-- Azure Network Security Groups (Web, App, DB, Bastion)
-- View the topology
-- Verify Public IP created for Web Linux VM
-- Verify Network Interface created for Web Linux VM
-- Verify Web Linux VM
-- Verify Network Security Groups associated with VM (web Subnet NSG and NIC NSG)
-- View Topology at Web Linux VM -> Networking
-- Verify Terraform Outputs in Terraform CLI
-- Connect to Web Linux VM
Comment NSG associated with VM
(7_04_web_linuxvm_network_security_group.tf)
# Comment code in c7-04-web-linuxvm-network-security-group.tf NSG associated with Web Linux VM NIC is commented # Terraform Validate terraform validate # Terraform Plan terraform plan # Terraform Apply terraform apply -auto-approve # Verify NSG associated with VM 1. Verify Network Security Groups associated with VM (web Subnet NSG only) 2. Access Application http://<PUBLIC-IP>/app1/metadata.html
Here you can see only NSG is present at the subnet level.
Delete Resources
We have completed the VM setup. ๐
In our next blog (Part 2), we will focus on setting up a bastion host and service, and then accessing this web subnet-related VM privately within the virtual network itself.
GitHub Repo Link:github.com/DeoreRohit4/Azure-Infrastructure..
Keep Exploring...