Skip to content
Rachit S edited this page Jun 28, 2024 · 8 revisions

Automated Azure Virtual Machine Deployment using Terraform


Project Overview

The Terraform Azure VM Automation project demonstrates how to automate the deployment of a virtual machine (VM) in Azure using Terraform. This project aims to showcase Infrastructure as Code (IaC) principles and how to manage cloud resources efficiently. The project includes automated deployment scripts, Terraform configuration files, and examples of outputs for easy management and scalability of Azure resources.


Prerequisites

Before you begin, ensure you have the following installed:

  1. Terraform: Install Terraform
  2. Azure CLI: Install Azure CLI
  3. Git: Install Git
  4. PowerShell: Available by default on Windows; for other operating systems, download from Microsoft.

Project Structure

terraform-azure-vm-automation/
│
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
├── git-init-push.ps1
├── tf-init-apply.ps1
├── tf-destroy.ps1
├── LICENSE
├── .gitignore
├── photos
│   ├── social_preview.jpg
│   ├── graph.png
│   ├── NetworkWatcherRG.png
└── README.md

Terraform Configuration

main.tf

This file contains the core configuration for provisioning resources in Azure. It includes the setup for the resource group, virtual network, subnet, network interface, and virtual machine.

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.105.0" # The operator >= programmatically states “greater than or equal to” in code.
    }
  }
}

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false # This if required
    }
  }
}
resource "azurerm_resource_group" "autoazvm-dev-rg" {
  name     = var.resource_group_name
  location = var.location
  tags     = var.tags
}

# Below starts critical resources for this Project

resource "azurerm_virtual_network" "autoazvm-dev-vnet" {
  name                = var.azurerm_virtual_network
  resource_group_name = var.resource_group_name # azure_resource_group.autoazvm-test-rg.name is an alternate. Ref: https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine
  location            = var.location            # azurerm_resource_group.autoazvm-test-rg.location is an alternate. Ref: ""
  address_space       = ["10.0.0.0/16"]

  depends_on = [azurerm_resource_group.autoazvm-dev-rg]
}

# Location selection via portal is mandatory, not in here!
resource "azurerm_subnet" "autoazvm-dev-subnet" {
  name                 = var.azurerm_subnet
  resource_group_name  = var.resource_group_name
  virtual_network_name = var.azurerm_virtual_network
  address_prefixes     = ["10.0.1.0/24"]
}

resource "azurerm_network_interface" "autoazvm-dev-ni" {
  name                = var.network_interface_name
  resource_group_name = var.resource_group_name
  location            = var.location

  ip_configuration {                                     # Mandatory
    name                          = var.ip_configuration # verify name after deployment
    subnet_id                     = azurerm_subnet.autoazvm-dev-subnet.id
    private_ip_address_allocation = "Dynamic"
  }
}

resource "azurerm_virtual_machine" "autoazvm-dev-vm" {
  name                  = var.azurerm_virtual_machine
  location              = var.location
  resource_group_name   = var.resource_group_name
  network_interface_ids = [azurerm_network_interface.autoazvm-dev-ni.id] # alternate to add all the NIC --> azurevm_network_interface.autoazvm-dev-ni.*.id
  vm_size               = var.vm_size

  storage_os_disk {
    name              = var.storage_os_disk
    create_option     = "FromImage"
    managed_disk_type = "Standard_LRS"
  }

  # Handy command: az vm image list --output table
  # Handy ref: https://learn.microsoft.com/en-us/azure/virtual-machines/linux/cli-ps-findimage#code-try-3

  storage_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
  os_profile {
    computer_name  = var.computer_name
    admin_username = var.admin_username
    admin_password = var.admin_password
  }

  os_profile_linux_config {
    disable_password_authentication = false # being a test and short-lived environment with password verified, this is set to false.
  }

  tags = var.tags
}

variables.tf

This file defines the variables used in the Terraform configuration and uses terraform.tfvars to specify values, making it easy to customize the deployment environment by changing variable values.

variable "resource_group_name" {
  description = "The name of the resource group"
}

# Location variable is referenced in multiple resources. ADD New if I want resources to be provisioned in different regions.
# az account list-locations -o table to Get Azure Regions
variable "location" {
  description = "The Azure region in which resources will be provisioned"
}

variable "azurerm_virtual_network" {
  description = "The name of the Virtual Network"
}

variable "azurerm_subnet" {
  description = "The name of the Subnet"
}

variable "network_interface_name" {
  description = "The name of the network interface"
}

variable "ip_configuration" {
  description = "The name of the IP Configuration resource"
}

variable "azurerm_virtual_machine" {
  description = "The name of the Virtual Machine"
}

variable "vm_size" {
  description = "The size of the Virtual Machine"
}

variable "storage_os_disk" {
  description = "The name of the Storage OS disk"
}

variable "computer_name" {
  description = "The HOSTNAME"
}

variable "admin_username" {
  description = "The admin username"
}

variable "admin_password" {
  description = "The admin password"
}
variable "tags" {
  type = map(string)
}

outputs.tf

This file defines the outputs of the Terraform configuration, providing useful information about the deployed resources.

# I personally prefer using below syntax:
output "resource_group_name" {
  value = azurerm_resource_group.autoazvm-test-rg.name
}

output "virtual_network_name" {
  value = azurerm_virtual_network.autoazvm-test-rg-vnet.name
}

output "subnet_id" {
  value = azurerm_subnet.autoazvm-test-rg-subnet.id
}

output "network_interface_ids" {
  value = azurerm_network_interface.autoazvm-test-rg-ni.id
}

output "virtual_machine_name" {
  value = azurerm_virtual_machine.autoazvm-test-rg-vm.name
}

PowerShell Scripts

git-init-push.ps1

This script initializes a Git repository, adds a remote origin, commits changes, and pushes to the remote repository. The script works uninterrupted if the GitHub repository is already created and the script is updated accordingly.

# Check if .git directory exists
$gitInitialized = Test-Path -Path ".git" -PathType Container

if (-not $gitInitialized) {
    # Git is not initialized, perform git init
    git init
}

# Check if remote 'origin' already exists
$remoteExists = git remote get-url origin 2>$null

if (-not $remoteExists) {
    # Remote 'origin' does not exist, add it
    git remote add origin https://github.com/yourusername/terraform-azure-vm-automation
}

# Add files and commit changes
git add .
$commitMessage = Read-Host -Prompt 'Enter commit message'
git commit -m "$commitMessage"

# Push changes to remote 'origin'
git push

tf-init-apply.ps1

This script initializes Terraform, validates the configuration, plans the deployment, and applies the changes. In addition, it takes a backup of the .tfstate and tfplan with an opportunity to creat the Graph.

terraform fmt
az login

<# Set the desired subscription
az account set --subscription "YOUR_SUBSCRIPTION_ID_OR_NAME" #>

terraform init
terraform validate

terraform plan -out='tfplan.out'
terraform apply -auto-approve tfplan.out

terraform state list
terraform show
terraform graph > graph.dot

tf-destroy.ps1

This script destroys the Terraform-managed infrastructure. Except NetworkWatcherRG, please refer my Key Learnings here

terraform plan -destroy -out="planout"
terraform apply "planout"

Graph Output

Graph Output


Key Learnings

  • network_interface_name: I encountered issues when trying to reference a map {} directly. I resolved this by creating a separate variable.
  • network_interface_ids: This parameter expects a list of strings. I addressed this by inserting as array[].
  • azurerm_virtual_network: The virtual network resource was being prioritized before the resource group creation, causing failures. I fixed this by adding a depends_on clause to ensure it waits for the resource group.
  • NetworkWatcherRG: Upon successful deployment, a new resource group named NetworkWatcherRG is automatically created as part of Azure's network monitoring service as shown in below image, which is currently free. This occurs because the configuration includes networking components such as VNET, SUBNET, and NI. This new resource group can be manually deleted or disabled for the deployed resource's region. Reference.

NetworkWatcherRG


Potential Enhancements

  1. Disable Password Authentication: Update the os_profile_linux_config to disable_password_authentication = true for better security and configure SSH keys for VM access.
  2. Secret Management: Move sensitive information such as admin passwords to a secure secret management system like Azure Key Vault.
  3. Scalability: Enhance the configuration to support deployment of multiple VMs and additional Azure resources such as databases and load balancers.

Contributing

Feel free to fork this repository and submit pull requests. For major changes, please open an issue first to discuss what you would like to change. I have started a discussion, and everyone can bring ideas there.


Acknowledgments

Thanks to the Terraform and Azure documentation teams for their extensive resources and examples. This project was inspired by the need to automate and efficiently manage cloud infrastructure.