Getting started with Terraform on Azure

Wanting to get started using Terraform in Azure? This blog post is for you, it will contain all that you will require for getting started with Terraform on Azure!

What will the blog post contain?

  • Intro to Terraform
  • Terraform Stages
  • Common Terraform commands
  • Terraform directory setup
  • Deploying your Azure first resources via Terraform
  • Storing your terraform state file in a remote location (Azure Storage Account)

Intro to Terraform

Terraform is a very common IaC (Infrastructure as code) toolset; itself is cloud agnostic and has providers for a number of cloud providers including Microsoft Azure

Why IaC?

Check out my blog post on this to find out Why Infrastructure as Code (IaC) ?

Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.

https://www.terraform.io/intro/index.html

Terraform Stages

Terraform has four main stages throughout the development and deployment of Terraform

  • Initialisation: The start of deploying any Terraform will begin with the initialisation stage
  • Planning: You can create and view a Terraform plan prior to creating any Azure resources using Terraform
  • Applying: Once the plan has been reviewed, you can run the apply stage to deploy the required Azure resources
  • Destroying: Not used all the time; but you can run the destroy stage to remove any Azure related resources/changes

Initialisation -> Planning -> Applying -> Destroying

Common Terraform commands

We’ve covered the Terraform stages – lets now look at the commands used to run these stages

  • terraform init – Used to initialise the Terraform directory
  • terraform plan – Shows the Terraform plan
  • terraform apply – Applies the Terraform code
  • terraform destroy – Destroys resources deployed via Terraform

Terraform Directory Setup

Terraform directories can be configured in multiple ways – depending on the project, customer, team etc! I will cover a standard terraform directory setup

terraform_folder
    └──main.tf
    └──variables.tf
    └──locals.tf
    └──providers.tf
    └──develop.tfvars

The folder setup above will be used in this blog post, lets have a closer look at each file

  • main.tf – The main Azure resources to deploy
  • variables.tf – Containing variables that will be used in main.tf
  • providers.tf – Mentioned at the start of the blog post, multiple Terraform providers can be used to deploy various Azure resources
  • locals.tf – A local value; usually a common value that may never change between deployments
  • develop.tfvars – .tfvars are great, they are used to define variables between different Terraform environments

Deploying your first Azure resources using Terraform

You would write Terraform; as you would write any sort of configuration language code, in an editor of your choice while storing any changes in a version controlled respository, whether it is local or remote!

I highly recommend you store your configuration code in a remote repository!

So… as you are making progress on your Terraform configuration; you would be running several Terraform Plans to confirm your syntax is correct and help to iron-out any errors in relation to syntax or misconfiguration(s) that you are noticing. Doing this, will ensure that your Terraform configuration is coming together as expected.

The plan is looking good? Time to commit that change! Once you commit to the required change, you run Terraform Apply which will add/remove or change infrastructure that you have defined within your Terraform configuration.

This core workflow is a continuous loop throughout any project; the next change or additional/removal you want to make – you will follow the exact same process

Terraform Folder breakdown

main.tf

The Azure resources that will be deployed

  • Resource Group
  • Storage Account
data "azurerm_client_config" "current" {}

terraform {
    backend "local" {
    }
}

# Create Resource Group
resource "azurerm_resource_group" "tamops" {
  name     = var.resource_group_name
  location = var.location
}

# Create Storage Account
resource "azurerm_storage_account" "example" {
  name                     = var.storage_account_name
  resource_group_name      = azurerm_resource_group.tamops.name
  location                 = azurerm_resource_group.tamops.location
  account_tier             = "Standard"
  account_replication_type = "LRS"

  tags = {
    environment = local.environment
  }
}

The terraform backend for now will be local – terraform will store its terraform.state file locally

Notice the reference to var? This will be looking for variables – you will see the variables declared in variables.tf

Similar to this, reference to local – local values will be declared in local.tf

local.tf

locals {
    environment = "development"
}

variables.tf

variable "location" {
  type        = string
  default     = "UK South"
  description = "default resources location"
}

variable "resource_group_name" {
  type        = string
  description = "resource group name"
}

variable "storage_account_name" {
  type        = string
  description = "storage account name"
}

providers.tf

Calling/declaring the azurerm provider

provider "azurerm" {
    # The "feature" block is required for AzureRM provider 2.x.
    # If you're using version 1.x, the "features" block is not allowed.
    version = "~>2.0"
    features {}
}

develop.tfvars

.tfvars for the “develop” environment, in this file it will contain the resource group & storage account name

resource_group_name = "tamopstfrg"
storage_account_name = "tamopstfsa"

The files are all created, ready to deploy some Terraform!

Deploying your first Azure Resources using Terraform

Prior to running terraform, you are required to log into the specific Azure subscription using Az CLI

# Run az login & enter your Azure credentials
az login

# Select your Azure Subscription
az account set -s "thomasthorntoncloud"

Once you have logged in & configured subscription above – init Terraform within the folder

thomasthorntoncloud@Thomass-MBP terraform % terraform init

Initializing the backend...

Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding hashicorp/azurerm versions matching "~> 2.0"...
- Installing hashicorp/azurerm v2.62.0...
- Installed hashicorp/azurerm v2.62.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.

Various files & folders relating to Terraform initialising are created as part of this

With Terraform now initialised – run a terraform plan, notice the reference to -var-file=develop.tfvars – this references the var-file that will be used for develop.tfvars

thomasthorntoncloud@Thomass-MBP terraform % terraform plan -var-file=develop.tfvars

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.tamops will be created
  + resource "azurerm_resource_group" "tamops" {
      + id       = (known after apply)
      + location = "uksouth"
      + name     = "tamopstfrg"
    }

  # azurerm_storage_account.example will be created
  + resource "azurerm_storage_account" "example" {
      + access_tier                      = (known after apply)
      + account_kind                     = "StorageV2"
      + account_replication_type         = "LRS"
      + account_tier                     = "Standard"
      + allow_blob_public_access         = false
      + enable_https_traffic_only        = true
      + id                               = (known after apply)
      + is_hns_enabled                   = false
      + large_file_share_enabled         = (known after apply)
      + location                         = "uksouth"
      + min_tls_version                  = "TLS1_0"
      + name                             = "tamopstfsa"
      + nfsv3_enabled                    = false
      + primary_access_key               = (sensitive value)
      + primary_blob_connection_string   = (sensitive value)
      + primary_blob_endpoint            = (known after apply)
      + primary_blob_host                = (known after apply)
      + primary_connection_string        = (sensitive value)
      + primary_dfs_endpoint             = (known after apply)
      + primary_dfs_host                 = (known after apply)
      + primary_file_endpoint            = (known after apply)
      + primary_file_host                = (known after apply)
      + primary_location                 = (known after apply)
      + primary_queue_endpoint           = (known after apply)
      + primary_queue_host               = (known after apply)
      + primary_table_endpoint           = (known after apply)
      + primary_table_host               = (known after apply)
      + primary_web_endpoint             = (known after apply)
      + primary_web_host                 = (known after apply)
      + resource_group_name              = "tamopstfrg"
      + secondary_access_key             = (sensitive value)
      + secondary_blob_connection_string = (sensitive value)
<<<<<< Removed part of storage account due to size of output >>>>>>>

Plan: 2 to add, 0 to change, 0 to destroy.

Reviewing the plan, we can see 2 resources will be added with 0 change or destroy

Time to apply!

thomasthorntoncloud@Thomass-MBP terraform % terraform apply -var-file=develop.tfvars

azurerm_resource_group.tamops: Creating...
azurerm_resource_group.tamops: Creation complete after 1s [id=/subscriptions/XXXXXXX/resourceGroups/tamopstfrg]
azurerm_storage_account.example: Creating...
azurerm_storage_account.example: Still creating... [10s elapsed]
azurerm_storage_account.example: Still creating... [20s elapsed]
azurerm_storage_account.example: Still creating... [30s elapsed]
azurerm_storage_account.example: Still creating... [40s elapsed]
azurerm_storage_account.example: Still creating... [50s elapsed]
azurerm_storage_account.example: Creation complete after 50s [id=/subscriptions/XXXXXXX/resourceGroups/tamopstfrg/providers/Microsoft.Storage/storageAccounts/tamopstfsa]

Resource group & storage account now deployed!

We can now destroy these two resources by terraform destroy

thomasthorntoncloud@Thomass-MBP terraform % terraform destroy -var-file=develop.tfvars

azurerm_storage_account.example: Destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg/providers/Microsoft.Storage/storageAccounts/tamopstfsa]
azurerm_storage_account.example: Destruction complete after 5s
azurerm_resource_group.tamops: Destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 10s elapsed]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 20s elapsed]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 30s elapsed]
azurerm_resource_group.tamops: Still destroying... [id=/subscriptions/04109105-f3ca-44ac-a3a7-66b4936112c3/resourceGroups/tamopstfrg, 40s elapsed]
azurerm_resource_group.tamops: Destruction complete after 45s

We have now ran through the 4 common Terraform commands

  • terraform init
  • terraform plan
  • terraform apply
  • terraform destroy

Throughout these 4 commands, you may have noticed two files also created within your terraform directory

terraform.tfstate & terraform.tfstate.backup, these are related to the current terraform state, this is what Terraform compares against when you run plan, apply or destroy! Lets now look at storing this file remotely

Storing your terraform state file in a remote location (Azure Storage Account)

Create an Azure Storage Account & blob container (script here)

#!/bin/sh

# Create Resource Group
az group create -l uksouth -n thomasthornton-tfstate

# Create Storage Account
az storage account create -n thomasthorntontfstate -g thomasthornton-tfstate -l uksouth --sku Standard_LRS

# Create Storage Account blob
az storage container create  --name tfstate --account-name thomasthorntontfstate

With a container created, we can now look at saving the remote state locally.

Update terraform backend to the below

terraform {
    backend "azurerm" {
        resource_group_name = "thomasthornton-tfstate"    
        storage_account_name = "thomasthorntontfstate"
        container_name = "tfstate"
    }
}

Now run terraform init with new property -migrate-state (you may be asked for storage account access key during this step)

thomasthorntoncloud@Thomass-MBP terraform % terraform init -migrate-state

Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.



Successfully configured the backend "azurerm"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/azurerm from the dependency lock file
- Using previously-installed hashicorp/azurerm v2.62.0

Checking the storage account, we can see the new terraform state file

Finishing up..

A lengthy blog post, but I hope you now have a firm understanding of “Getting started with Terraform on Azure”

Feel free to check out my other blog posts! Why not check out Deploy Terraform using Azure DevOps

Full search all references to Terraform on my blog

GitHub Repository for code in this blog post

1 comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s