Create Azure VM with Terraform

Azure Vm will be created along with Resource Group, Vnet, Security Group, Public IP

Why do I use Terraform to create Azure resources?

Well, I am not interested in repeating the same manual steps in Azure Portal, every-time for multiple environments which needs same configuration.

Terraform gives the flexibility to define a template for an environment & then use it for multiple times.

Now, let's write some hcl code...

providers.tf

terraform {
  backend "local" {}
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = "3.20.0"
    }
  }
}

provider "azurerm" {
  features {}
}

Configure a remote backend to store state files. Terraform uses persisted state data to keep track of the resources it manages. This lets multiple people access the state data and work together on that collection of infrastructure resources.

Resource Group

resource "azurerm_resource_group" "rg" {
  location = "eastus"
  name     = "tf-anils-demo"
}

All our resources will be created in this resource group only.

Vnet

resource "azurerm_virtual_network" "vnet" {
  address_space       = ["10.0.0.0/16"]
  name                = "anils_demo-vnet"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  depends_on = [
    azurerm_resource_group.rg,
  ]
}

Subnet

resource "azurerm_subnet" "subnet" {
  address_prefixes     = ["10.0.0.0/24"]
  name                 = "default"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  depends_on = [
    azurerm_virtual_network.vnet,
  ]
}

Public-IP

resource "azurerm_public_ip" "public_ip" {
  allocation_method   = "Static"
  location            = azurerm_resource_group.rg.location
  name                = "anils-demo-1-ip"
  resource_group_name = azurerm_resource_group.rg.name
  sku                 = "Standard"
  depends_on = [
    azurerm_resource_group.rg,
  ]
}

Public IP will be attached to Network Interface.

Security Group & Rule

resource "azurerm_network_security_group" "sg" {
  location            = azurerm_resource_group.rg.location
  name                = "anils-demo-1-nsg"
  resource_group_name = azurerm_resource_group.rg.name
  depends_on = [
    azurerm_resource_group.rg,
  ]
}
resource "azurerm_network_security_rule" "rule1" {
  access                      = "Allow"
  destination_address_prefix  = "*"
  destination_port_range      = "22"
  direction                   = "Inbound"
  name                        = "SSH"
  network_security_group_name = azurerm_network_security_group.sg.name
  priority                    = 300
  protocol                    = "Tcp"
  resource_group_name         = azurerm_resource_group.rg.name
  source_address_prefix       = "*"
  source_port_range           = "*"
  depends_on = [
    azurerm_network_security_group.sg,
  ]
}

Here in rules allowing only SSH for our login purpose.

Network Interface

resource "azurerm_network_interface" "ni-1" {
  location            = azurerm_resource_group.rg.location
  name                = "anils-demo-114"
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "ipconfig1"
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id = azurerm_public_ip.public_ip.id
    subnet_id = azurerm_subnet.subnet.id
  }

  depends_on = [
    azurerm_public_ip.public_ip,
    azurerm_subnet.subnet,
    azurerm_network_security_group.sg,
  ]
}

resource "azurerm_network_interface_security_group_association" "attach-sg-ni" {
  network_interface_id      = azurerm_network_interface.ni-1.id
  network_security_group_id = azurerm_network_security_group.sg.id
}

Don't forget to attach network interface & security group. If not attached, you can't able to access VM.

Linux VM

resource "tls_private_key" "ssh" {
  algorithm = "RSA"
  rsa_bits  = "4096"
}

resource "local_file" "ssh_private_key" {
  content           = tls_private_key.ssh.private_key_pem
  filename          = "./tls/private.pem"
  file_permission   = "0600"
}
resource "azurerm_linux_virtual_machine" "vm-1" {

  admin_username                  = "anil"
  disable_password_authentication = true
  location                        = azurerm_resource_group.rg.location
  name                            = "anils-demo-1"

  admin_ssh_key {
    username   = "anil"
    # public_key = file("./ssh_keys/id_rsa.pub")    ## use this if you existing keys
    public_key = tls_private_key.ssh.public_key_openssh
  }
  network_interface_ids = [
    azurerm_network_interface.ni-1.id,
    ]
  resource_group_name             = azurerm_resource_group.rg.name
  size                            = "Standard_B1s"
  boot_diagnostics {
  }
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }
  source_image_reference {
    offer     = "0001-com-ubuntu-server-jammy"
    publisher = "canonical"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
  depends_on = [
    azurerm_network_interface.ni-1,
  ]
}

Here, ssh-keys will be created and then public key will be added to VM. Using the private we have to login.

Place all components in single file. main.tf

################################
##      Resource Group
################################
resource "azurerm_resource_group" "rg" {
  location = "eastus"
  name     = "tf-anils-demo"
}

################################
##      Vnet
################################
resource "azurerm_virtual_network" "vnet" {
  address_space       = ["10.0.0.0/16"]
  name                = "anils_demo-vnet"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  depends_on = [
    azurerm_resource_group.rg,
  ]
}

################################
##      subnet
################################
resource "azurerm_subnet" "subnet" {
  address_prefixes     = ["10.0.0.0/24"]
  name                 = "default"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  depends_on = [
    azurerm_virtual_network.vnet,
  ]
}

################################
##      Public IP
################################
resource "azurerm_public_ip" "public_ip" {
  allocation_method   = "Static"
  location            = azurerm_resource_group.rg.location
  name                = "anils-demo-1-ip"
  resource_group_name = azurerm_resource_group.rg.name
  sku                 = "Standard"
  depends_on = [
    azurerm_resource_group.rg,
  ]
}


################################
##      Security Group & Rules
################################
resource "azurerm_network_security_group" "sg" {
  location            = azurerm_resource_group.rg.location
  name                = "anils-demo-1-nsg"
  resource_group_name = azurerm_resource_group.rg.name
  depends_on = [
    azurerm_resource_group.rg,
  ]
}
resource "azurerm_network_security_rule" "rule1" {
  access                      = "Allow"
  destination_address_prefix  = "*"
  destination_port_range      = "22"
  direction                   = "Inbound"
  name                        = "SSH"
  network_security_group_name = azurerm_network_security_group.sg.name
  priority                    = 300
  protocol                    = "Tcp"
  resource_group_name         = azurerm_resource_group.rg.name
  source_address_prefix       = "*"
  source_port_range           = "*"
  depends_on = [
    azurerm_network_security_group.sg,
  ]
}


################################
##      Network Interface
################################
resource "azurerm_network_interface" "ni-1" {
  location            = azurerm_resource_group.rg.location
  name                = "anils-demo-114"
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "ipconfig1"
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id = azurerm_public_ip.public_ip.id
    subnet_id = azurerm_subnet.subnet.id
  }

  depends_on = [
    azurerm_public_ip.public_ip,
    azurerm_subnet.subnet,
    azurerm_network_security_group.sg,
  ]
}


################################
##      Attach Security Group & Network Interface
################################

resource "azurerm_network_interface_security_group_association" "attach-sg-ni" {
  network_interface_id      = azurerm_network_interface.ni-1.id
  network_security_group_id = azurerm_network_security_group.sg.id
}


################################
##      Linux VM
################################

resource "tls_private_key" "ssh" {
  algorithm = "RSA"
  rsa_bits  = "4096"
}

resource "local_file" "ssh_private_key" {
  content           = tls_private_key.ssh.private_key_pem
  filename          = "./tls/private.pem"
  file_permission   = "0600"
}

resource "azurerm_linux_virtual_machine" "vm-1" {

  admin_username                  = "anil"
  disable_password_authentication = true
  location                        = azurerm_resource_group.rg.location
  name                            = "anils-demo-1"

  admin_ssh_key {
    username   = "anil"
    # public_key = file("./ssh_keys/id_rsa.pub")    ## use this if you existing keys
    public_key = tls_private_key.ssh.public_key_openssh
  }
  network_interface_ids = [
    azurerm_network_interface.ni-1.id,
    ]
  resource_group_name             = azurerm_resource_group.rg.name
  size                            = "Standard_B1s"
  boot_diagnostics {
  }
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"
  }
  source_image_reference {
    offer     = "0001-com-ubuntu-server-jammy"
    publisher = "canonical"
    sku       = "22_04-lts-gen2"
    version   = "latest"
  }
  depends_on = [
    azurerm_network_interface.ni-1,
  ]
}

outputs.tf

output "Vm-ip" {
    value = azurerm_linux_virtual_machine.vm-1.public_ip_address
}

Run the terraform commands to create resources

$ terraform init

$ terraform plan
-> Plan: 12 to add, 0 to change, 0 to destroy.

$ terraform apply --auto-approve
-> Apply complete! Resources: 12 added, 0 changed, 0 destroyed.

Outputs:

Vm-ip = "20.232.149.161"

Capture.JPG

Destroy resources once done...

$ terraform destroy --auto-approve
-> Destroy complete! Resources: 12 destroyed.

Code available at Github Thanks for reading :)