Starting with Terraform and Azure

Photo by Lorenzo Cafaro from Pexels

Creating infrastructure using the Azure Portal, one click at a time, can be a great way to explore, and to learn and see what’s available. When it comes to creating real environments for development, testing, and production however, this approach has major limitations.

  • It’s time consuming to create environments in this way.
  • They are difficult to document.
  • It’s almost impossible to recreate them with complete accuracy.

So ideally you should automate your creation of cloud infrastructure. This gives the ability to create environments by running a single command. The scripts or configuration files can be checked into source control and serve as documentation. Anyone with access to these files can create identical environments — eliminating the sorts of problems when something works in one environment but not another. Finally, when an environment is no longer needed, it can be destroyed within minutes by running another command.

There are a number of ways to do this, including writing scripts using Azure Powershell or Azure CLI, or by using Azure Resource Manager Templates.

In this article I’m going to show how to start creating Azure resources with Terraform. We’ll create an app service, which will launch a docker container hosting a static website. In future articles, we’ll look at creating more complex resources, and at making our terraform configuration more flexible and reusable.

To follow along, you will of course, need to have some sort of Azure account. If you don’t have one, you can sign up for a free trial here.

Why Terraform?

With Terraform, you don’t write scripts which detail how to create your resources — rather you create a file, or set of files, called a configuration which describes how your resources should look once created. Terraform does the work of determining how to get to your desired state. This means you can apply a terraform configuration, make a small adjustment to it, and then apply it again, much more easily than you can adjust and re-run a powershell script — you don’t have to worry about what the current state of your infrastructure is, Terraform keeps track of that, and only makes the changes that are needed to achieve your desired state.

Installing the pre-requisites

The first thing to do is to download Terraform from here. The download is a zip file containing a single executable (these instructions assume we are using Windows, but if you’re using Linux, most of this should still work). You need to extract the zip file, drop it in a folder, and add that folder to your path. Note that the configurations in this article assume you have at least version 0.12 of Terraform.

Next up, you’ll need to install the Azure CLI, which can be downloaded here for Windows users. Download links for other platforms are available. Download and follow the install instructions.

Open up your favouite command-prompt, and verify that everything is available

> terraform --version
Terraform v0.12.1
> az --version
azure-cli 2.0.62
(lots more output)

Creating the configuration file

We are going to start with a single configuration file, so create a folder, open it with your code editor (I’m using Visual Studio Code), and create a file called main.tf, containing a single line.

provider "azurerm" {}

Save the file, open a command prompt and switch to the folder created earlier, and run:

Here we are telling Terraform which provider we want to us. Providers are the bridge between Terraform and the various kinds of infrastrure that can be controlled. There’s an extensive list of providers on hashicorp’s site. As we’re creating Azure resources, we’re using the azurerm provider.

Terraform will read the file, and download the azurerm provider which it saves in a folder called .terraform

Creating our first resource

Resources in Azure are created in resource groups, so we’ll now edit main.tf and add the following block:

resource “azurerm_resource_group” “my-group” {
name = “my-resource-group”
location = “westus”
}

Here, the word resource is part of the terraform configuration syntax, azurerm_resource_group is the type of resource we want to create, and my-group is the name of the resource. Note that this is the name of the resource in the terraform configuration — it is not the same thing as the name of the azure resource group, which is my-resource-group and appears inside the block.
Once you’ve added this to the file, you can run

> terraform plan

Terraform will analyse the configuration and output what would need to be done to bring your infrastructure in line with what you’ve requested, in this case we’ll see something like this:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
+ azurerm_resource_group.my-group
id: <computed>
location: “westus”
name: “my-resource-group”
tags.%: <computed>
Plan: 1 to add, 0 to change, 0 to destroy.

If you’re not logged into Azure you’ll get an error like this:

* provider.azurerm: Error building AzureRM Client: Error retrieving the Profile from the Azure CLI: No Subscription was Marked as Default in the Azure Profile. Please re-authenticate using `az login`.

In this case you need to do as suggested, and run the az login command to authenticate, and then go back and run terraform plan again.

Once you’ve successfully run the plan command, you can go ahead and create the resource group with:

> terraform apply

You’ll be prompted for confirmation, and then the resource group will be created. You can use the Azure portal to verify that the group does now exist.

State file

If you look at your configuration folder, you’ll now see that it contains an additional file — terraform.tfstate. You shouldn’t edit this file, but feel free to take a look at its contents — it’s a description of the current state of your infrastructure. When Terraform processes the configuration and determines what needs to be done to bring your infrastructure into line with it, it is comparing the configuration with the state described in this file, rather than looking at the actual resources you’ve created. This implies two things —

  1. You shouldn’t alter your Terraform created infrastructure, except through Terraform. If you were to alter your resources using the Azure Portal for instance, then those changes wouldn’t be reflected in your terraform state file.
  2. The state file is important, and having it in your configuration folder isn’t ideal. A co-worker who also needs to work with your Terraform configuration will need access to the file. You could commit it to source control along with your configuration, but if the state file contains secrets, then that again isn’t a good idea. I’ll explore a better solution in a future article.

Creating a website

Let’s add a couple more resources to our configuration.

resource "azurerm_app_service_plan" "appserviceplan" {
name = "my-service-plan"
location = "westus"
resource_group_name = azurerm_resource_group.group.name
reserved = true # Mandatory for Linux plans
kind = "Linux"
  sku {
tier = "Basic"
size = "B1"
}
}
resource "azurerm_app_service" "web" {
name = "my-site"
location = "westus"
resource_group_name = azurerm_resource_group.group.name
app_service_plan_id = azurerm_app_service_plan.appserviceplan.id
# Configure Docker Image to load on start
  site_config {
linux_fx_version = "DOCKER|nginx"
}
}

So here we are adding to our resource group a service plan, and an app service. The app service is configured to start up a container using the default nginx image, which displays a simple static web page.

There are a number of things worth noting here.

Firstly, the name of the appservice will be used to create the url for the website. The url in this case would be my-site.azurewebsites.net. Unsurprisingly, this has to be globally unique, so you’ll need to change the name to something else. I usually use a fixed prefix which I think will be unique, followed by a suffix which reflects the purpose of the site — e.g. myclient-web-test-1.

Secondly, we can see the syntax used to refer to properties of previously defined resources (strictly speaking, just other resources — as the order in the file doesn’t matter). I could just have hardcoded the resource group name rather than using this syntax, but by writing it this way, am informing Terraform that the app service plan depends on the resource group. This helps Terraform to construct a dependency graph of the resources it’s to create. Without this information, it may attempt to create them in the wrong order.

Once the file’s been updated, we can run terraform apply again, and then we can see that in the portal that, our website exists, and that we can access it through the configured URL.

The final step, when we’ve finished experimenting, is to tear down the environment we’ve created. This is as simple as running

> terraform destroy

After a confirmation step, all of the resources we’ve created will be deleted, and we’re back to a clean slate.

In the next article I’ll build on the configuration we used here, and show how we can build more complex environments, and how we can create multiple environments which are identical apart from their names, so we can have test and development environments which are truly production-like.

Our final configuration file, at this stage looks like this: