This article is a quick introduction to Terraform. I’m going to keep it simple, with all code local to laptop, state is just local and running of Terraform itself is from laptop.
We are going to build a Virtual Machine running Windows Server 2022 (and all the required infrastructure required for that and for us to remote onto it via the Internet).
This includes:
- Resource Group – A placeholder for all resources for this tutorial
- Virtual Network – The network the VM lives on
- Public IP Address – The IP address to connect to the VM via the internet
- Network Security Group – A Security group to restrict the ports open to connect (and from where)
- Virtual Machine – The Windows Server itself
Required Software
To begin you need VSCode installed (free download from Microsoft) or whatever IDE or even text editor you prefer.
Terraform installed (free download from HashiCorp)
You’ll need an Azure Subscription (you can sign up for a free trial account)
Azure CLI (free download from Microsoft)
Starting VSCode
When you first open VSCode it will look something like this
In the top left click the button that looks like two pages, one on top of the other
This opens the folder view, click ‘Open Folder’ then select a folder where you want to save all your code files. I typically create a folder named ‘VSCode’ under documents then within create folder for each project. So in this example create a folder named ‘Terraform Tutorial’, select it and click Open.
This will load this folder into the folder view (since its blank there will be nothing to see).
Now we can create our first file.
From the highlighted buttons circled above, the first is create new file and the second create folder.
VSCode Extensions
Click this icon on the left This is the extensions icon.
Search for Terraform and install the HashiCorp Terraform extension. This will enable syntax highlighting and IntelliSense (this suggests code as you type).
Also search and install ‘Azure CLI Tools’, this is the same as above but for Azure CLI commands.
Azure CLI Login
First we need to login and authenticate to Azure, there is a number of ways to do this see here. For simplicity here we are just going to authenticate using CLI.
In the View menu click Terminal to show the command window with VSCode.
to authenticate to Azure type:
az login
This will launch a browser window for you to login to Microsoft Azure. Once you have completed the login you should get a message saying you can close the window and return to VSCode.
now you can check you are logged into the correct subscription using:
az account list
If you have a few subscriptions and the wrong one is selected (stated as the ‘IsDefault: True’ flag, you can change the selected subscription using:
az account set --subscription="SUBSCRIPTION_ID"
Where Subscription ID is the ID of the subscription you want to use.
First Terraform files
Click create new file to make an empty file enter a name ‘main.tf’ and press enter. This will be our first terraform file. All terraform code files end with the extension .tf
Enter the following:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0.2"
}
}
required_version = ">= 1.1.0"
}
provider "azurerm" {
features {}
}
All of this simply tells terraform you want to use the AzureRM provider and is the base of the terraform setup.
When terraform runs it parses all files in the directory your running from so we can keep things tidy by using separate files for parts and to keep this provider block separate.
Create a new file but name this one ‘resources.tf’ In this file we will create our resources.
Enter the following:
resource "azurerm_resource_group" "rg" {
name = "RG-Test"
location = "uksouth"
}
This will create a Resource Group named’ RG-Test’ in the UK South region.
First Run – Resource Group
To run this we first need to initialise, in the terminal window ensure your within the directory where your terraform files are located.
Enter:
terraform init
The output should looks something like:
You can use the terraform fmt
command below to format your code correctly and consistently, this ensures all the indentations are correct – great if you like to keep everything looking tidy.
terraform fmt
You can also make sure your configuration is syntactically valid and internally consistent by using the terraform validate
command
terraform validate
Next when you are ready to run you can test or ‘plan’ to see what terraform is actually going to do by running terraform plan
command. This will output exactly what terraform is going to do in your environment.
terraform plan
As you can see in the above, at the end it shows ‘Plan: 1 to add, 0 to change, 0 to destroy.
‘ and above that with the green + you can see exactly what is going to be added to our environment. Since this is very simple, its just going to create a resource group in UK South named ‘RG-Test’.
If you are happy you can run the command terraform apply
to actually do the action. It will ask you to confirm with ‘yes’.
terraform apply
So that you don’t have to enter ‘yes’ to approve you can use the auto-approve flag as below.
terraform apply -auto-approve
Once its successful take a look at your Azure Portal and you should see the new Resource Group ‘RG-Test’ created.
Also note that if you run terraform apply again, it’ll tell you that no changes need to be made as the RG-Test resource group already exists. This is because Terraform unlike PowerShell is an ‘end state’ language. Meaning that the code defines how the end should look like and terraform compares the state with the code and only makes changes are required.
Lastly you can clean up everything that terraform created with the terraform destroy
command.
terraform destroy
Use this last one carefully.
Virtual Network
Using the following block to create the Virtual Network
# Create virtual network
resource "azurerm_virtual_network" "my_terraform_network" {
name = "test-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
Note the location and resource group name used for the virtual network, since we’ve already defined the location and resource group when we created that we can reference the resource group block as shown here. azurerm_resource_group.rg.location
azurerm_resource_group
– the resource blockrg
– the name of the above blocklocation
– the parameter we are referencing
Next we need to create the subnet within the network
# Create subnet
resource "azurerm_subnet" "my_terraform_subnet" {
name = "main-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.my_terraform_network.name
address_prefixes = ["10.0.1.0/24"]
}
Again here we are referencing the virtual_network_name from the previous block
Public IP Address
The following block will create the public IP address
# Create public IPs
resource "azurerm_public_ip" "my_terraform_public_ip" {
name = "test-public-ip"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
}
Network Security Group
To ensure only you can connect to the VM we need to create a Network Security Group.
# Create Network Security Group and rules
resource "azurerm_network_security_group" "my_terraform_nsg" {
name = "test-nsg"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
security_rule {
name = "RDP"
priority = 1000
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefix = "<enter your public IP here>"
destination_address_prefix = "*"
}
}
Replace <enter your public IP here>
with your IP address (google ‘what is my ip address’ to find out)
VM Network Interface
The following will create the network interface for the VM
# Create network interface
resource "azurerm_network_interface" "my_terraform_nic" {
name = "test-nic"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "my_nic_configuration"
subnet_id = azurerm_subnet.my_terraform_subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.my_terraform_public_ip.id
}
}
Attach the Network Security Group to the Network Interface
The following will attach the network security group to the network interface to enforce the RDP rule we setup above.
# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
network_interface_id = azurerm_network_interface.my_terraform_nic.id
network_security_group_id = azurerm_network_security_group.my_terraform_nsg.id
}
Create Virtual Machine
Lastly this will create the Windows Server Virtual Machine.
# Create virtual machine
resource "azurerm_windows_virtual_machine" "main" {
name = "test-vm"
admin_username = "azureuser"
admin_password = random_password.password.result
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
network_interface_ids = [azurerm_network_interface.my_terraform_nic.id]
size = "Standard_B2s"
os_disk {
name = "myOsDisk"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2022-datacenter-azure-edition"
version = "latest"
}
}
resource "random_password" "password" {
length = 20
min_lower = 1
min_upper = 1
min_numeric = 1
min_special = 1
special = true
}
Note here we’ve used a random password generator provider for the admin password. The password will be generated at build time. You should also notice we’ve coded the username to be ‘azureuser’ and set the size of the VM to ‘Standard B2s’. All of these things can be changed and in another article or if you check the terraform or Microsoft docs you can turn each of the parameters into variables easier use.
Output Values
Since the Public IP address changes each time and the password is generated at build time we can use output values to output once the build is complete. To do this create a new file called outputs.tf and enter the following.
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}
output "public_ip_address" {
value = azurerm_windows_virtual_machine.main.public_ip_address
}
output "admin_password" {
sensitive = true
value = azurerm_windows_virtual_machine.main.admin_password
}
Using output blocks, the above values will be output to the command line at the end of the build. Notice the admin password has the value ‘sensitive’ true. This will mean that it is not output and you need to run a command to get the value. Run the following to output the password value.
echo $(terraform output -raw admin_password)
Run terraform apply to build everything and see the output.
You should now be able to RDP to the VM using the IP address output and the username ‘azureuser’ and password as output with above command.
Checking in the Azure portal you should see everything built as below.
Lastly run terraform destroy to cleanup and delete everything we’ve created above.