Tag Archives: Terraform

Conditionally Creating Resources in Terraform

I’ve recently been learning and blogging about Terraform (the latest of which you can find here).

In this post, I’m going to cover the conditional creation of a resource, using the count variable.

Disclaimer

As with most of the stuff that finds its way into my blog, this is from finer minds than my own. It’s also worth noting, as with all the stuff that finds its way here, the main audience is future me.

Count

All of the resources in Terraform have an inbuild property of count – what that means is that you can do something like this:

resource "azurerm_resource_group" "rg" {
  count = 2
  name     = "myTFResourceGroup${count.index}"
  location = "westus2"
}

This will create two resource groups, called myTFResourceGroup0 and myTFResourceGroup1. Admittedly, this has limited uses; however, it becomes much more useful when you use something like this:

resource "azurerm_resource_group" "rg" {
  count                      = var.myvar == "some_setting" ? 1 : 0 

So now, using the variables associated with the configuration that you run, you can either create the resource, or not (a count of 0 meaning that it will not be created).

Error

So that’s all well and good, but let’s imagine that you’re using this resource; for example:

resource "azurerm_app_service_plan" "app-service-plan" {
  name                = "pcm-app-service-plan"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  sku {
    tier = "Standard"
    size = "S1"
  }
}

(See this post for an explanation of this)

If you’re using the resource group, you’ll start to get an error:

Because azurerm_resource_group.rg has “count” set, its attributes must be
accessed on specific instances.

What this is basically telling you is that, because you’ve specified a count, you now have to tell it which version of the resource you’re referring to. In our case, we know that if the resource group exists at all, there will only be one; so we can use this:

resource "azurerm_app_service_plan" "app-service-plan" {
  name                = "pcm-app-service-plan"
  location            = azurerm_resource_group.rg[0].location
  resource_group_name = azurerm_resource_group.rg[0].name
  sku {
    tier = "Standard"
    size = "S1"
  }
}

However, there’s still a problem here. Let’s imagine that our myvar setting is not set to “some_setting” – well in that case, the resource group will not create; however, the app service plan will, because no such check exists. The upshot of this is that you’ll need to ensure that anything that uses a resource that has a count, must itself, have a count (and on the same logic).

Terraform – Autoscale an App Service

I’ve recently been writing about Terraform – mainly because I’m learning it from scratch, and playing about with tech and then writing about it is basically my way of learning.

In this post, I’m going to build on this previous post on creating an App Service, by adding a Scale Out feature to it.

This is the App Service that we created in the referenced post:

In the image, you’ll see Scale Out. Note that it says (App Service Plan): in fact, this is just a link to the App Service Plan Scale Out. We can access it from here – let’s see what that looks like:

As we can see, there’s a single instance of the App Service, and it’s managed manually. What we’re going to do is change that so that the App Service is auto-scaled.

The Terraform script here is broadly taken from the example here. However, that applies to a VM Scale Set, whereas we’re applying it to an App Service Plan.

resource "azurerm_monitor_autoscale_setting" "example" {
  name                = "myAutoscaleSetting"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  target_resource_id  = azurerm_app_service_plan.app-service-plan.id
  profile {
    name = "default"
    capacity {
      default = 1
      minimum = 1
      maximum = 10
    }
    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = azurerm_app_service_plan.app-service-plan.id
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "GreaterThan"
        threshold          = 90
      }
      scale_action {
        direction = "Increase"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }
    rule {
      metric_trigger {
        metric_name        = "CpuPercentage"
        metric_resource_id = azurerm_app_service_plan.app-service-plan.id
        time_grain         = "PT1M"
        statistic          = "Average"
        time_window        = "PT5M"
        time_aggregation   = "Average"
        operator           = "LessThan"
        threshold          = 10
      }
      scale_action {
        direction = "Decrease"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT1M"
      }
    }
  }  
}

Some key points:

– The example uses “Percentage CPU”, whereas for an App Service, this gets switched to CpuPercentage.
– The resource IDs that are referred to are that of the App Service Plan.

Finally, if we apply that, we can see the autoscale:

References

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_autoscale_setting

https://stackoverflow.com/questions/58657096/error-creating-auto-scaling-rule-for-app-service-using-terraform-in-azure

Terraform – Provisioning an Azure App Service

In my previous post on Getting started with Terraform I covered a very quick, and vague explanation of what Terraform is, and what it does. In this post, I’m going to cover the explanation of what the various syntax looks like; I’m also going to provision some infrastructure in the form of an App Service.

Before we get into the what we’ll need to create an app service, let’s first analyse the config that we used in the previous post:

# Configure the Azure provider
terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = "myTFResourceGroup"
  location = "ukwest"
}

Let’s breakdown exactly what we’re seeing here for the resource:

Now that we’ve broken that down, it makes sense that, if we want to deploy an App Service, that we simply need to know what the correct type of the app service is. There’s probably a list of these somewhere.

Let’s have a look at the config for the App Service:

# App Service
resource "azurerm_app_service_plan" "app-service-plan" {
  name                = "pcm-app-service-plan"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  sku {
    tier = "Standard"
    size = "S1"
  }
}
resource "azurerm_app_service" "app-service" {
  name                = "pcm-app-service"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  app_service_plan_id = azurerm_app_service_plan.app-service-plan.id
}

Again, let’s break this down – starting with the plan:

Finally, let’s have a look at the app service itself – there’s not too much difference here:

If we now run

terraform.exe plan

Then we’ll see that it intends to create an app service plan and app service within that plan; running:

terraform.exe apply

Will execute that and generate our new resources.

Terraform – Getting Started

In this post, I’m going to download Terraform and create a resource group, and then change the location of that resource group. Most of what is in this post is much more succinctly put in the link to the Terraform tutorial at the end. Feel free to jump to the end and go there (I won’t be offended).

Download

Terraform is an application that runs locally, however, the install procedure is essentially just download an exe and place it in a directory. The download link is here. For my test, I downloaded the 64 bit Windows exe, and just extracted it to c:\tmp.

If you now run the Windows Terminal, you can run the terraform.exe and you’ll see a list of possible commands:

Since this is just an experiment, I won’t edit the path (perhaps we’ll come back to that in a later post), but if you are installing this, then it makes sense to have it in your Windows PATH – that way you can run it from anywhere.

To set-up terraform, the first step is to run:

.\terraform.exe init

If you do that now, you’ll get an error:

The directory has no Terraform configuration files. You may begin working
with Terraform immediately by creating Terraform configuration files.

Configuration Files

Configuration files essentially tell Terraform what you want it to do. At a very basic level, they have two sections:

Provider Block – where are we creating infrastructure

Resource Block – what are we creating

There are other features and sections, but getting down to basics, we need to say what we are creating, and where to create it.

The sample configuration from the tutorial referenced at the bottom of this post is this:

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
    }
  }
}

# Provider - creating Infrastructure in Azure
provider "azurerm" {
  features {}
}

# Resource - creating a resource group
resource "azurerm_resource_group" "rg" {
  name     = "myTFResourceGroup"
  location = "westus2"
}

Now that we’ve created that, we can run init again; this time, it should work:

.\terraform.exe init

Plan

Let’s run this. Step one is to log-in to Azure – to do this, you’ll need the Azure CLI:

az login

Once you’re logged in to Azure, create the terraform plan:

.\terraform.exe plan

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:

+ resource “azurerm_resource_group” “rg” {
+ id = (known after apply)
+ location = “westus2”
+ name = “myTFResourceGroup”
}

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

That all looks good – so let’s apply that:

.\terraform.exe apply

This takes the plan, and executes it. Checking in the Azure Portal, I can see that I now have a resource group called myTFResourceGroup:

Okay, so this is where the tutorial took me to.

Change of Plan

I happen to live in the UK; so the first thing I want to try is to change the location. I actually thought that terraform would balk at this; but no. Here’s the modified .tf file:

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = ">= 2.26"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "rg" {
  name     = "myTFResourceGroup"
  location = "ukwest"
}

When I run this, it actually comes up with a way to get from where it is to where I want it to be:

PS C:\tmp> .\terraform.exe plan
azurerm_resource_group.rg: Refreshing state… [id=/subscriptions/16a7fc79-9bea-4d04-83a1-09f3b260adb0/resourceGroups/myTFResourceGroup]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement

Terraform will perform the following actions:

# azurerm_resource_group.rg must be replaced
-/+ resource “azurerm_resource_group” “rg” {
~ id = “/subscriptions/16a7fc79-9bea-4d04-83a1-09f3b260adb0/resourceGroups/myTFResourceGroup” -> (known after apply)
~ location = “westus2” -> “ukwest” # forces replacement
name = “myTFResourceGroup”
– tags = {} -> null
}

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

Okay, so what it’s telling me is that it’s going to trash my resource group, and recreate it in the correct location. That seems fine, so I’ll apply that change.

Pulling The Rug

Next, I wanted to see what would happen if I just removed the resource group from the portal. My guess was that Terraform should realise what happened and simply recreate it.

And, sure enough, that’s exactly what it did:

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.rg will be created
+ resource “azurerm_resource_group” “rg” {
+ id = (known after apply)
+ location = “ukwest”
+ name = “myTFResourceGroup”
}

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

References

https://learn.hashicorp.com/tutorials/terraform/azure-build?in=terraform/azure-get-started