As we approach the end of in our journey, we will unveil the steps to deploying an Azure Virtual Machine using Terraform. We will build on the infrastructure deployed in our previous outings,
and deploy a Windows Server VM using infrastructure as code.
As part of the deployment we will deploy a Public IP resource and a Network Interface resource.
Public IP
In Azure a public IP is a configurable resource that can be created independently of the network interface. Once created the public IP address can be associated with different Azure resources,
- VM Network Interface
- VM Scale Sets
- Public Load Balancers
- Virtual Network Gateways
- Azure Firewall
This is not a complete list, bit it gives you an idea of the type of resources a Public IP address can be associated with. Keep in mind the Public IP addresses resource can only be associated with one resource at a time.
The following code block details the code we will use to deploy a public IP.
resource "azurerm_public_ip" "ftpip" {
name = "ftpip01"
location = var.location
resource_group_name = azurerm_resource_group.ftrg001.name
allocation_method = "Dynamic"
tags = {
environment = "dev"
}
}
Detailed configuration options when creating a Azure Public IP address can be found in the Terraform documentation here.
In our code base we are using the variable var.location to define what location the resource will be created in.
allocation_method is set to dynamic not static in our example.
Network Interface
The code block below details the code we will use to define a network interface card that we can attach to our VM. The network interface resource will use the public IP address resource we defined by referencing the name.id of the public IP resource,
- azurerm_public_ip.ftpip.id
resource "azurerm_network_interface" "tfnetint01" {
name = "tfnetint01"
location = "uksouth"
resource_group_name = azurerm_resource_group.ftrg001.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.ftsubnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.ftpip.id
}
tags = {
environment = "dev"
}
}
The Terraform documentation for creating an Azure Network Interface is located here.
Windows Server VM Code
We will be using the code defined in the code block below for deploying our Windows 2019 Server,
resource "azurerm_windows_virtual_machine" "tfvm01" {
name = "tfvm01"
resource_group_name = azurerm_resource_group.ftrg001.name
location = "uksouth"
size = "Standard_B1s"
admin_username = "azuradmin"
admin_password = var.admin_password
network_interface_ids = [azurerm_network_interface.tfnetint01.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-Datacenter"
version = "latest"
}
tags = {
environment = "dev"
}
}
It is not best practice to define password or secrets in your code. As we our learning I have added the following variable admin_password to the variables file and referenced it in the code.
For the variable no default has not been defined. This will require you to enter a password into the terminal when running terraform plan or apply.
We will cover how to handle secrets in future posts.
variable "admin_password" {
type = string
description = "value for admin_password"
}
In Visual Studio Code copy all blocks to your main.tf file,
From the terminal run,
- terraform init
- terraform validate
Your main.tf file should now read as follows,
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "3.82.0"
}
}
}
provider "azurerm" {
features {}
}
locals {
tags = {
environment = "dev"
}
}
resource "azurerm_resource_group" "ftrg001" {
name = "FT23-RG-001"
location = var.location
tags = {
environment = "dev"
}
}
resource "azurerm_virtual_network" "ftvnet" {
name = "ftvnet01"
address_space = ["10.10.0.0/16"]
location = var.location
resource_group_name = azurerm_resource_group.ftrg001.name
tags = {
environment = "dev"
}
}
resource "azurerm_subnet" "ftsubnet" {
name = "ftsubnet01"
resource_group_name = azurerm_resource_group.ftrg001.name
virtual_network_name = azurerm_virtual_network.ftvnet.name
address_prefixes = ["10.10.0.0/24"]
}
resource "azurerm_network_security_group" "ftnsg01" {
name = "ft-test-nsg01"
location = var.location
resource_group_name = azurerm_resource_group.ftrg001.name
security_rule = [
{
name = "SSH"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
description = "Allow SSH"
destination_address_prefixes = null
source_application_security_group_ids = null
source_port_ranges = null
destination_application_security_group_ids = null
destination_port_ranges = null
source_address_prefixes = null
},
{
name = "RDP"
priority = 1002
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefix = "*"
destination_address_prefix = "*"
description = "Allow RDP"
destination_address_prefixes = null
source_application_security_group_ids = null
source_port_ranges = null
destination_application_security_group_ids = null
destination_port_ranges = null
source_address_prefixes = null
}
]
tags = {
environment = "dev"
}
}
resource "azurerm_subnet_network_security_group_association" "tfnsg-tfsubnet01" {
subnet_id = azurerm_subnet.ftsubnet.id
network_security_group_id = azurerm_network_security_group.ftnsg01.id
}
resource "azurerm_public_ip" "ftpip" {
name = "ftpip01"
location = var.location
resource_group_name = azurerm_resource_group.ftrg001.name
allocation_method = "Dynamic"
tags = {
environment = "dev"
}
}
resource "azurerm_network_interface" "tfnetint01" {
name = "tfnetint01"
location = "uksouth"
resource_group_name = azurerm_resource_group.ftrg001.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.ftsubnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.ftpip.id
}
tags = {
environment = "dev"
}
}
resource "azurerm_windows_virtual_machine" "tfvm01" {
name = "tfvm01"
resource_group_name = azurerm_resource_group.ftrg001.name
location = "uksouth"
size = "Standard_B1s"
admin_username = "azuradmin"
admin_password = var.admin_password
network_interface_ids = [azurerm_network_interface.tfnetint01.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsServer"
offer = "WindowsServer"
sku = "2019-Datacenter"
version = "latest"
}
tags = {
environment = "dev"
}
}
Terraform validate should return the configuration is valid,
- terraform plan
If you have been following along in this series you should get the return message to add 3 resources
- Public IP
- Network Interface
- Windows Virtual Machine
If there are no errors run terraform apply
Once successfully completed the terminal should return the message
Run the following command to confirm the VM has been successfully created
- az vm list –output table
To confirm your VM has been allocated both a public and private IP address run the following command
- az vm list-ip-addresses –output table
In part 5 of our Azure Terraform journey we created an NSG with an inbound allow rule for RDP 3389. We can test this by trying to connect to our VM using its public IP address,
Enter the username and password you defined in the code block when creating the VM
You should successfully login to the VM
In Conclusion
As the curtain falls on our magical journey, remember Terraform allows you to craft your environment with precision. The script that we have created is a basic introduction to Terraform, covering some of Terraforms key concepts to help you get started on your journey.
We also deployed the basic infrastructure required before you can deploy and IaaS VM in Azure,
- Resource group
- Subnet
- VNET
And for fun we deployed an NSG with a few rules defined.
Remember, running services in Azure incur a cost. Now that we have finished with our test environment it is time to clean up. You can destroy everything you have created by running the terraform destroy command,
Terraform destroy will create a plan detailing all resources to be destroyed review before confirming.
It will as you to confirm before destroying any resource.
For further reading on the destroy command refer to the Terraform documentaton here.
Hope you have enjoyed this this introduction and it has inspired you to continue your Terraform journey. Thank you for reading.