Terraform Deploy via Azure DevOps

Posted by
In this blog post, we’ll walk through an Azure DevOps pipeline designed to deploy Terraform configurations. This pipeline has two primary steps:
  1. Running the Terraform Plan to identify changes.
  2. Executing the Terraform Apply to deploy the changes.
Additionally, the pipeline ensures a manual approval process via the Kubernetes environment in Azure DevOps, where admins are notified to review and approve the deployment before proceeding. Let’s dive into the details.

Pipeline Overview

The pipeline is triggered by updates to the main branch and allows the flexibility to target different AKS clusters (cluster-dev, cluster-qa, cluster-prod). It uses an Ubuntu agent to execute the Terraform commands.

Step 1: Terraform Plan

The first stage of the pipeline, Terraform_Plan, runs the Terraform plan command to identify changes in the infrastructure.

Code:

trigger:
- main
variables:
  DEFAULT_AKS_CLUSTER: 'cluster-dev'
parameters:
  - name: aksCluster
    type: string
    default: cluster-dev
    values:
      - cluster-qa
      - cluster-dev
      - cluster-prod
    displayName: 'Select AKS Cluster'
pool:
  vmImage: ubuntu-latest
stages:
- stage: Terraform_Plan
  displayName: "Plan: ${{ parameters.aksCluster }}"
  jobs:
  - job: Plan
    displayName: "Run Terraform Plan"
    steps:
    - script: |
        terraform --version
      displayName: Verify Terraform Installation
    - script: |
        echo "az login"
        az login --service-principal -u $ARM_CLIENT_ID -p $(ARM_CLIENT_SECRET) --tenant $ARM_TENANT_ID
        echo "az account show"
        az account show
      displayName: 'Azuze login'
    - script: |
        cd terraform
        echo "Selecting workspace for ${{ parameters.aksCluster }}..."
        ARM_CLIENT_SECRET=$(ARM_CLIENT_SECRET) terraform init
        echo "terraform select workspace"
        ARM_CLIENT_SECRET=$(ARM_CLIENT_SECRET) terraform workspace select ${{ parameters.aksCluster }}
        echo "Running terraform plan for cluster ${{ parameters.aksCluster }}..."
        ARM_CLIENT_SECRET=$(ARM_CLIENT_SECRET) terraform plan -var-file=environments/${{ parameters.aksCluster }}/terraform.tfvars -input=false -no-color -detailed-exitcode
        retVal=$?
        if [ $retVal -eq 2 ]; then
          echo "Terraform plan changes detected"
          echo "##vso[task.setvariable variable=ChangeDetected;isOutput=true]true"
        fi
      displayName: 'Terraform plan'
    - script: |
        echo "Logging out of Azure..."
        az logout
      displayName: 'Azure Logout'

Explanation:

  1. Verify Terraform Installation: Ensures the Terraform CLI is available.
  2. Azure Login: Authenticates to Azure using a service principal.
  3. Terraform Plan: Initializes Terraform, selects the appropriate workspace, and generates a plan based on the terraform.tfvars file for the selected cluster. If changes are detected, it sets a variable (ChangeDetected) for the next stage.
  4. Azure Logout: Cleans up by logging out of Azure.
The result of this stage determines if there are infrastructure changes requiring deployment.

Step 2: Terraform Apply

If changes are detected during the plan stage, the Terraform_Apply stage deploys the updates. This stage uses the Kubernetes environment in Azure DevOps, which requires manual approval by an admin.

Code:

- stage: Terraform_Apply
  displayName: "Apply: ${{ parameters.aksCluster }}"
  dependsOn: Terraform_Plan
  condition: eq(dependencies.Terraform_Plan.outputs['Plan.CheckPlan.ChangeDetected'], 'true')
  jobs:
  - deployment: ApplyJob
    displayName: "Run Terraform Apply"
    environment: kubernetes  # Requires manual approval configured in the Azure DevOps UI
    strategy:
      runOnce:
        deploy:
          steps:
          - checkout: self
          - script: |
              echo "az login"
              az login --service-principal -u $ARM_CLIENT_ID -p $(ARM_CLIENT_SECRET) --tenant $ARM_TENANT_ID
              echo "az account show"
              az account show
            displayName: 'Azuze login'
          - script: |
              echo "az login"
              az login --service-principal -u $ARM_CLIENT_ID -p $(ARM_CLIENT_SECRET) --tenant $ARM_TENANT_ID
              echo "az account show"
              az account show
            displayName: 'Azuze login'
          - script: |
              cd terraform
              echo "Selecting workspace for ${{ parameters.aksCluster }}..."
              ARM_CLIENT_SECRET=$(ARM_CLIENT_SECRET) terraform init
              ARM_CLIENT_SECRET=$(ARM_CLIENT_SECRET) terraform workspace select ${{ parameters.aksCluster }}
              echo "Running terraform apply for cluster ${{ parameters.aksCluster }}..."
              ARM_CLIENT_SECRET=$(ARM_CLIENT_SECRET) terraform apply -auto-approve -var-file=environments/${{ parameters.aksCluster }}/terraform.tfvars
            displayName: 'Terraform Apply'
          - script: |
              echo "Logging out of Azure..."
              az logout
            displayName: 'Azure Logout'

Explanation:

  1. Precondition: This stage runs only if changes were detected in the previous stage.
  2. Manual Approval: The Kubernetes environment triggers an email notification to admins, requiring them to approve the deployment.
  3. Terraform Apply: Once approved, the pipeline logs into Azure, initializes Terraform, selects the appropriate workspace, and applies the changes.
  4. Azure Logout: Logs out of Azure to ensure secure session management.
 

How Manual Approval Works

The manual approval is configured in the Azure DevOps UI for the kubernetes environment. When the pipeline reaches the Terraform_Apply stage, it pauses and sends an email notification to the designated admins. Only after an admin approves the deployment does the pipeline proceed with applying the Terraform changes. This feature ensures that sensitive infrastructure updates are reviewed and authorized before execution, adding an extra layer of control and security.

Conclusion

This Azure DevOps pipeline provides an efficient and secure way to manage Terraform deployments. By dividing the process into plan and apply stages and incorporating a manual approval step, it ensures that all infrastructure changes are reviewed and implemented with minimal risk. Whether you’re deploying to a development, QA, or production environment, this approach offers the flexibility and control needed for robust infrastructure management.