Table of Contents
Terraform in Azure Cloud Shell
Summary: In this post I'll show you a few basic terraform commands and configuration files. If you're trying to learn terraform on azure this is an easy and fast tutorial to get started.
Date: 11 January 2025
We'll cover the following topics:
- Setup the main.tf file with the azurerm provider configuration and the resource group
- We will create terraform files for a vnet and a subnet
- We will create a vm with a ip address in the subnet
- We will have terraform create a random password for the vm
- We will figure out where to find the password
- At the end we will manually create an additional subnet and have the vm use it
In the end we will use terraform destroy
to remove all resources.
A Reminder on Terraform
Here is a list of terraform terminology and knowledge you need to know to follow this tutorial. Most of it was already covered here, but I'll repeat it here for your convenience:
- Terraform looks automatically for all files with the .tf extension in the directory you run the command from and combines them into one configuration
- So you only need to define the provider once
- So you can use separate files for separate resources
- Hashicorp calls the terraform technical documentation online a “registry”:
- Make sure you know how to find the documentation for the resources you want to create:
- Use the 'Documentation' per provider for examples and authentication information
- If you google for 'terraform registry' + the resource you want to create you'll almost always find the right page
- For example: 'terraform registry azurerm virtual network'
- Use the version dropdown to find the version of the provider you are using:
terraform { required_version = ">= 0.14.9" required_providers { azurerm = { source = "hashicorp/azurerm" version = "3.112" } } }
- Resources describe infrastructure objects in Terraform configurations
- Data sources allow Terraform to use information defined outside of Terraform or defined by another separate Terraform configuration
Start Cloud Shell
See here for how to start cloud shell. For direct access, use: https://shell.azure.com/
Once started, you can check if you're ready to go using the following commands:
terraform --version cd clouddrive code rg.tf
This willl check if terraform is available, and will start the integrated editor in the clouddrive directory.
Setup the main.tf file
Use the code below to create a main.tf file in your cloud shell. Don't forget to change the subscription_id and tenant_id to your own values.
- main.tf
variable "resource_group_core" { type = string default = "rg_tf" } provider "azurerm" { subscription_id = "30b3c71d-a123-a123-a123-abcd12345678" tenant_id = "7e4an71d-a123-a123-a123-abcd12345678" features {} } resource "azurerm_resource_group" "rg" { name = var.resource_group_core location = "West Europe" }
Now, you can test the configuration by running the following commands:
terraform init # output: Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/azurerm... - Installing hashicorp/azurerm v4.15.0... - Installed hashicorp/azurerm v4.15.0 (signed by HashiCorp) Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future. Terraform has been successfully initialized! # Now run the plan command terraform plan # output: Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # azurerm_resource_group.rg will be created + resource "azurerm_resource_group" "rg" { + id = (known after apply) + location = "westeurope" + name = "rg_tf" } Plan: 1 to add, 0 to change, 0 to destroy. # Now run the apply command terraform apply # output: Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # azurerm_resource_group.rg will be created + resource "azurerm_resource_group" "rg" { + id = (known after apply) + location = "westeurope" + name = "rg_tf" } Plan: 1 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes azurerm_resource_group.rg: Creating... azurerm_resource_group.rg: Creation complete after 9s [id=/subscriptions/30b3c71d-a123-a123-a123-abcd12345678/resourceGroups/rg_tf]
Create a VNet and Subnet
Now create a vnet.tf file with the following content:
- vnet.tf
resource "azurerm_virtual_network" "vnet" { name = "vnet-sjoerd" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name address_space = ["10.0.0.0/16"] } resource "azurerm_subnet" "subnet" { name = "snet-sjoerd" virtual_network_name = azurerm_virtual_network.vnet.name resource_group_name = azurerm_resource_group.rg.name address_prefixes = ["10.0.1.0/24"] }
You can now run terraform apply
to create the vnet and subnet.
Create a VM
Now create a vm.tf file with the following content. Note that the network interface is using the subnet we defined earlier. Because terraform is looking at all of the terraform files in the directory, it knows about the subnet:
- vm.tf
resource "azurerm_network_interface" "nic" { name = "nic-vm-sjoerd" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name ip_configuration { name = "Configuration" subnet_id = azurerm_subnet.subnet.id private_ip_address_allocation = "Dynamic" } } resource "azurerm_windows_virtual_machine" "vm" { name = "vm-sjoerd" admin_username = "azureuser" admin_password = "password1234!" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name network_interface_ids = [azurerm_network_interface.nic.id] size = "Standard_DS1_v2" os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2022-datacenter-azure-edition" version = "latest" } }
You can now run terraform apply
to create the vm and the network interface.
Create a Random Password
As you can see above we have created a set password for the VM. You can't really use this in production environments as it exposes your password in plain text and possibly in a repository. So let's change the code to use a random password generated by terraform:
- vm2.tf
resource "random_password" "admin_password" { length = 10 special = true override_special = "!#$%&*()-_=+[]{}<>:?" } resource "azurerm_network_interface" "nic2" { name = "nic-vm-sjoerd2" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name ip_configuration { name = "Configuration" subnet_id = azurerm_subnet.subnet.id private_ip_address_allocation = "Dynamic" } } resource "azurerm_windows_virtual_machine" "vm2" { name = "vm-sjoerd2" admin_username = "azureuser" admin_password = random_password.admin_password.result location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name network_interface_ids = [azurerm_network_interface.nic2.id] size = "Standard_DS1_v2" os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2022-datacenter-azure-edition" version = "latest" } }
You can now run terraform apply
to create the vm and the network interface. The new VM will have a random password.
Where to Find the Password
You can find the password in the terraform state file. You can find the state file in the .terraform directory in the directory you are running terraform from. The password is stored in plain text in the state file. You can run the following command to find the password: code bash.tfstate
Note that some characters in the password might be shown as a unicode character:
"password": "Oy\u003cQUJpwd%"
The above part \u003c
is actually the <
character. So be aware of \uXXXX
characters in the password. The password above is actually: Oy<QUJpwd%
Use an existing subnet
Now in the azure portal, in the vnet we created, create a new subnet. You can use the following code to create a new network interface and vm that uses the new subnet:
- vm3.tf
data "azurerm_subnet" "subnet_manual" { name = "snet-manual" virtual_network_name = azurerm_virtual_network.vnet.name resource_group_name = azurerm_resource_group.rg.name } resource "random_password" "admin_password3" { length = 10 special = true override_special = "!#$%*()-_=+[]{}:?" } resource "azurerm_network_interface" "nic3" { name = "nic-vm-sjoerd3" location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name ip_configuration { name = "Configuration" subnet_id = data.azurerm_subnet.subnet_manual.id private_ip_address_allocation = "Dynamic" } } resource "azurerm_windows_virtual_machine" "vm3" { name = "vm-sjoerd3" admin_username = "azureuser" admin_password = random_password.admin_password3.result location = azurerm_resource_group.rg.location resource_group_name = azurerm_resource_group.rg.name network_interface_ids = [azurerm_network_interface.nic3.id] size = "Standard_DS1_v2" os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2022-datacenter-azure-edition" version = "latest" } }
Now the subnet is defined as a data source, indicating it was created outside of terraform. You can now run terraform apply
to create the vm and the network interface.
Destroy All Resources
When you're done, you can run the following command to destroy all resources: terraform destroy