Deploying Azure Virtual Machines using Terraform

In my last post we talked about using Terraform to create Networking in Azure. As part of that post we created an Azure VNets, Subnets and NSGs. If you missed that, you can review it here.

In this article we are going to build out on top of the Azure networking from the previous post and add Windows and Linux virtual machines. As with the previous article, we will be using Terraform to deploy these resources.

Hopefully you’ll have a code editor and Terraform installed on your system. If not, to get them installed, whiz on over to this article here.

What are we building today?

In this article we are going to create the following resources in Azure using Terraform:

  • 1 x Azure Keyvault and 1 x Secret
  • 2 x Windows Virtual Machine
  • 2 x Linux Virtual Machine
  • 2 x Resource Groups
  • 2 x Virtual Networks
  • 4 x Subnets
  • 1 x Virtual Network Peer
  • 2 x Network Security Groups (NSG)
  • Resource Tags

The image below shows a high-level overview of what we are going to deploy using Terraform.

What we are going to build.

We will be expanding on the code used in my previous post to built this lab, we will simply be adding an additional files named keyvault.tf, linux_vms.tf and msft_vms.tf. The previous code can be viewed here.

The Terraform code used for this lab can be found in my GitHub here. The files in the repo are used to do the following:

  • main.tf – Used to specify providers and create the resource groups
  • variables.tf – Used to define variables for this deployment. They are defaults and referenced using the terraform.tfvars file.
  • terraform.tfvars – This is file the only file you’ll need to edit amend parameters for your deployment.
  • output.tf – This file will be used will be used to capture the randomly generated name of the Resource Group for future deployments that need to reference the name of this resource group.
  • vnets.tf – This file builds the virtual networks and subnets
  • peerings.tf – This file creates the peering between the two virtual networks
  • nsg.tf – This file creates the network security group and assigns it to the subnets
  • keyvault.tf – This file creates an Azure KeyVault and Secret that are used as the Username and Password for the Virtual Machines.
  • linux_vms.tf – This file creates the Linux Azure Virtual Machines
  • msft_vms.tf – This file creates the Microsoft Windows Azure Virtual Machines.
Let’s create the Azure Keyvault and Secret

1. In my last post we covered creating an Azure Keyvault using Terraform. We will use the same code from that post here to deploy our Azure Virtual Machines. We are going to deploy an Azure Keyvault and a Secret, the keyvault.tf file is used to complete this task. The secret and its value will be used as the username and password for the virtual machines.

# Create Keyvault ID
resource "random_id" "keyvault_name" {
  byte_length = "3"
  prefix      = "keyvault-${var.prefix}-"
}

# Create Keyvault
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "keyvault" {
  depends_on                  = [azurerm_resource_group.rg_1]
  name                        = random_id.keyvault_name.hex
  location                    = azurerm_resource_group.rg_1.location
  resource_group_name         = azurerm_resource_group.rg_1.name
  enabled_for_disk_encryption = true
  tenant_id                   = data.azurerm_client_config.current.tenant_id
  soft_delete_retention_days  = 7
  purge_protection_enabled    = false

  sku_name = "standard"

  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = data.azurerm_client_config.current.object_id

    key_permissions = [
      "get",
    ]

    secret_permissions = [
      "get", "backup", "delete", "list", "purge", "recover", "restore", "set",
    ]

    storage_permissions = [
      "get",
    ]
  }
  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }

}

# Create Keyvault Admin Password
resource "random_password" "admin_random_password" {
  length  = 20
  special = true
}

# Create Keyvault Secret
resource "azurerm_key_vault_secret" "admin_secret" {
  name         = var.labadmin
  value        = random_password.admin_random_password.result
  key_vault_id = azurerm_key_vault.keyvault.id
  depends_on   = [azurerm_key_vault.keyvault]
}

2. Next, we are going to deploy 2 x Microsoft Windows Server Virtual Machines. This is using the msft_vms.tf file. The secret created in the previous keyvaut.tf file will be used for the random generation of a password for the admin account.

# Create random string for VM 1
resource "random_string" "vm_rs_1" {
  length  = 5
  special = false
}

# Create Public IP for VM1
resource "azurerm_public_ip" "pip_vm1" {
  name                = "pip-${var.vm1_name}-${random_string.vm_rs_1.result}"
  resource_group_name = azurerm_resource_group.rg_1.name
  location            = azurerm_resource_group.rg_1.location
  allocation_method   = var.pip_allocation
  sku                 = var.pip_sku

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

# Create NIC for VM1
resource "azurerm_network_interface" "nic_vm1" {
  name                = "nic-${var.vm1_name}-${random_string.vm_rs_1.result}"
  location            = azurerm_resource_group.rg_1.location
  resource_group_name = azurerm_resource_group.rg_1.name


  ip_configuration {
    name                          = "ipconfig-${var.vm1_name}-${random_string.vm_rs_1.result}"
    subnet_id                     = azurerm_subnet.vnet_1_snet_1.id
    private_ip_address_allocation = var.nic_ip_allocation
    public_ip_address_id          = azurerm_public_ip.pip_vm1.id
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }

}

# Create VM 1
resource "azurerm_windows_virtual_machine" "vm1" {
  name                = "vm-${var.vm1_name}-${random_string.vm_rs_1.result}"
  depends_on          = [azurerm_key_vault.keyvault]
  resource_group_name = azurerm_resource_group.rg_1.name
  location            = azurerm_resource_group.rg_1.location
  size                = var.vm1_size
  admin_username      = azurerm_key_vault_secret.admin_secret.name
  admin_password      = azurerm_key_vault_secret.admin_secret.value
  network_interface_ids = [
    azurerm_network_interface.nic_vm1.id,
  ]

  os_disk {
    caching              = var.disk_caching
    storage_account_type = var.storage_account_type
  }

  source_image_reference {
    publisher = var.vm1_publisher
    offer     = var.vm1_offer
    sku       = var.vm1_sku
    version   = var.vm1_version
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

# Create random string for VM 2
resource "random_string" "vm_rs_2" {
  length  = 5
  special = false
}

# Create Public IP for VM2
resource "azurerm_public_ip" "pip_vm2" {
  name                = "pip-${var.vm2_name}-${random_string.vm_rs_2.result}"
  resource_group_name = azurerm_resource_group.rg_2.name
  location            = azurerm_resource_group.rg_2.location
  allocation_method   = var.pip_allocation
  sku                 = var.pip_sku

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

# Create NIC for VM2
resource "azurerm_network_interface" "nic_vm2" {
  name                = "nic-${var.vm2_name}-${random_string.vm_rs_2.result}"
  location            = azurerm_resource_group.rg_2.location
  resource_group_name = azurerm_resource_group.rg_2.name


  ip_configuration {
    name                          = "ipconfig-${var.vm2_name}-${random_string.vm_rs_2.result}"
    subnet_id                     = azurerm_subnet.vnet_2_snet_1.id
    private_ip_address_allocation = var.nic_ip_allocation
    #private_ip_address            = var.vm2_ip_address
    public_ip_address_id = azurerm_public_ip.pip_vm2.id
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }

}

# Create VM 2
resource "azurerm_windows_virtual_machine" "vm2" {
  name                = "vm-${var.vm2_name}-${random_string.vm_rs_2.result}"
  depends_on          = [azurerm_key_vault.keyvault]
  resource_group_name = azurerm_resource_group.rg_2.name
  location            = azurerm_resource_group.rg_2.location
  size                = var.vm2_size
  admin_username      = azurerm_key_vault_secret.admin_secret.name
  admin_password      = azurerm_key_vault_secret.admin_secret.value
  network_interface_ids = [
    azurerm_network_interface.nic_vm2.id,
  ]

  os_disk {
    caching              = var.disk_caching
    storage_account_type = var.storage_account_type
  }

  source_image_reference {
    publisher = var.vm1_publisher
    offer     = var.vm1_offer
    sku       = var.vm1_sku
    version   = var.vm1_version
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

3. Finally, we are going to deploy 2 x Linux Virtual Machines, for this lab I use Ubuntu but you can choose the distribution you prefer by changing the .tfvars file. This will also use the secret generated in the keyvault.tf file for the admin password.

# Create random string for VM 3
resource "random_string" "vm_rs_3" {
  length  = 5
  special = false
}

# Create Public IP for VM 3
resource "azurerm_public_ip" "pip_vm3" {
  name                = "pip-${var.vm3_name}-${random_string.vm_rs_3.result}"
  resource_group_name = azurerm_resource_group.rg_1.name
  location            = azurerm_resource_group.rg_1.location
  allocation_method   = var.pip_allocation
  sku                 = var.pip_sku

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

# Create NIC for Linux VM 3
resource "azurerm_network_interface" "nic_vm3" {
  name                = "nic-${var.vm3_name}-${random_string.vm_rs_3.result}"
  location            = azurerm_resource_group.rg_1.location
  resource_group_name = azurerm_resource_group.rg_1.name

  ip_configuration {
    name                          = "ipconfig-${var.vm3_name}-${random_string.vm_rs_3.result}"
    subnet_id                     = azurerm_subnet.vnet_1_snet_1.id
    private_ip_address_allocation = var.nic_ip_allocation
    public_ip_address_id          = azurerm_public_ip.pip_vm3.id
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }

}

# Create VM 3
resource "azurerm_linux_virtual_machine" "vm3" {
  name                            = "vm-${var.vm3_name}-${random_string.vm_rs_3.result}"
  depends_on                      = [azurerm_key_vault.keyvault]
  resource_group_name             = azurerm_resource_group.rg_1.name
  location                        = azurerm_resource_group.rg_1.location
  size                            = var.vm3_size
  admin_username                  = azurerm_key_vault_secret.admin_secret.name
  admin_password                  = azurerm_key_vault_secret.admin_secret.value
  disable_password_authentication = false
  network_interface_ids = [
    azurerm_network_interface.nic_vm3.id,
  ]

  os_disk {
    caching              = var.disk_caching
    storage_account_type = var.storage_account_type
  }

  source_image_reference {
    publisher = var.vm3_publisher
    offer     = var.vm3_offer
    sku       = var.vm3_sku
    version   = var.vm3_version
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

# Create random string for VM 4
resource "random_string" "vm_rs_4" {
  length  = 5
  special = false
}

# Create Public IP for VM 4
resource "azurerm_public_ip" "pip_vm4" {
  name                = "pip-${var.vm4_name}-${random_string.vm_rs_4.result}"
  resource_group_name = azurerm_resource_group.rg_2.name
  location            = azurerm_resource_group.rg_2.location
  allocation_method   = var.pip_allocation
  sku                 = var.pip_sku

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}

# Create NIC for Linux VM 4
resource "azurerm_network_interface" "nic_vm4" {
  name                = "nic-${var.vm4_name}-${random_string.vm_rs_4.result}"
  location            = azurerm_resource_group.rg_2.location
  resource_group_name = azurerm_resource_group.rg_2.name

  ip_configuration {
    name                          = "ipconfig-${var.vm4_name}-${random_string.vm_rs_4.result}"
    subnet_id                     = azurerm_subnet.vnet_2_snet_1.id
    private_ip_address_allocation = var.nic_ip_allocation
    public_ip_address_id          = azurerm_public_ip.pip_vm4.id
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }

}

# Create VM 4
resource "azurerm_linux_virtual_machine" "vm4" {
  name                            = "vm-${var.vm4_name}-${random_string.vm_rs_4.result}"
  depends_on                      = [azurerm_key_vault.keyvault]
  resource_group_name             = azurerm_resource_group.rg_2.name
  location                        = azurerm_resource_group.rg_2.location
  size                            = var.vm4_size
  admin_username                  = azurerm_key_vault_secret.admin_secret.name
  admin_password                  = azurerm_key_vault_secret.admin_secret.value
  disable_password_authentication = false
  network_interface_ids = [
    azurerm_network_interface.nic_vm4.id,
  ]

  os_disk {
    caching              = var.disk_caching
    storage_account_type = var.storage_account_type
  }

  source_image_reference {
    publisher = var.vm4_publisher
    offer     = var.vm4_offer
    sku       = var.vm4_sku
    version   = var.vm4_version
  }

  tags = {
    Environment = var.tag_environment
    CreatedBy   = var.tag_createdby
    CreatedWith = var.tag_createdwith
    Project     = var.tag_project
  }
}
Deploying the code

Once you have your files in your Terraform directory you can go ahead and deploy the code. To do this, open your editor of choice and browse to your Terraform directory.

1. In your Terraform directory, run the following command to initialise the Terraform deployment and download the required modules.

terraform init

2. Next we need to create a Terraform plan. This is used to determine what is required to create the configuration you have specified in your Terraform directory. To do this, run the command below.

terraform plan -out main.tfplan

3. Now that you have generated your Terraform deployment plan, we can push the Terraform code into Azure and create our resources. Run the command below to apply your code.

terraform apply main.tfplan

4. That’s it, you have now deployed your terraform code. If you have a look in the Azure portal you will see the resource groups you created and within it them a whole load of resources.

5. Now that you have successfully deployed your resources into Azure, once you have finished with them, it’s time to clean it up and remove your deployment. This is quite straightforward, simply run the command below. This will use the .tfstate file and destroy all resource that terraform built using the apply command previously.

terraform destroy
Summary

I hope that this very short blog post about creating Azure Virtual Machines and the infrastructure to support them using Terraform has been helpful? I think that the more you use this toolset to deploy infrastructure into Azure, the more you will appreciate its power and simplicity. In my next blog, we’ll build upon this one and add more services into the resource group. Next time, its building Azure NetApp Files 😊

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.