Civica Training - Terraform on Azure

Introduction to Terraform

Infrastructure as Code for your AKS Platform

Module 1 of 5 Beginner

The Scenario

"You've been asked to provision all infrastructure for your team's AKS platform. Resource groups, networking, clusters, monitoring, container registries... all of it. Let's learn Infrastructure as Code with Terraform."

The Manual Provisioning Problem

The Azure Portal Way

  • Click through dozens of screens
  • Remember every setting by heart
  • Hope your colleague clicks the same options
  • No audit trail of what changed

The Consequences

  • Configuration drift between environments
  • "Snowflake" servers nobody dares touch
  • Disaster recovery takes days
  • No code review for infrastructure

What is Infrastructure as Code?

Managing and provisioning infrastructure through machine-readable definition files rather than manual processes.

Key Principles

  • Declarative definitions
  • Version controlled
  • Idempotent operations
  • Self-documenting

Benefits

  • Repeatable deployments
  • Peer-reviewed changes
  • Automated provisioning
  • Consistent environments

What is Terraform?

Terraform vs. Alternatives

ToolLanguageMulti-CloudStateApproach
TerraformHCLYesState fileDeclarative
ARM TemplatesJSONAzure onlyAzure tracksDeclarative
BicepBicep DSLAzure onlyAzure tracksDeclarative
PulumiPython/TS/Go/C#YesState fileImperative
CrossplaneYAML (K8s CRDs)YesK8s etcdDeclarative

Why Terraform for Azure?

Knowledge Check 1

1. What language does Terraform use for configuration files?

2. What is the main problem with manual infrastructure provisioning?

3. Which of these tools is Azure-only?

The Core Terraform Workflow

1. Write

Define your infrastructure in .tf files

2. Init

Download providers & set up backend

3. Plan

Preview what will change

4. Apply

Create/update real resources

terraform init

Initializes the working directory. Downloads providers, sets up backend, installs modules.

$ terraform init Initializing the backend... Initializing provider plugins... - Finding hashicorp/azurerm versions matching "~> 3.0"... - Installing hashicorp/azurerm v3.85.0... Terraform has been successfully initialized!

Run this once when starting, and again when providers or backend change.

terraform plan

Creates an execution plan showing what Terraform will do, without making any changes.

$ terraform plan # azurerm_resource_group.main will be created + resource "azurerm_resource_group" "main" { + name = "rg-aks-platform" + location = "uksouth" } Plan: 1 to add, 0 to change, 0 to destroy.

terraform apply

Executes the plan. Creates, updates, or destroys real infrastructure.

$ terraform apply # Shows the plan, then prompts: Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes azurerm_resource_group.main: Creating... azurerm_resource_group.main: Creation complete after 2s Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

terraform destroy

Removes all resources managed by the current configuration.

$ terraform destroy # azurerm_resource_group.main will be destroyed - resource "azurerm_resource_group" "main" { - name = "rg-aks-platform" - location = "uksouth" } Plan: 0 to add, 0 to change, 1 to destroy.
Use with extreme caution in production. Always review the plan first.

HCL Basics: Resources

Resources are the most important element. They describe a single infrastructure object.

resource "azurerm_resource_group" "main" { name = "rg-aks-platform" location = "uksouth" tags = { Environment = "Development" ManagedBy = "Terraform" } }

HCL Basics: Variables

Declaring Variables

variable "location" { description = "Azure region" type = string default = "uksouth" } variable "environment" { description = "Environment name" type = string }

Using Variables

resource "azurerm_resource_group" "main" { name = "rg-${var.environment}" location = var.location }

Set values via:

  • -var="env=dev"
  • terraform.tfvars file
  • TF_VAR_environment env var

HCL Basics: Outputs

Outputs expose values from your configuration - useful for passing data between modules or displaying results.

output "resource_group_id" { description = "The ID of the resource group" value = azurerm_resource_group.main.id } output "resource_group_name" { description = "The name of the resource group" value = azurerm_resource_group.main.name }
$ terraform output resource_group_id "/subscriptions/.../resourceGroups/rg-aks-platform"

Knowledge Check 2

1. What does terraform plan do?

2. In the resource block resource "azurerm_resource_group" "main", what is "main"?

3. How do you reference a variable named "location" in HCL?

Provider Configuration: azurerm

The provider block configures the Azure Resource Manager provider.

terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.0" } } } provider "azurerm" { features {} }

Azure Authentication Methods

Azure CLI (dev)

$ az login # Terraform uses the CLI session provider "azurerm" { features {} }

Service Principal (CI/CD)

provider "azurerm" { features {} client_id = var.client_id client_secret = var.client_secret tenant_id = var.tenant_id subscription_id = var.sub_id }

For CI/CD, prefer environment variables (ARM_CLIENT_ID, etc.) over hardcoding credentials.

Terraform State

The state file (terraform.tfstate) is Terraform's record of what it manages.

What State Tracks

  • Resource IDs in Azure
  • Attribute values (names, IPs, etc.)
  • Dependencies between resources
  • Metadata for performance

Why It Matters

  • Maps config to real-world resources
  • Detects drift from desired state
  • Determines what to create/update/destroy
  • Must be shared in team environments

State File: Handle with Care

Critical Rules

1. Never commit terraform.tfstate to Git - it may contain secrets
2. Always use remote state for team projects (Azure Blob Storage)
3. Enable state locking to prevent concurrent modifications
4. Backup state before dangerous operations

# .gitignore *.tfstate *.tfstate.* .terraform/ *.tfvars # may contain secrets

Your First Azure Resource

Let's create a resource group and storage account

# main.tf - Provider configuration terraform { required_version = ">= 1.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.0" } } } provider "azurerm" { features {} }

Your First Azure Resource

Step 1: Resource Group

# main.tf - Resource Group resource "azurerm_resource_group" "main" { name = "rg-terraform-demo" location = "uksouth" tags = { Environment = "Demo" ManagedBy = "Terraform" } }

Every Azure resource lives in a resource group. This is always your starting point.

Your First Azure Resource

Step 2: Storage Account

resource "azurerm_storage_account" "main" { name = "stterraformdemo001" resource_group_name = azurerm_resource_group.main.name location = azurerm_resource_group.main.location account_tier = "Standard" account_replication_type = "LRS" tags = { Environment = "Demo" } }

Notice how we reference the resource group - Terraform automatically understands the dependency.

Your First Azure Resource

Step 3: Outputs

output "resource_group_id" { value = azurerm_resource_group.main.id } output "storage_account_name" { value = azurerm_storage_account.main.name } output "storage_primary_endpoint" { value = azurerm_storage_account.main.primary_blob_endpoint }

Your First Azure Resource

Step 4: Run It!

# 1. Initialize $ terraform init # 2. Preview $ terraform plan Plan: 2 to add, 0 to change, 0 to destroy. # 3. Apply $ terraform apply Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: resource_group_id = "/subscriptions/.../rg-terraform-demo" storage_account_name = "stterraformdemo001" storage_primary_endpoint = "https://stterraformdemo001.blob.core.windows.net/" # 4. Clean up $ terraform destroy

Recommended File Structure

project/ |- main.tf # Resources |- variables.tf # Variable declarations |- outputs.tf # Output declarations |- providers.tf # Provider config |- terraform.tfvars # Variable values |- .gitignore # Ignore state/secrets |- .terraform.lock.hcl # Lock file (commit this!)
  • Terraform loads all .tf files in a directory
  • File names are conventions, not requirements
  • Splitting files improves readability
  • Always commit .terraform.lock.hcl
  • Never commit .tfstate or .tfvars with secrets

Version Constraints

terraform { required_version = ">= 1.5.0" # Terraform CLI version required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 3.75" # >= 3.75, < 4.0 } } }
OperatorMeaningExample
=Exact version= 3.75.0
>=Minimum version>= 3.0
~>Pessimistic (rightmost increments only)~> 3.75 means >= 3.75, < 4.0
>= , <Range>= 3.0, < 4.0

Essential Terraform Commands

CommandPurpose
terraform initInitialize working directory
terraform planPreview changes
terraform applyApply changes
terraform destroyDestroy all resources
terraform fmtFormat code to standard style
terraform validateCheck configuration syntax
terraform outputShow output values
terraform state listList resources in state
terraform state show <addr>Show a resource's attributes

Knowledge Check 3

1. What file should you NEVER commit to Git?

2. What does the ~> 3.0 version constraint mean?

3. When referencing a storage account's resource group, you write azurerm_resource_group.main.name. Terraform uses this to:

Module 1 Summary

Key Concepts

  • IaC solves manual provisioning problems
  • Terraform uses HCL and providers
  • Core workflow: init, plan, apply, destroy
  • State maps config to real resources

What's Next

  • Module 2: Azure Fundamentals with Terraform
  • VNets, NSGs, Storage, Key Vault
  • Data sources and dependencies
  • Building the foundation for AKS
Module 1 Complete
← Back