Create Azure Virtual Machine (Linux VM) with Terraform
Tags: azure, terraform, linux, iac
Published: 2023-05-29 04:20:02

Azure Virtual Machines provide a flexible and scalable solution for running your applications in the cloud. By leveraging Terraform, we can automate the creation and configuration of VMs, ensuring consistency and reproducibility in our deployments. We will specifically focus on Linux VMs, as they are widely used for various purposes, such as web hosting, application deployment, and data analysis.

In this article, we will go through the steps of using Terraform to create an Azure Virtual machine with the following resources: Azure Resource Group, Azure Virtual Network, Network Subnet, Public IP address, Network Security Group, Network Interface, Linux Virtual Machine.

[Prerequisites](#Prerequisites) 
[Install and Configure Azure CLI](#Azure_CLI) 
[Install and Configure Terraform](#Terraform)
[Provision the Infrastructure on Azure](#Provision)
[Destroy the infrastructure](#Destroy)
[Conclusion](#Conclusion)

<a name="Prerequisites"></a> 
## Prerequisites
- An azure account and subscription: If you don't have an Azure subscription, create a [free account](https://azure.microsoft.com/free/?ref=microsoft.com&utm_source=microsoft.com&utm_medium=docs&utm_campaign=visualstudio) before you begin.
- Infrastructure-as-Code Basics: Basic understanding of infrastructure as code is required to follow along.

**Terraform** is a declarative open-source “[Infrastructure as Code](https://www.ibm.com/topics/infrastructure-as-code)" tool, created by HashiCorp, that enables the definition of on-premises and cloud resources in human-readable configuration files that can be versioned, reused, and shared as code. 

<a name="Azure_CLI"></a>
## Install and Configure Azure CLI

Open a command line from any device that has access to the Azure CLI. For steps on how to configure azure CLI locally, refer to this [Microsoft Documentation](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt)

```bash
sudo apt update
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
```

Run the command `az --version` to confirm this has been installed successfully.

![AZ-Login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l60gg805gs2w91x93ym0.png)

<a name="Terraform"></a>
## Install and Authenticate Terraform to Azure

### Install Terraform

Depending on your operating system, the command to install terraform may be slightly different. Refer to the terraform documentation for system-specific instruction: [Terraform Installation Guide](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli)

```bash
sudo apt update
sudo snap install terraform --classic 
```

Run `terraform --version` command to view the version on terraform installed.

```bash
terraform --version
```

Make a Repository to hold our Terraform files and change the directory to this newly created repository.

```bash
mkdir Terraform
cd Terraform
```

### Authenticate Terraform to Azure using a Microsoft Account

You can either authenticate to Azure using a Microsoft account or by using a service principal. In this blog, we will authenticate using a Microsoft account.

Run `az login` without any parameters and follow the instructions to sign in to Azure. When you log in successful, `az login` will display a list of the Azure subscriptions associated with the logged-in Microsoft account, including the default subscription.

```
az login
```

If you want to use a specific subscription, run the command below to show and set the required subscription.

```bash
az account show
az account set --subscription "<subscription_id_or_subscription_name>"
```
<a name="Provision"></a>
## Provision the Infrastructure on Azure

Create a file to provision virtual machine on Microsoft Azure. This file will create following resources: Azure Resource Group, Azure Virtual Network, Network Subnet, Public IP address, Network Security Group, Network Interface, Linux Virtual Machine. We will call the file `main.tf`.

```
nano main.tf
```

Copy and paste the code below into the file created above

```
provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "main" {
  name     = "${var.prefix}-resources"
  location =  "eastus"
}

resource "azurerm_virtual_network" "main" {
  name                = "${var.prefix}-network"
  address_space       = ["10.0.0.0/22"]
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
}

resource "azurerm_subnet" "internal" {
  name                 = "internal"
  resource_group_name  = azurerm_resource_group.main.name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.2.0/24"]
}

resource "azurerm_public_ip" "public_ip" {
  name                = "acceptanceTestPublicIp1"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  allocation_method   = "Dynamic"
}

resource "azurerm_network_interface" "main" {
  name                = "${var.prefix}-nic"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location

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

resource "azurerm_network_security_group" "nsg" {
  name                = "ssh_nsg"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name

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

resource "azurerm_network_interface_security_group_association" "association" {
  network_interface_id      = azurerm_network_interface.main.id
  network_security_group_id = azurerm_network_security_group.nsg.id
}

resource "azurerm_linux_virtual_machine" "main" {
  name                            = "${var.prefix}-vm"
  resource_group_name             = azurerm_resource_group.main.name
  location                        = "eastus"
  size                            = "Standard_D2s_v3"
  admin_username                  = "adminuser"
  admin_password                  = "The$admin#password"
  disable_password_authentication = false
  network_interface_ids = [
    azurerm_network_interface.main.id,
  ]

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

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

Remember to change the admin password to a string with your desired password (e.g. `admin_password = "253P@assWord!@#"`.

Next, create a terraform variable file with name `variable.tf`

```bash
variable "prefix" {
  description = "The prefix which should be used for all resources in this example"
}

variable "location" {
  description = "eastus"
} 
```

Run `Terraform init` to initialize the repository. 

![terraform-initn](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a92rz841pjfr1u4y1cca.png)

To see changes that will take effect before any action run `terraform plan`.

```bash
terraform plan
```

When prompted for location, enter your preferred location e.g. eastus

When prompted for prefix, enter the prefix that will be prepended to all resources created by terraform.

![terraform-plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/byjnzed7oxdpyqll8ift.png)

To apply the changes to our Azure environment, run the command below `terraform apply` to apply the changes. This should go off and provision the resources in your azure cloud environment. 

```bash
terraform apply
```

![terraform-apply](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lyoivvauxppiz8ymblo.png)

To see a list of resources provisioned, run the `terraform state list` command.

```bash
terraform state list
```


![terraform-state-list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvthzeo1oexcdzfesvy7.png)

<a name="Destroy"></a>
## Destroying the infrastructure

Run the below command and type `yes` to destroy the infrastructure

```bash
terraform destroy
```
<a name="Conclusion"></a>
## Conclusion:
In this article, we harnessed the power of Terraform for infrastructure as code to provision resources on Azure. Firstly, we installed and configured Azure CLI on local device, installed and authenticated Terraform to provision infrastructure and resources on Azure. At the end, we destroyed the infrastructure to avoid incurring much cost.

Thanks for reading. Let me know if you found this helpful. You are welcome to follow me on LinkedIn → [Paschal Ogu](https://www.linkedin.com/in/paschal-ogu/) and Twitter → [Paschal_ik](https://twitter.com/Paschal_ik).

**Coming Up Next**:
- Configure Apache web server on Linux Virtual Machine using Ansible
- Enable SSL on a Custom Domain
- Install LAMP STACK on an Azure Virtual machine

**References**:
- Terraform on Azure documentation – Articles, samples, references, and resources – [Terraform on Azure](https://learn.microsoft.com/en-us/azure/developer/terraform/)
- Terraform | HashiCorp Developer [Terraform Documentation](https://developer.hashicorp.com/terraform)