"Your AKS platform works great in dev. Now the team wants staging and production with the same infrastructure. Copying and pasting .tf files isn't going to scale. Let's learn modules and state management."
Variables: Deep Dive
Variables make your configuration flexible and reusable.
variable"environment" {
description = "Deployment environment"type = stringvalidation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable"node_count" {
description = "Number of AKS nodes"type = numberdefault = 3validation {
condition = var.node_count >= 1 && var.node_count <= 100
error_message = "Node count must be between 1 and 100."
}
}
Variable Types
Primitive Types
variable"name" {
type = string
}
variable"count" {
type = number
}
variable"enabled" {
type = bool
}
Complex Types
variable"tags" {
type = map(string)
}
variable"zones" {
type = list(string)
}
variable"node_pool" {
type = object({
name = stringvm_size = stringcount = number
})
}
$ terraform plan
# azurerm_key_vault_secret.db will be created
+ resource"azurerm_key_vault_secret""db" {
+ name = "db-password"
+ value = (sensitive value)
}
sensitive = true only hides the value from CLI output. The value is still stored in plain text in the state file. Use remote state with encryption.
Setting Variable Values
Method
Precedence
Example
Default value
Lowest
default = "uksouth"
terraform.tfvars
location = "uksouth"
*.auto.tfvars
dev.auto.tfvars
-var-file flag
-var-file="prod.tfvars"
TF_VAR_ env var
export TF_VAR_location="uksouth"
-var flag
Highest
-var="location=uksouth"
Higher precedence overrides lower. Use -var-file for environment-specific values.
Knowledge Check 1
1. Which variable setting method has the highest precedence?
2. What does sensitive = true on a variable do?
3. What happens if a validation block's condition evaluates to false?
Locals: Computed Values
Locals define computed or derived values to reduce repetition.
output"vnet_id" {
description = "ID of the virtual network"value = azurerm_virtual_network.main.id
}
output"vnet_name" {
description = "Name of the virtual network"value = azurerm_virtual_network.main.name
}
output"subnet_ids" {
description = "Map of subnet name to subnet ID"value = { for k, v in azurerm_subnet.main : k => v.id }
}
Organize with folder-like key paths per environment/project
Enable versioning on the container for state recovery
State Locking
Prevents concurrent operations from corrupting state.
How It Works
Azure Blob backend uses blob leases for locking
When running plan/apply, Terraform acquires a lease
Other operations wait or fail
Lock is released when the operation completes
Lock Stuck?
# Force unlock (use with caution!)
$ terraform force-unlock LOCK_ID
# The lock ID is shown in the# error message when a lock# conflict occurs.
State locking is automatic with the Azure Blob backend. No extra configuration needed.
Setting Up the State Backend
Create the storage account first (bootstrap)
# bootstrap.sh - Run this ONCE before terraform init# Create resource group for state
az group create \
--name rg-terraform-state \
--location uksouth
# Create storage account
az storage account create \
--name stterraformstate001 \
--resource-group rg-terraform-state \
--sku Standard_GRS \
--encryption-services blob \
--min-tls-version TLS1_2
# Create container
az storage container create \
--name tfstate \
--account-name stterraformstate001
# Enable versioning for recovery
az storage account blob-service-properties update \
--account-name stterraformstate001 \
--resource-group rg-terraform-state \
--enable-versioning true
Workspaces
Workspaces allow you to use the same configuration with different state files.
# List workspaces
$ terraform workspace list
* default
# Create and switch to a new workspace
$ terraform workspace new dev
Created and switched to workspace "dev"!
$ terraform workspace new staging
$ terraform workspace new prod
# Switch between workspaces
$ terraform workspace select prod
# Reference current workspace in configname = "rg-aks-${terraform.workspace}"
Workspaces: Pros and Cons
Advantages
Simple to set up
Same code, different state
Quick environment switching
Backend stores each workspace's state separately
Limitations
All workspaces share the same backend config
Can't have different provider configs per workspace
Easy to apply to the wrong workspace
Not suitable when environments differ significantly
Many teams prefer directory-based environments (separate folders) over workspaces for production use.
Directory-Based Environments
The recommended approach for significant environment differences
# Apply with environment-specific vars
$ terraform apply -var-file="prod.tfvars"
Knowledge Check 3
1. How does the Azure Blob backend implement state locking?
2. What does terraform.workspace return?
3. Why do many teams prefer directory-based environments over workspaces?
Common State Operations
Command
Purpose
terraform state list
List all resources in state
terraform state show <addr>
Show attributes of one resource
terraform state mv
Move/rename a resource in state
terraform state rm
Remove a resource from state (doesn't delete it)
terraform state pull
Download and display the remote state
terraform state push
Upload local state to remote (dangerous)
terraform state mv is useful for refactoring. If you rename a resource in code, use state mv (or a moved block) so Terraform doesn't destroy and recreate it.
Moved Blocks: Refactoring Safely
Introduced in Terraform 1.1. Declare renames in code instead of manual state commands.
# Rename a resource without destroying itmoved {
from = azurerm_resource_group.main
to = azurerm_resource_group.aks
}
# Move a resource into a modulemoved {
from = azurerm_virtual_network.main
to = module.networking.azurerm_virtual_network.main
}
Moved blocks are processed during terraform plan
Once applied, the moved block can be removed
Safer than terraform state mv - version controlled and reversible
Reading Remote State (Cross-Project)
Access outputs from another Terraform configuration.
# In the AKS project, read networking project's statedata"terraform_remote_state""networking" {
backend = "azurerm"config = {
resource_group_name = "rg-terraform-state"storage_account_name = "stterraformstate001"container_name = "tfstate"key = "networking/prod.terraform.tfstate"
}
}
# Use the outputsvnet_subnet_id = data.terraform_remote_state.networking.outputs.aks_subnet_id
Knowledge Check 4
1. What does terraform state rm do to the actual Azure resource?
2. What is the purpose of a moved block?
3. How does terraform_remote_state data source help in multi-project setups?