
Introduction
To create a comprehensive Azure DevOps CI/CD pipeline with Terraform, we’ll implement an end-to-end workflow for a sample e-commerce application using advanced patterns. This guide includes troubleshooting for real-world scenarios and optimizations for enterprise environments.
Infrastructure Architecture
Our deployment will provision:
- Azure Kubernetes Service (AKS) cluster with managed identities
- Azure PostgreSQL Flexible Server with VNET integration
- Azure Front Door with Web Application Firewall (WAF)
- Azure Monitor workspace with diagnostic settings
- Key Vault for secret management
Terraform Module Structure:
modules/
├── network
├── database
├── kubernetes
├── monitoring
└── frontdoor
environments/
├── prod
│ ├── main.tf
│ ├── variables.tf
│ └── terraform.tfvars
└── staging
Pipeline Implementation
Multi-Stage YAML Pipeline
# azure-pipelines.yml
variables:
TF_VERSION: 1.8.5
ENVIRONMENT: prod
BACKEND_SA: $(backendStorageAccount)
stages:
- stage: Validate
jobs:
- job: Lint
steps:
- script: terraform fmt -check -recursive
- task: terrascan@1
inputs:
scanType: 'iac'
iacType: 'terraform'
- stage: Plan
dependsOn: Validate
jobs:
- job: TerraformPlan
steps:
- task: TerraformInstaller@0
inputs:
terraformVersion: $(TF_VERSION)
- script: |
terraform init -backend-config="storage_account_name=$(BACKEND_SA)"
terraform plan -out=tfplan
workingDirectory: environments/$(ENVIRONMENT)
- stage: Approve
dependsOn: Plan
jobs:
- job: Approval
pool: server
steps:
- task: ManualValidation@0
timeoutInMinutes: 1440
- stage: Apply
dependsOn: Approve
jobs:
- job: TerraformApply
steps:
- script: terraform apply tfplan
workingDirectory: environments/$(ENVIRONMENT)
- stage: Monitor
jobs:
- job: DeploymentCheck
steps:
- task: AzureCLI@2
inputs:
script: |
az monitor activity-log list \
--resource-group $(resourceGroup) \
--query "[?operationName.value=='Microsoft.Resources/deployments/write']"
Advanced Configuration Patterns
State Management with Azure Storage
# backend.tf
terraform {
backend "azurerm" {
resource_group_name = "tfstate-rg"
storage_account_name = "sttfstate$(unique_string)"
container_name = "tfstate"
key = "prod.terraform.tfstate"
use_oidc = true
}
}
Zero-Downtime Deployment
resource "azurerm_kubernetes_cluster" "aks" {
lifecycle {
create_before_destroy = true
}
automatic_channel_upgrade = "patch"
maintenance_window {
allowed {
day = "Saturday"
hours = [2, 4]
}
}
}
Real-World Troubleshooting Scenarios
Case 1: Authentication Failure During Init Symptom: Error: Failed to get existing workspaces: storage: service returned error Solution:
- Verify Managed Identity has Storage Blob Data Contributor role
- Enable network exceptions for Azure DevOps IP ranges in storage account
- Use OIDC authentication instead of SAS tokens
# Add to backend configuration
use_oidc = true
Case 2: State Locking Conflicts Symptom: Error acquiring state lock Resolution:
terraform force-unlock LOCK_ID -force
az storage blob lease break --container-name tfstate --blob-name prod.tflock
Case 3: Pipeline Dependency Race Conditions Mitigation: Implement pipeline gates and deployment slots
- stage: BlueGreen
jobs:
- deployment: SlotSwap
environment: Production
strategy:
rolling:
preDeploy:
steps:
- script: terraform apply -target azurerm_web_app_slot.prod
deploy:
steps:
- script: az webapp deployment slot swap -g MyResourceGroup -n MyApp --slot staging
Monitoring & Observability
terraform.tf:
module "monitoring" {
source = "Azure/avm-res-insights-monitor/azurerm"
diagnostic_settings = {
"aks-diag" = {
target_resource_id = azurerm_kubernetes_cluster.aks.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
}
}
alert_rules = {
"HighCPU" = {
description = "Average CPU > 80%"
condition = "avg Percentage CPU > 80"
}
}
}
Security Best Practices
- Secret Management:
data "azurerm_key_vault_secret" "db_password" {
name = "postgres-password"
key_vault_id = azurerm_key_vault.main.id
}
resource "azurerm_key_vault_access_policy" "devops" {
key_vault_id = azurerm_key_vault.main.id
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azuread_service_principal.devops.object_id
secret_permissions = ["Get"]
}
- Network Restrictions:
resource "azurerm_storage_account_network_rules" "tfstate" {
default_action = "Deny"
ip_rules = ["52.237.103.0/24"] # Azure DevOps IP range
virtual_network_subnet_ids = [azurerm_subnet.agents.id]
}
FAQ: Common Pipeline Issues
Q: Pipeline fails with “Error: AuthorizationFailure” A:
- Verify Service Principal has Contributor role at resource group level
- Check storage account firewall rules allow Azure DevOps IPs
- Enable managed identity authentication in pipeline settings
Q: Terraform plan shows unexpected changes on every run A:
- Use
ignore_changesfor cloud-generated tags:
lifecycle {
ignore_changes = [tags["CreatedBy"], tags["Timestamp"]]
}
2. Set explicit versions for AzureRM provider
3. Use Terraform Cloud for remote execution consistency
Q: How to manage multiple environments? A: Implement workspace strategy:
terraform workspace new staging
terraform workspace select prod
Combine with Azure DevOps variable groups for environment-specific configurations.
Q: Debugging failed pipeline runs A:
- Enable debug logging:
variables:
SYSTEM_DEBUG: true
2. Use Terraform console for state inspection
3. Implement pipeline artifacts for plan files:
- task: PublishPipelineArtifact@1
inputs:
targetPath: $(System.DefaultWorkingDirectory)/tfplan
artifact: TerraformPlan
Final Recommendations
- Policy Enforcement: Use Azure Policy with Terraform validation
- Cost Control: Integrate Infracost in pipeline gates
- Drift Detection: Schedule daily Terraform plan runs
- Collaboration: Implement Terraform Cloud for team workflows
This implementation provides production-grade patterns while addressing real-world operational challenges. The combination of Azure DevOps pipeline capabilities with Terraform’s infrastructure management creates a robust framework for cloud-native application delivery.
📢 Have questions or feedback? Drop a comment below or connect with me on Twitter/X@spysood!
Originally published on Medium.