Provisioning Azure resources with Terraform - Part 1 - Basics
Intro
- It uses configuration files to describe infrastructure. The configuration is written in declarative language called HCL (Hashicorp Configuration Language).
- Thanks to rich Terraform Registry it supports dozens of providers - this obviously includes Azure along with GCP and AWS.
- In order to create resources in proper order it creates dependency graph between them.
- When deploying infrastructure changes it is saving state of the infrastructure. With this approach in case of change in infrastructure it is able to use the state to calculate which resources have changed and update only those.
As a prerequisite you will need to execute following steps:
- Download Terraform. You can do that using official download site: https://www.terraform.io/downloads.html or Chocolatey (in case you are using this great tool): https://chocolatey.org/packages/terraform.
- Make sure that you have Azure CLI installed and default subscription is set up. If you don't know how to do that refer to my older post about Azure Resource Manager which describes required steps.
Configuring provider
Code for this section is available in GitHub repository: https://github.com/sszarek/provisioning-azure-with-terraform/tree/1-create-resource-group, under 1-create-resource-group tag.
Lets start from creating main.tf file and adding following content:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 2.26"
}
}
}
Code above declares terraform block which is used for setting Terraform configuration. Examples of such configuration are:
- required providers - required_providers block - defines list of providers and their versions required for running the script.
- required Terraform version - required_version block - defines version constraints for Terraform CLI required for running the script.
- backend - backend block - configures backend used for storing Terraform state file. I will touch this topic later in this article.
- source argument defines Terraform registry URL where provider can be located. The format used in the code above consists of namespace (hashicorp) and type (azurerm) which will be used to locate provider in official Terraform registry located in: https://registry.terraform.io/. Terraform also allows to use other registries (e.g. company private registry) - in such scenario full URL to provider has to be provided.
- version argument defines that the script requires at least version 2.26 of the module.
Now that we have defined dependency on Azure provider we need to add one more important block.
provider "azurerm" {
features { }
}
provider block contains configuration of the provider and it is required. features argument is required for Azure provider - it customizes behavior of some Azure resources. Since we do not want to customize anything here, we leave it blank. Full list of arguments available for configuring Azure provider can be found in documentation.
Adding first resource
resource "<resource type>" "<local name>" {
argument1 = "foo"
argument2 = "bar"
}
As seen on snippet above, when creating creating a resource we need to provide:
- Type (<resource type> label) of resource. This refers to resource types defined by providers e.g. Azure provider.
- Local name (<local name> label) which allows to refer to resource within the scope of Terraform script. This should not be confused with the name of "physically" created resource in the cloud.
- Resource configuration arguments.
It should not be surprise that the first resource we will create is Resource Group. Add code below to main.tf file.
resource "azurerm_resource_group" "rg" {
name = "terraform_example_app"
location = "centralus"
}
The code above creates Azure Resource Group in Central US region with name terraform_example_app. Inside script it will referenced as rg.
Now that we have provider configured and first resource defined it is time to deploy that stuff to Azure!
First deployment
- Downloading providers required by the script - since the providers are not built in they have to be downloaded from registry to local directory. The directory will be created in same location where you run the command and will be called .terraform.
- Creating (or update) lock file (.terraform.lock.hcl). The purpose of the lock file is to freeze concrete versions of dependencies - thanks to that you will always get the same version of providers no matter if you will be running from your local machine or some automated deployment.
- Initializing backends - this topic will be covered in one of the following articles in the series.
We are now finally ready to apply changes - this is done with terraform apply command. In its first phase it will display output similar to one from plan command, letting you review changes one more time. After confirming, Terraform will start creating resources and after it completes you will be able to see newly created Resource Group available in Azure Portal.
State file
If you look closer you will notice that after running terraform apply there was new file created - it is terraform.tfstate. The file contains JSON representation of the current state of infrastructure managed by Terraform. It maps resources defined in scripts (identified by resource type and local name) with "physical" resources in the cloud (identified by resource id assigned by cloud provider). Below you will find state file similar to one on your machine - red rectangles mark script local and physical resource identifiers.
Further in this article we will modify already created scripts and add other resources. When we will be deploying that changes Terraform will use information from terraform.tfstate in order to build execution plan and determine which resources will be modified, deleted or added.
You should avoid manual editing of the file as it might yield undesired results.
Variables
Code for this section is available in GitHub repository under 2-parametrize-with-variables tag: https://github.com/sszarek/provisioning-azure-with-terraform/tree/2-parametrize-with-variables.
Terraform comes with the support of variables which allow you to parametrize your scripts. Lets create new variables.tf file.
variable "location" {
default = "centralus"
description = "Azure region where resources will be provisioned"
type = string
}
Block above defines location variable. There are three arguments set here:
- type - defines type of the variable. Here we are using string type. Full list of types can be found in documentation: https://www.terraform.io/docs/language/values/variables.html#type-constraints
- description - provides information for other developers or users of the script on the purpose of the variable.
- default - sets value for variable in case it is not explicitly set.
resource "azurerm_resource_group" "rg" {
name = "terraform_example_app"
location = var.location
}
When I was starting this section I was talking about case when we would like to deploy resources to different region than Central US which is currently used and set as default value for variable. To do so we need to explicitly set its value. We can do that in two ways.
Setting value as argument for CLI
terraform plan -var="location=eastus"
Screenshot below presents output similar to one you should see on your machine.
Since we have provided new region for our Resource Group it needs to be recreated: resource in Central US will be destroyed and new one will be created in East US. It is also worth to take a look at the location argument on the output above: there is information in red next to it saying: "forces replacement" - when working on more complicated scripts you might run into situation where Terraform will be trying to replace some resource and this information is priceless for determining the reason of this.
Using tfvars file
location = "eastus"
Now that the file is created we will run terraform plan with -var-file argument which accepts path to tfvar file.
terraform plan -var-file="development.tfvars"
The result of calling the command should be the same as in previous case when we were providing variable value with -var command line argument.
Applying with variables
- Using -var command line argument where you provide values directly in command line.
- Using -var-file command line argument where you provide name of file which contains values for variables used in scripts.