Terraform your Azure Infrastructure

Terraform makes it extremely easy to deploy vm instances on multiple providers like CloudStack, OpenStack, AWS, Azure and more by describing your complete infrastructure as code. You describe you infrastructure in configuration files “*.tf” for a simple or complex applications or deployments. Terraform then takes that configuration and creates an execution plan so that you can see what it’s going to do before it does it. As the configuration changes, Terraform is able to determine what changed and create incremental execution plans which can be applied. And when you need to tear down the deployed services it’s as easy “terraform destroy”.

I’m going to get into Terraform and deploy vm instances in Azure as a starting point but first we need to download and setup Terraform. I’m on a Mac so that’s the perspective your goin to get here. Download and unzipped the archive file. Everything you need will be located in a single directory from the archive as shown below.

Change into the directory then run:

cd terraformdir
ls -al


Now all you have to do is add the terraform directory path your envionment path. This can be done a few different ways. Here are a couple. The first is not permanent so you’ll have to repeat this process every time you want to use terraform.

export PATH=“$PATH:/path/to/terraform”

You can also add the above line to your “~/.bash_profile”. If you don’t have one, create it. Now you’ll be able to run the terraform commands from the console no matter which path your in.

You should now be able to run terraform commands like `terraform -help` from the console.

My-MacBook:~ antone$ terraform --help
usage: terraform [--version] [--help] <command> [<args>]
Available commands are:
apply Builds or changes infrastructure
destroy Destroy Terraform-managed infrastructure
get Download and install modules for the configuration
graph Create a visual graph of Terraform resources
init Initializes Terraform configuration from a module
output Read an output from a state file
plan Generate and show an execution plan
push Upload this Terraform module to Atlas to run
refresh Update local state file against real resources
remote Configure remote state storage
show Inspect Terraform state or plan
taint Manually mark a resource for recreation
version Prints the Terraform version

Now lets create a virtual machine in Azure using terraform. Terraform uses the Azure provider to connect to your Azure subscription. We’re going to use your publishing file so lets get download it now. Click this link to get your publishing settings file. You’ll be asked for your login and password if your not already logged into Azure. The file should automatically download once your logged in.

Once you we have the publishing settings file we need to create the terraform configuration file. Lets also create a new folder.

mkdir terraformazure
cd terraformazure
vi azure demo.tf

Lets start adding stuff to the configuration file. We’ll start with the provider. Add the section below and replace the filename with your own. My sure the path is correct. In my example, I copied the Azure publishing file to the working folder. This section will allow terraform to connect to your Azure subscription.

# Connect to the Azure Provider
provider "azure" {
settings_file = "${file("BizSpark-8-2-2020-credentials.publishsettings")}”

It’s required that we have a storage account to store the virtual machine files. This section of the configuration file creates the storage service. You’ll see later that we use this resources name as a variable when creating the virtual machines using the “${azure_storage_service.storage.name}” syntax.

# create a storage account
resource "azure_storage_service" "storage" {
name = "defaultstor1"
description = "Made by Terraform."
account_type = "Standard_LRS”

In the next section we create a virtual network and subnet. Just like the storage resource name, we use this resources name as a variable when creating the virtual machines using the “${azure_virtual_network.default.name}” syntax.

#create virtual network
resource "azure_virtual_network" "default" {
name = "test-network"
address_space = [""]
subnet {
name = "subnet1"
address_prefix = ""

Next we create a windows virtual machine making sure to add an endpoint for RDP so that we can connect to later.

# create windoows virtual machine
resource "azure_instance" "windows" {
depends_on = ["azure_virtual_network.default"]
name = "mytf-win-254${count.index}"
image = "Windows Server 2012 R2 Datacenter, July 2015"
size = "Basic_A1"
storage_service_name = "${azure_storage_service.storage.name}"
username = "antone"
password = "Pass!admin123"
time_zone = "America/New_York"
virtual_network = "${azure_virtual_network.default.name}"
count = "1"
endpoint {
name = "RDP"
protocol = "tcp"
public_port = 3389
private_port = 3389

And since we have a windows virtual machine why not create a linux one too? Well thats what we’re doing in the next section. If you notice I have not set to linux virtual machine to use the virtual network. I do however create an ssh endpoint so that we can connect to it remotely.

# create linux virtual machine
resource "azure_instance" "linux" {
name = "mytf-linux-3254${count.index}"
image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1"
storage_service_name = "${azure_storage_service.storage.name}"
username = "antone"
password = "Pass!admin123"
count = "1"
endpoint {
name = "SSH"
protocol = "tcp"
public_port = 22
private_port = 22

In the last section, I get an output of all the ip and virtual ip(vip) addresses created. You can see below that I’m also getting the virtual machine name. You can of course get this information and more by using the `terraform show` command after we’ve deployed the infrastructure using terraform.

# IP address outputs
output "azure-ips" {
value = "${join(",", azure_instance.linux.*.name)}:${join(",", azure_instance.linux.*.vip_address)}"
output "azure-windows-vips" {
value = "${join(",", azure_instance.windows.*.name)}:${join(",", azure_instance.windows.*.vip_address)}"
output "azure-windows-ips" {
value = "${join(",", azure_instance.windows.*.name)}:${join(",", azure_instance.windows.*.ip_address)}”

Paste all of this to your file then save.

Now lets run `terraform plan`. This will show what’s going to be done and should look like the below output.

My-MacBook:terraformazure antone$ terraform plan
Refreshing Terraform state prior to plan...
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.
Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
+ azure_instance.linux
automatic_updates: "" => "0"
description: "" => "<computed>"
endpoint.#: "" => "1"
endpoint.2462817782.name: "" => "SSH"
endpoint.2462817782.private_port: "" => "22"
endpoint.2462817782.protocol: "" => "tcp"
endpoint.2462817782.public_port: "" => "22"
hosted_service_name: "" => "<computed>"
image: "" => "Ubuntu Server 14.04 LTS"
ip_address: "" => "<computed>"
location: "" => "West US"
name: "" => "mytf-linux-32540"
password: "" => "Pass!admin123"
security_group: "" => "<computed>"
size: "" => "Basic_A1"
storage_service_name: "" => "defaultstor1"
subnet: "" => "<computed>"
username: "" => "antone"
vip_address: "" => "<computed>"
+ azure_instance.windows
automatic_updates: "" => "0"
description: "" => "<computed>"
endpoint.#: "" => "1"
endpoint.1814039778.name: "" => "RDP"
endpoint.1814039778.private_port: "" => "3389"
endpoint.1814039778.protocol: "" => "tcp"
endpoint.1814039778.public_port: "" => "3389"
hosted_service_name: "" => "<computed>"
image: "" => "Windows Server 2012 R2 Datacenter, July 2015"
ip_address: "" => "<computed>"
location: "" => "West US"
name: "" => "mytf-win-2540"
password: "" => "Pass!admin123"
security_group: "" => "<computed>"
size: "" => "Basic_A1"
storage_service_name: "" => "defaultstor1"
subnet: "" => "<computed>"
time_zone: "" => "America/New_York"
username: "" => "antone"
vip_address: "" => "<computed>"
virtual_network: "" => "test-network"
+ azure_storage_service.storage
account_type: "" => "Standard_LRS"
description: "" => "Made by Terraform."
label: "" => "Made by Terraform."
location: "" => "West US"
name: "" => "defaultstor1"
primary_key: "" => "<computed>"
secondary_key: "" => "<computed>"
url: "" => "<computed>"
+ azure_virtual_network.default
address_space.#: "" => "1"
address_space.0: "" => ""
location: "" => "West US"
name: "" => "test-network"
subnet.#: "" => "1"
subnet.1787288781.address_prefix: "" => ""
subnet.1787288781.name: "" => "subnet1"
subnet.1787288781.security_group: "" => ""
Plan: 3 to add, 0 to change, 0 to destroy.

Before we run the deployment, lets take a look at some files that we’re created automately. If you run `ls -l` from the command console while in the new folder we create you should see a couple files you had not seen before (“terraform.tfstate” and “terraform.tfstate.backup“).

My-MacBook:terraformazure antone$ ls -l
total 32
-rw-r--r--@ 1 antoneheyward staff 3933 Aug 16 16:44 BizSpark-8-2-2020-credentials.publishsettings
-rw-r--r--@ 1 antoneheyward staff 1754 Aug 16 17:36 azuredeploy.tf
-rw-r--r--@ 1 antoneheyward staff 197 Aug 16 17:36 terraform.tfstate
-rw-r--r--@ 1 antoneheyward staff 197 Aug 16 17:36 terraform.tfstate.backup

The terraform.tfstate file is used to save the state of your infrastructure and the terraform.tfstate.backup file is just a what its called, a backup just in case you need to restore the state file. From the command console run `cat terraform.tfstate`. You should the below output.

    "version": 1,
    "serial": 0,
    "modules": [
            "path": [
            "outputs": {},
            "resources": {}

Now lets get to deploying infrastructure in Azure. Run `terraform apply` to get things going. Terraform will output its progress as its doing its thing. So far I’ve noticed that terraform is somewhat slow provisioning infrastructure in Azure. Much slower than with CloudStack but that’s running in my home lab so it’s clearly not an apple to apple comparison. And its really more of Azure being slow than terraform. Once it’s done you’ll get the output as shown below.

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
azure-ips = mytf-linux-320:
azure-windows-ips = mytf-win-250:
azure-windows-vips = mytf-win-250:

Lets try to add a couple more virtual machines to the configuration. Edit the configuration file `vi azuredemo.tf` and change the “count” to “2” for both the windows and linux resources section. Then run `terraform plan` again. Notice that only the 2 new virtual machines are shown to be added with no other changes needed. Now run `terraform apply`.

Now the output shows the previous and additional virtual machine names and ip addresses.

azure-ips = mytf-linux-320,mytf-linux-321:,
azure-windows-ips = mytf-win-250,mytf-win-251:,
azure-windows-vips = mytf-win-250,mytf-win-251:,

Here are a couple more commands for you to play with. They are <strong>terraform show</strong> and <strong>terraform destroy</strong>. You can log into your Azure subscription and see the rescues you’ve just provisioned with terraform. Remember the Azure portal may not be completely in sync with what terraform from a resource state perspective. Terraform will sometimes show complete when Azure is still provisioning the resources.

Hopefully this was helpful.