Terraform | Deploy Virtual Machines to Azure

Header terraform
Reading Time: 5 minutes

In a previous post, Azure Bicep | Deploy Virtual Machines – GetToTheCloud, we have seen how to use Azure Bicep to deploy resources. To do these kind of actions there are multiple possibilities. A Counter part of this is using Terraform. A big difference is that Terraform is multi-cloud and Azure Bicep only to Azure. So let’s see when we use Terraform what we need to do to achieve the same result.

Azure App Registration

Terraform is using an app registration for the deployment of the resources. Let’s create that.

First we need to create an app registration to have a service principal. Login to https://portal.azure.com and browse to Azure Active Directory / App Registrations. Once there click on New registration and fill in the form with a name and register the application. Once created the app registration browse to the certificates & secrets tab in the app registration. Create a new client secret and write down, we will need this later on.

Now browse to subscriptions

Select your subscription and click Access control (IAM). Select Add to add permissions.

Select Privileged administrator roles and click Role

Select Contributor and click Members

Select Select Members and search for you app registration which you created at the first step. In this case I named the app Terraform deployer.

Now we have given permissions to the app on subscription level, we need to write down some information:

  • subscriptionId
  • applicationId (clientId)
  • tenantId
  • clientsecret

Goal

In this example we will start deploying some Virtual Machines. These Virtual Machines can be used for a testlab.

Virtual MachineIp AddressPurpose
DC0110.10.0.4Domain Controller
EX0110.10.0.5Exchange Server
WIN1110.10.0.6Client for Access to servers over RDP


So three virtual machines, network and network security groups, public ips and storage will be created with Terraform to Azure.

Terraform

Now we have an app registration with the correct permissions for terraform, we can start creating the files.

  • testdomain.tf 
  • variables.tf — It is used to declare the variables which are to be used in TF code. Even you can initialize the default values as well.
  • terraform.tfvars — It is used to define the variables declared in variables.tf (if and only if it is not already defined in variables.tf)

In this case I use both files, variables.tf and terraform.tfvars, to understand the working of those files.

variables.tf

In this file you define the global variables to reuse in the different terraform files.

variable "node_location" {
  type = string
}

variable "resourcegroup" {
  type = string
}

variable "node_address_space" {
  default = ["10.0.0.0/16"]
}

#variable for network range
variable "node_address_prefix" {
  default = "10.0.1.0/24"
}

variable "virtualnetwork" {
  type = string
}

As you can see, I left open node_location, resourcegroup and virtualnetwork. This information will come from terraform.tfvars.

terraform.tfvars

node_location  = "westeurope"
resourcegroup  = "TestDomainTerraform"
virtualnetwork = "TerraformTestDomain"

testdomain.tf

# azure connection
provider "azurerm" {
  # whilst the `version` attribute is optional, we recommend pinning to a given version of the Provider
  subscription_id = "xxx-xxx-xxxx-xxxx"
  client_id       = "xxx-xxx-xxxx-xxxx"
  client_secret   = "xxx-xxx-xxxx-xxxx"
  tenant_id       = "xxx-xxx-xxxx-xxxx"
  version         = "=2.0.0"
  features {}
}

Fill out the connection details that where written down earlier.

variable "DCcomputername" {
  default = "DC01"
}

variable "EXcomputername" {
  default = "EX01"
}

variable "WIN11computername" {
  default = "WIN11"
}

Variable names for the different machines we would like to deploy.

resource "azurerm_resource_group" "rg" {
  name     = var.resourcegroup
  location = var.node_location
}

resource "azurerm_virtual_network" "vnet" {
  name                = var.virtualnetwork
  address_space       = var.node_address_space
  location            = var.node_location
  resource_group_name = azurerm_resource_group.rg.name
}

Create a resource group and virtual network using the variables set in variables.tf.

resource "azurerm_network_security_group" "nsgdc" {
  name                = "NSG-${var.DCcomputername}"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "allow-port-5985"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "5985"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_security_group" "nsgex" {
  name                = "NSG-${var.EXcomputername}"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "allow-port-5985"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "5985"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "allow-port-443"
    priority                   = 101
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_security_group" "nsgwin11" {
  name                = "NSG-${var.WIN11computername}"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "allow-port-3389"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

Adding the network security groups for the virtual machines with the rules. In the NAME we use a variable that we define earlier.

resource "azurerm_subnet" "frontendsubnet" {
  name                 = "frontendSubnet"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefix       = var.node_address_prefix
}

resource "azurerm_public_ip" "expublicip" {
  name                = "${var.EXcomputername}-PIP"
  location            = var.node_location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
  sku                 = "Basic"
}

resource "azurerm_public_ip" "win11publicip" {
  name                = "${var.WIN11computername}-PIP"
  location            = var.node_location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
  sku                 = "Basic"
}

Add the subnet and the public ip’s for the EX01 and WIN11

resource "azurerm_network_interface" "dcnic" {
  name                = "${var.DCcomputername}-nic"
  location            = var.node_location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "ipconfig1"
    subnet_id                     = azurerm_subnet.frontendsubnet.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_network_interface" "exnic" {
  name                = "${var.EXcomputername}-nic"
  location            = var.node_location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "ipconfig1"
    subnet_id                     = azurerm_subnet.frontendsubnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.expublicip.id
  }
}

resource "azurerm_network_interface" "win11nic" {
  name                = "${var.WIN11computername}-nic"
  location            = var.node_location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "ipconfig2"
    subnet_id                     = azurerm_subnet.frontendsubnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.win11publicip.id
  }
}

Creating the virtual nic’s for the machines and adding the public ip’s to the nic’s.

resource "azurerm_network_interface_security_group_association" "nsgexassociation" {
  network_interface_id      = azurerm_network_interface.exnic.id
  network_security_group_id = azurerm_network_security_group.nsgex.id
}

resource "azurerm_network_interface_security_group_association" "nsgwin11association" {
  network_interface_id      = azurerm_network_interface.win11nic.id
  network_security_group_id = azurerm_network_security_group.nsgwin11.id
}

Associate the network security groups with the virtual nics.

resource "azurerm_windows_virtual_machine" "DCvirtualmachine" {
  name                  = var.DCcomputername
  location              = var.node_location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.dcnic.id]
  size                  = "Standard_B2s"
  admin_username        = "adminuser"
  admin_password        = "Password123!"

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter"
    version   = "latest"
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
}

resource "azurerm_windows_virtual_machine" "EXvirtualmachine" {
  name                  = var.EXcomputername
  location              = var.node_location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.exnic.id]
  size                  = "Standard_B2s"
  admin_username        = "adminuser"
  admin_password        = "Password123!"

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter"
    version   = "latest"
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
}

resource "azurerm_windows_virtual_machine" "WIN11virtualmachine" {
  name                  = var.WIN11computername
  location              = var.node_location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.win11nic.id]
  size                  = "Standard_B2s"
  admin_username        = "adminuser"
  admin_password        = "Password123!"

  source_image_reference {
    publisher = "microsoftwindowsdesktop"
    offer     = "windows-11"
    sku       = "win11-22h2-avd"
    version   = "latest"
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
} 

Add the virtual machines to create.

Now you have a terraform file with all the resources needed for a deployment of three virtual machines.

Commands for deployment

To deploy the terraform files, you need to install/download terraform. Once downloaded and installed you can browse with a command line to the folder and run commands:

# check if all resources for connection are available
.\terraform.exe init

# plan deployment and check if files are correct
.\terraform.exe plan

# deploy terraform files
.\terraform.exe apply

Results

Share and Enjoy !

Shares
Designer (23)

Stay close to the action—follow GetToThe.Cloud across social!
Deep dives and hands‑on how‑tos on Azure Local, hybrid cloud, automation, PowerShell/Bicep, AVD + FSLogix, image pipelines, monitoring, networking, and resilient design when the internet/Azure is down.

🔗 Our channels
▶️ YouTube: https://www.youtube.com/channel/UCa33PgGdXt-Dr4w3Ub9hrdQ
💼 LinkedIn Group: https://www.linkedin.com/groups/9181126/
✖️ X (Twitter): https://x.com/Gettothecloud
🎵 TikTok: https://www.tiktok.com/@gettothecloud
🐙 GitHub: https://github.com/GetToThe-Cloud/Website
💬 Slack: DM us for an invite
📲 WhatsApp: DM for the community link

WP Twitter Auto Publish Powered By : XYZScripts.com
We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners. View more
Cookies settings
Accept
Privacy & Cookie policy
Privacy & Cookies policy
Cookie name Active

Who we are

Our website address is: https://www.gettothe.cloud

Comments

When visitors leave comments on the site we collect the data shown in the comments form, and also the visitor’s IP address and browser user agent string to help spam detection. An anonymized string created from your email address (also called a hash) may be provided to the Gravatar service to see if you are using it. The Gravatar service privacy policy is available here: https://automattic.com/privacy/. After approval of your comment, your profile picture is visible to the public in the context of your comment.

Media

If you upload images to the website, you should avoid uploading images with embedded location data (EXIF GPS) included. Visitors to the website can download and extract any location data from images on the website.

Cookies

If you leave a comment on our site you may opt-in to saving your name, email address and website in cookies. These are for your convenience so that you do not have to fill in your details again when you leave another comment. These cookies will last for one year. If you visit our login page, we will set a temporary cookie to determine if your browser accepts cookies. This cookie contains no personal data and is discarded when you close your browser. When you log in, we will also set up several cookies to save your login information and your screen display choices. Login cookies last for two days, and screen options cookies last for a year. If you select "Remember Me", your login will persist for two weeks. If you log out of your account, the login cookies will be removed. If you edit or publish an article, an additional cookie will be saved in your browser. This cookie includes no personal data and simply indicates the post ID of the article you just edited. It expires after 1 day.

Embedded content from other websites

Articles on this site may include embedded content (e.g. videos, images, articles, etc.). Embedded content from other websites behaves in the exact same way as if the visitor has visited the other website. These websites may collect data about you, use cookies, embed additional third-party tracking, and monitor your interaction with that embedded content, including tracking your interaction with the embedded content if you have an account and are logged in to that website.

Who we share your data with

If you request a password reset, your IP address will be included in the reset email.

How long we retain your data

If you leave a comment, the comment and its metadata are retained indefinitely. This is so we can recognize and approve any follow-up comments automatically instead of holding them in a moderation queue. For users that register on our website (if any), we also store the personal information they provide in their user profile. All users can see, edit, or delete their personal information at any time (except they cannot change their username). Website administrators can also see and edit that information.

What rights you have over your data

If you have an account on this site, or have left comments, you can request to receive an exported file of the personal data we hold about you, including any data you have provided to us. You can also request that we erase any personal data we hold about you. This does not include any data we are obliged to keep for administrative, legal, or security purposes.

Where we send your data

Visitor comments may be checked through an automated spam detection service.
Save settings
Cookies settings