3 Steps to Deploy Terraform Azure VM Custom Data example [Infrastructure as code]

This post will show you, how to create an Azure VM using Terraform and pass custom data to the Virtual machine. I will deploy a virtual machine and will install a simple web server after the VM creation. I will not cover much theory, it will be more of a hands-on article, So just be ready with your terraform setup and start typing infrastructure as code (IaC) for your Virtual machine.

Pre-requisite to deploy Azure VM with custom data

Below mentioned are the requirements to get Azure VM deployed in Terraform with custom data -

  1. You must have at least basic knowledge of Azure and Terraform
  2. You must have a set up that is ready to execute terraform commands i.e. editor like visual studio code or Vim, Azure CLI, Terraform plugins. In case you don't have the terraform and Visual code installed in your system. Then follow these articles on the installation of Terraform in Linux and Windows.
  3. Create a Microsoft Azure account, if doesn't exist already.
  4. Internet connectivity obviously 😉


What Azure resources will be created during this exercise?

We will create or use the following resources during this exercise -

  • Azure Resource Group
  • Azure Virtual Network
  • Azure Subnet
  • Azure Public IP
  • Azure Network Interface
  • Azure Linux Virtual Machine
  • random_string Resource
  • Azure custom_data argument
  • Terraform file Function
  • Terraform filebase64 Function

Steps to create Terraform Azure VM with Custom Data example

In this article, we will create a Linux virtual machine with the installation of the web server as custom data using Terraform. I would try to make you understand every step in detail so that you can create any VM with custom data in future.
Following are the steps -

  1. Generate SSH Keys for Linux Virtual machine
  2. Write infrastructure as code for Linux VM
  3. Run terraform commands to deploy all the resources
  4. Clean up resources, if required

Step1 - Create SSH Keys for your Linux Azure virtual machine

We will generate ssh keypair to access our new Linux VM, once it will get provisioned. This is a recommended practise and must be followed compared to using Username and password to login to your Virtual machines.

I am using Windows machine to run all these steps, you can just replace the Windows command with Linux command on terminal to get the same output.

To generate SSH keys on Windows 10 -

  • Open the "Windows PowerShell" Prompt
  • Run ssh-keygen command
C:\> ssh-keygen -m PEM -t rsa -b 4096 -C "azureuser@myserver" -f terraform-azure.pem

In this command -

Username is azureuser and file name is "terraform-azure.pem". You can give passphrase as blank or can enter a value. I have kept it blank for demo. You will find this key in the directory you were working on.

Important Note: If you give passphrase during ssh key generation, then everytime you login to VM, you also need to provide passphrase in the ssh command.
SSH Key generated for Azure Linux VM
  • List the directory and files to validate our ssh Keys got generated.
C:\> ls 

You will find terraform-azure.pem and terraform-azure.pem.pub as shown in the file.

# Files Generated details -

Public Key: terraform-azure.pem.pub
Private Key: terraform-azure.pem
public and private files listed for SSH keys
  • Just rename "terraform-azure.pem.pub" file to "terraform-azure.pub"
C:\> mv .\terraform-azure.pem.pub .\terraform-azure.pub
  • Create a directory "ssh-keys" , where you have your Terraform working directory. In my case, i will create it in the "terraform manifes" directory. I am using it for my terraform codes and configurations. Ignore the spelling of manifest, it has been kept knowingly 🙂
  • Change terraform-azure.pem key to Read and execute only. This is equivalent to setting chmod 400 in Linux.
  • Right-click on "terraform-azure.pem" key file > Properties > Security > Remove all other users except administrator or your own user, in case you are not logged in with administrator and give only "Read and Execute" permission.
set Permission as 400 for ssh pub file in Windows 10

Step2 - Write terraform code to deploy required resources for Linux VM in Azure

Follow these steps to write terraform code and to deploy Linux virtual machine.

Important note - I am creating all resource from scratch. You must replace the variables, argument values and follow naming convention according to your environment, if you already have all resources deployed and just need to create VM.

Step2.1 Create c1-versions.tf file in the visual studio code editor

In this file we will keep our Terraform block, provider block and Random string resource code.

# Terraform Block
terraform {
  required_version = ">= 1.0.0"
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.0" 
    }
    random = {
      source = "hashicorp/random"
      version = ">= 3.0"
    }
  }
}

# Provider Block
provider "azurerm" {
 features {}          
}

# Random String Resource
resource "random_string" "myrandom" {
  length = 6
  upper = false 
  special = false
  numeric = false   
}

Step2.2 Create c2-Resource-group.tf file

# Resource-1: Azure Resource Group
resource "azurerm_resource_group" "myrg" {
  name = "myrg-1"
  location = "East US"
}

Step2.3 Create virtual network resource file - C3-Virtual-network.tf

In this file, we will write terraform code to deploy Virtual network, Azure subnet, associate Azure public IP, and virtual network interface.

# Create Virtual Network
resource "azurerm_virtual_network" "myvnet" {
  name                = "myvnet-1"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.myrg.location
  resource_group_name = azurerm_resource_group.myrg.name
}

# Create Subnet
resource "azurerm_subnet" "mysubnet" {
  name                 = "mysubnet-1"
  resource_group_name  = azurerm_resource_group.myrg.name
  virtual_network_name = azurerm_virtual_network.myvnet.name
  address_prefixes     = ["10.0.2.0/24"]
}

# Create Public IP Address
resource "azurerm_public_ip" "mypublicip" {
  name                = "mypublicip-1"
  resource_group_name = azurerm_resource_group.myrg.name
  location            = azurerm_resource_group.myrg.location
  allocation_method   = "Static"
  domain_name_label = "app1-vm-${random_string.myrandom.id}"
  tags = {
    environment = "Dev"
  }
}

# Create Network Interface
resource "azurerm_network_interface" "myvmnic" {
  name                = "vmnic"
  location            = azurerm_resource_group.myrg.location
  resource_group_name = azurerm_resource_group.myrg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.mysubnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id = azurerm_public_ip.mypublicip.id 
  }
}

Step2.4 Create Linux Virtual machine in C4-Linux-virtual-machine.tf file

Here i am selecting "Standard_DS1_V2" virtual machine with RedHat 8.3. You can change these values as per your requirements.

# Resource: Azure Linux Virtual Machine
resource "azurerm_linux_virtual_machine" "mylinuxvm" {
  name                = "mylinuxvm-1"
  computer_name       = "devlinux-vm1" # Hostname of the VM
  resource_group_name = azurerm_resource_group.myrg.name
  location            = azurerm_resource_group.myrg.location
  size                = "Standard_DS1_v2"
  admin_username      = "azureuser"
  network_interface_ids = [
    azurerm_network_interface.myvmnic.id
  ]
  admin_ssh_key {
    username   = "azureuser"
    public_key = file("${path.module}/ssh-keys/terraform-azure.pub")
  }
  os_disk {
    name = "osdisk"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }
  source_image_reference {
    publisher = "RedHat"
    offer     = "RHEL"
    sku       = "83-gen2"
    version   = "latest"
  }
  custom_data = filebase64("${path.module}/app-scripts/app1-cloud-init.txt") # Custom_data code file to deploy web server
}
Terraform code to deploy Azure VM with custom data

Step2.5 Prepare custom data file as app1-cloud-init.txt

Write following lines of code in a text file to deploy the web server in your Linux virtual machine. We are calling this file in last step using filebase64 function in our terraform code shown in Green text. Make sure you gave the path right in custom_data field for this file as shown in the red text.

#cloud-config
package_upgrade: false
packages:
  - httpd
write_files:
  - owner: root:root 
    path: /var/www/html/index.html
    content: |
      <h1>Welcome to cloudlinuxtech.com - APP-1</h1>
  - owner: root:root 
    path: /var/www/html/app1/index.html
    content: |
      <!DOCTYPE html> <html> <body style="background-color:rgb(250, 210, 210);"> <h1>Welcome to cloudlinuxtech.com - APP-1</h1> <p>Terraform Demo</p> <p>Application Version: V1</p> </body></html>      
runcmd:
  - sudo systemctl start httpd  
  - sudo systemctl enable httpd
  - sudo systemctl stop firewalld
  - sudo mkdir /var/www/html/app1 
  - [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]

Step2.6 Run Terraform commands to deploy these resources

Run the following terraform commands one by one from the Azure CLI prompt.

# Initialize Terraform
terraform init

# Terraform Validate
terraform validate

# Terraform Plan 
terraform plan

# Terraform Apply 
terraform apply 
Terraform apply command output

Step3 Verify the resources manually from the Azure portal

  • Login to Azure portal and quickly check the new resources you have just created.
azure VM custom data
  • Try login into your Linux virtual machine with your public IP as shown in the image.
# Connect to VM and Verify 
ssh -i ssh-keys/terraform-azure.pem azureuser@<PUBLIC-IP>
Login to azure VM with custom data as web server
  • Try accessing the following web pages to check web server is working fine.
http://<PUBLIC_IP>
http://<PUBLIC_IP>/app1
http://<PUBLIC_IP>/app1/metadata.html

web server showing test page successfully

Step4 - Clean up the resources, in case you are testing to avoid charges

If you are running these commands in the test lab, then make sure you clean all deployed resources after testing is finished. Else Azure will keep charging you for the resources deployed.

#Terraform destroy to clean up resources
terraform destroy

End note

I hope you are able to create the Azure VM with custom data usisng Terraform. Please make a note, you need to keep the terraform version and terraform provider version same. In case you have different version, you just need to tweak the code according to your Terraform version. Else you will get those errors or warnings during Terraform validate or plan command.

If you want to know, how to pass custom data to Azure VM using ARM, follow this link. You can also check Azure registry documentation, in case further information required regarding terraform code.

Leave a Comment

Your email address will not be published.