Create VMs in Azure with Terraform
What is Terraform
Terraform is an open-source infrastructure as code software tool that enables you to safely and predictably create, change, and improve infrastructure as described on their website.
Creating a Service Principal on Azure
If you want to automate some of the workloads on Azure, then you will need Service Principal (SP) accounts. We will use an SP for our automation.
Because only way for Terraform to work on Azure is to connecting it, we will connect Terraform with Azure via a Service Principal. Let’s create our SP.
You will need your subscription ID for this SP. You can get your subscription ID after logging into Azure-CLI or Azure portal and using CLI with below command:
az account show --subscription <subscription_name> --query id
After getting your subscription ID, we can create our SP with below command:
az ad sp create-for-rbac - name <service_principal_name> - role Contributor - scopes /subscriptions/<subscription_id>
Now, after creating our SP, the information about our SP will be on the STDOUT like below:
{
"appId": "someappid",
"displayName": "sp-diplay-name",
"password": "superstrongpassword",
"tenant": "tenantid"
}
Configuring Terraform
First we need to configure Azure connection information for Terraform. This can be done in multiple ways. I prefer to create environment variables and then pass these variables to Terraform in runtime. The other way is to write connection information in the providers.tf file. (Not recommending it)
File structure
Our file structure will be like below:
├── .connection.env
├── main.tf
├── outputs.tf
├── providers.tf
└── variables.tf
Creating providers.tf file
Terraform will know which provider to use from our providers.tf file. Let’s create a file named providers.tf with below configuration
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "=2.84.0"
}
}
required_version = ">= 1.1.3"
}
provider "azurerm" {
features {}
}
Creating variables.tf file
We will provide any variables to Terraform with variables.tf file. In this case, we are providing only our resource group location.
variable "resource_group_location" {
default = "West Europe"
description = "Location of the resource group."
}
Creating outputs.tf file
We will get any output generated from Terraform with outputs.tf file. In this case we are getting our VM’s resource group name, public IP and private key of the SSH-key.
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}
output "public_ip_address" {
value = azurerm_linux_virtual_machine.my_terraform_vm.public_ip_address
}
output "tls_private_key" {
value = tls_private_key.secureadmin_ssh.private_key_pem
sensitive = true
Creating main.tf file
Terraform will run based on main.tf file. We will add our VM configuration as well as Azure resource configurations to this file.
resource "azurerm_resource_group" "rg" {
location = var.resource_group_location
name = "my-first-terraform-RG"
}
# Create virtual network
resource "azurerm_virtual_network" "my_terraform_network" {
name = "my-first-terraform-network"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}
# Create subnet
resource "azurerm_subnet" "my_terraform_subnet" {
name = "my-first-terraform-subnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.my_terraform_network.name
address_prefixes = ["10.0.1.0/24"]
}
# Create public IPs
resource "azurerm_public_ip" "my_terraform_public_ip" {
name = "my-first-terraform-PublicIP"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
}
# Create Network Security Group and rule
resource "azurerm_network_security_group" "my_terraform_nsg" {
name = "my-first-terraform-NetworkSecurityGroup"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
# Note that this rule will allow all external connections from internet to SSH port
security_rule {
name = "SSH"
priority = 200
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
# Create network interface
resource "azurerm_network_interface" "my_terraform_nic" {
name = "my-first-terraform-myNIC"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
ip_configuration {
name = "my-first-terraform-nic-configuration"
subnet_id = azurerm_subnet.my_terraform_subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.my_terraform_public_ip.id
}
}
# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "my-nsg-assoc" {
network_interface_id = azurerm_network_interface.my_terraform_nic.id
network_security_group_id = azurerm_network_security_group.my_terraform_nsg.id
}
# Create (and display) an SSH key
resource "tls_private_key" "secureadmin_ssh" {
algorithm = "RSA"
rsa_bits = 4096
}
# Create virtual machine
resource "azurerm_linux_virtual_machine" "my_terraform_vm" {
name = "my-first-terraform-VM"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
network_interface_ids = [azurerm_network_interface.my_terraform_nic.id]
size = "Standard_DS1_v2"
os_disk {
name = "my-first-terraform-OsDisk"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "UbuntuServer"
sku = "18.04-LTS"
version = "latest"
}
computer_name = "my-first-terraform-vm"
admin_username = "secureadmin"
disable_password_authentication = true
admin_ssh_key {
username = "secureadmin"
public_key = tls_private_key.secureadmin_ssh.public_key_openssh
}
}