Create VM on Microsoft Azure with Terraform
Tags: azure, terraform, tutorial, devops
Published: 2023-12-21 14:08:14

## Azure
Azure is a comprehensive cloud computing platform by Microsoft, offering a diverse range of services and solutions. It provides a scalable infrastructure with virtual machines, storage, and networking capabilities. Azure supports various programming languages, frameworks, and operating systems, promoting flexibility for developers. The platform excels in data management, offering robust databases and analytics tools. Azure's AI and machine learning services empower businesses to build intelligent applications. With a global network of data centers, Azure ensures high availability and low-latency performance. Its identity and access management services enhance security. Azure DevOps facilitates seamless application development, testing, and deployment. Azure's IoT services enable the creation of innovative Internet of Things solutions. The platform emphasizes hybrid cloud solutions, allowing businesses to integrate on-premises infrastructure with cloud services seamlessly.

## Terraform

Terraform is an open-source Infrastructure as Code (IaC) tool developed by HashiCorp. It allows users to define and provision infrastructure using a declarative configuration language. With Terraform, you can manage and automate the creation of infrastructure components across various cloud providers, on-premises environments, and third-party services. Its code-driven approach ensures consistent and reproducible infrastructure deployments. Terraform supports a wide range of providers, making it a versatile tool for orchestrating infrastructure across diverse platforms. Its state management and version control features contribute to efficient infrastructure management and collaboration among teams.

## Pre-requisites
* Terraform should be installed on the machine on which the script will be executed. Install it from [here](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli).
* As the project is for Microsoft Azure, Azure CLI should be installed and configured which will be the authorization for Terraform to provision the resources on the respected account.
To install Azure CLI, click [here](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli).

---

The list of the Azure resources that will be provisioned after terraform execution are:
* [Azure Resource Group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group)
* [Azure Virtual Network](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network)
* [Azure Subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet)
* [Azure Public IP](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip)
* [Azure Network Security Group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group)
* [Azure Network Interface](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface)
* [Azure Virtual Machine](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_virtual_machine)

The architecture diagram is as follows:

![Azure Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4otiodbsin169v4ljlns.png)

Now, the Terraform script is as follows:
```hcl
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.83.0"
    }
    tls = {
      source = "hashicorp/tls"
      version = "4.0.5"
    }
    local = {
      source = "hashicorp/local"
      version = "2.4.0"
    }
  }
}

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
}

provider "tls" {
}

provider "local" {
}

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

resource "local_file" "private-key" {
  filename = "privatekey.pem"
  content = tls_private_key.pem.private_key_openssh

  depends_on = [ tls_private_key.pem ]
}

resource "azurerm_resource_group" "azure-rg" {
  name     = "terraform-learning"
  location = "Central India"

  depends_on = [ local_file.private-key ]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_virtual_network" "vnet" {
  name                = "test-vnet"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location
  address_space       = ["10.0.0.0/16"]

  depends_on = [azurerm_resource_group.azure-rg]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_subnet" "subnet" {
  name                 = "test-subnet"
  resource_group_name  = azurerm_resource_group.azure-rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]

  depends_on = [azurerm_virtual_network.vnet]
}

resource "azurerm_public_ip" "pip" {
  name                = "test-pip"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location
  allocation_method   = "Static"

  depends_on = [azurerm_subnet.subnet]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_network_security_group" "nec-sec-grp" {
  name                = "test-sec-grp"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location

  security_rule {
    name                       = "test123"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  depends_on = [ azurerm_public_ip.pip ]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_network_interface" "nic" {
  name                 = "test-nic"
  resource_group_name  = azurerm_resource_group.azure-rg.name
  location             = azurerm_resource_group.azure-rg.location
  enable_ip_forwarding = true

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.pip.id
  }

  depends_on = [ azurerm_network_security_group.nec-sec-grp ]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_network_interface_security_group_association" "example" {
  network_interface_id      = azurerm_network_interface.nic.id
  network_security_group_id = azurerm_network_security_group.nec-sec-grp.id

  depends_on = [ azurerm_network_interface.nic ]
}

resource "azurerm_linux_virtual_machine" "vm" {
  name                = "test-linux-vm"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location
  size                = "Standard_B2pls_v2"
  admin_username      = "adminuser"

  network_interface_ids = [
    azurerm_network_interface.nic.id,
  ]

  admin_ssh_key {
    username   = "adminuser"
    public_key = tls_private_key.pem.public_key_openssh
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  depends_on = [ azurerm_network_interface_security_group_association.example ]

  tags = {
    environmet = "testing"
  }
}
```

---

Let's understand each block of the terraform script separately:
### Terraform Providers:
* In this block, we can define the providers that we will use for the provision of the resources. Here as per the tutorial, we have used the `azurerm` provider along with `tls` for TLS private and public keys for VM and the `local` provider to store the private key to connect the remote machine on the local machine where Terraform will execute the script.
```hcl
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "3.83.0"
    }
    tls = {
      source = "hashicorp/tls"
      version = "4.0.5"
    }
    local = {
      source = "hashicorp/local"
      version = "2.4.0"
    }
  }
}

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
}

provider "tls" {
}

provider "local" {
}
```
### TLS PEM key and Local File
* TLS PEM keys refer to the cryptographic keys encoded in the PEM (Privacy Enhanced Mail) format, commonly used for Transport Layer Security (TLS) protocols. These keys, usually in the form of PEM-encoded files, contain public and private key pairs used for securing communications over a network, such as those in HTTPS connections. 
* The `local_file` provider is used to store any resource item created by Terraform on the machine from where it is being executed.
* Check here for [terraform tls private key](https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key) and [terraform local file](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file)
```hcl
resource "tls_private_key" "pem" {
  algorithm = "RSA"
  rsa_bits = 4096
}

resource "local_file" "private-key" {
  filename = "privatekey.pem"
  content = tls_private_key.pem.private_key_openssh

  depends_on = [ tls_private_key.pem ]
}
```

### Resource Group:
* In Azure, Resource Groups are like containers to manage and group the resources. *(Similar to AWS VPC* or *GCP Projects)*
```hcl
resource "azurerm_resource_group" "azure-rg" {
  name     = "terraform-learning"
  location = "Central India"

  depends_on = [ local_file.private-key ]

  tags = {
    environmet = "testing"
  }
}
```

### Virtual Network & Subnet:
* Azure Virtual Network (VNet) is a fundamental building block in Microsoft Azure, allowing users to create private, isolated networks in the cloud. It enables secure communication between Azure resources, extends on-premises networks, and provides customizable IP address spaces, subnets, and network security policies for efficient and controlled connectivity.

```hcl
resource "azurerm_virtual_network" "vnet" {
  name                = "test-vnet"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location
  address_space       = ["10.0.0.0/16"]

  depends_on = [azurerm_resource_group.azure-rg]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_subnet" "subnet" {
  name                 = "test-subnet"
  resource_group_name  = azurerm_resource_group.azure-rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.2.0/24"]

  depends_on = [azurerm_virtual_network.vnet]
}
```

### Network Security Group
* Azure NSG allows users to manage and control the inbound and outbound network traffic for the resources present in the specific Resource Group.

```hcl
resource "azurerm_network_security_group" "nec-sec-grp" {
  name                = "test-sec-grp"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location

  security_rule {
    name                       = "test123"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  depends_on = [ azurerm_public_ip.pip ]

  tags = {
    environmet = "testing"
  }
}
```

### Network Interface Card
* Azure NIC (Network Interface Card) is a crucial component in Microsoft Azure's networking infrastructure, serving as a virtual or physical hardware device that enables communication between virtual machines (VMs) and the broader Azure network. It plays a vital role in managing network traffic, facilitating secure connections, and supporting various networking features, contributing to the overall performance and connectivity within the Azure cloud environment.
* Also, the inbound/outbound rules mentioned in the Network Security Group are assigned to the respective Network Interface Card. Hence, in the future, if any further changes in the network rules are done then it will directly update the interface card.

```hcl
resource "azurerm_network_interface" "nic" {
  name                 = "test-nic"
  resource_group_name  = azurerm_resource_group.azure-rg.name
  location             = azurerm_resource_group.azure-rg.location
  enable_ip_forwarding = true

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.pip.id
  }

  depends_on = [ azurerm_network_security_group.nec-sec-grp ]

  tags = {
    environmet = "testing"
  }
}

resource "azurerm_network_interface_security_group_association" "example" {
  network_interface_id      = azurerm_network_interface.nic.id
  network_security_group_id = azurerm_network_security_group.nec-sec-grp.id

  depends_on = [ azurerm_network_interface.nic ]
}
```

### Virtual Machine
* Azure VM (Virtual Machine) is a scalable and flexible computing solution provided by Microsoft Azure. It allows users to deploy and run virtualized Windows or Linux servers in the cloud, offering on-demand computing resources. Azure VMs support a wide range of applications and workloads, providing the ability to customize configurations, scale resources up or down based on demand, and integrate seamlessly with other Azure services for comprehensive cloud-based solutions.

```hcl
resource "azurerm_linux_virtual_machine" "vm" {
  name                = "test-linux-vm"
  resource_group_name = azurerm_resource_group.azure-rg.name
  location            = azurerm_resource_group.azure-rg.location
  size                = "Standard_B2pls_v2"
  admin_username      = "adminuser"

  network_interface_ids = [
    azurerm_network_interface.nic.id,
  ]

  admin_ssh_key {
    username   = "adminuser"
    public_key = tls_private_key.pem.public_key_openssh
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-jammy"
    sku       = "22_04-lts"
    version   = "latest"
  }

  depends_on = [ azurerm_network_interface_security_group_association.example ]

  tags = {
    environmet = "testing"
  }
}
```
> NOTE: To check the information about VM size, image publisher, offer, SKU, and version can be fetched using the below commands:
```
az vm list-sizes --location centralindia --output table
az vm image list --location centralindia --output table
```
All the Terraform modules are used from the official [Terraform Azure Provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) documentation.

---

To run the terraform script, execute the following commands:
* `terraform init`: To initialize the project and install the mentioned terraform providers if needed.
* `terraform plan`: This command will provide a terminal output that includes the list of resources that will be provisioned by terraform.
* `terraform apply`: This command will create the resources stated in the script.

That is all for today's blog. Keep learning👍🏻
Thank You 🙂