man standing near mountains

After unfortunately missing out on a colleague's presentation on "Terraforming Azure", I decided to spend the day looking into terraform and how it works.  I've used Vagrant before and quite like HashiCorp and their products so thought it was about time I had a little play around.

Terraform is a tool which allows you to create and manage cloud-based infrastructure in a structured and codified way.  Infrastructure as code is good because it allows you to use source control to version the changes you want to make to your infrastructure.  This is great when it comes to rolling back if mistakes are made, or indeed knowing what you actually have running in the cloud to begin with.  Of course you can use Azure ARM templates to achieve the same thing, but terraform scripts are written in a very straight forward syntax which allows you to interact with a multitude of cloud vendors, whether it's Azure, AWS or Google Cloud Platform without having to learn any different tools or syntax.

Terraform uses the concept of "providers", which are like plugins which extend the capabilities of the tool, to interact with various cloud systems in a number of different ways.

It interacts with your cloud infrastructure by interpreting .tf files you prepare.  These files are what you would version with a source control system and commit to a shared company repo.  Any changes to your infrastructure should then be handled exclusively through changing these files, instead of by interacting with your cloud provider's GUI or CLI directly.

In order to get started you first have to install the terraform cli.  This can be done by downloading it from the website, but I took the simple way out and used brew.  If you're on a windows machine you can download it using chocolatey as well which is nice.

brew install terraform

After terraform was successfully installed, I had to allow it access to make changes to my Azure cloud subscription.  The quickest way to do this was to use the Azure-Cli.  Similar to terraform this can be installed using brew.

brew install azure-cli

I then authenticated the azure-cli with my account by executing az login and logging in.  Once I had given the azure-cli access to my azure account, I executed the following commands to create a new service principal.

az account show --query "{subscriptionId:id, tenantId:tenantId}"

# I then used the subcriptionId in the following command.

az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<subscription_id>"

This command will return some values which you need to remember, namely the appId and password.

We should be in a position now where we have all the required authentication information available to us to start to configure our environment to interact with our Azure subscription.  There are a couple of ways you can do this.  You can either set specific environment variables, or alternatively include the values in your .tf files directly.  I would advise against storing the values in your terraform files, due to the security implications if you accidentally commit these to a public git repo.

I went ahead and set the following environment variables with the values obtained via the azure-cli previously.

export ARM_SUBSCRIPTION_ID=your_subscription_id
export ARM_CLIENT_ID=your_appId
export ARM_CLIENT_SECRET=your_password
export ARM_TENANT_ID=your_tenant_id

Now it was time to write my first terraform script and see if I could run it against my azure subscription.  I created the following file and entered the following:

provider "azurerm" {
    version = "=2.0.0"
    features {}

resource "azurerm_resource_group" "rg" {
    name = "testResourceGroup"
    location = "westus"

This file says that it's going to use the azurerm provider and is going to create a resource which is of type azurerm_resource_group, which is a Resource Group in Azure.  Each resource can have many different properties defined on it, depending on the type of resource you are trying to create.  For a Resource Group we simply need to give it a name and a location.

I then ran terraform init which scans through my file(s) and downloads the necessary providers, in this case the azurerm provider was all that was required.  If this has worked you should see in bold:

Terraform has been successfully initialized!

At this stage you are ready to deploy your infrastructure as code.  You can either go ahead and do this straight away with terraform apply or if you're a bit more cautious and want to confirm what's going to happen, you can run terraform plan first to get a preview of what the apply command will do.

If everything went to plan you should now be able to browse All Resources in Azure and see your newly created Resource Group. Congratulations!

Check out my github to see a more complex example where I create a publicly accessible virtual machine, complete with external IP address and storage.

It's easy to see why using something like terraform to handle the creation and maintenance of cloud infrastructure is advantageous over having to update documentation describing step by step how to setup an environment.

By describing our infrastructure in code and using something like git to handle version control, we have a simple audit trail of changes and can easily roll our infrastructure back, should problems occur.

Overall I'm impressed with Terraform and how simple it is to get up and running with it and look forward to playing with it more over the course of the weekend.

For a more in depth guide to Terraform and for some further insight into how it compares to Azure ARM templates, I recommend checking out this presentation from my colleague Torstein.