In my previous blog post; you may have saw how you can deploy Azure Kubernetes Service (AKS) with Application Gateway Ingress using Terraform. In this blog post I am going to show how you can deploy the same Terraform code in Azure DevOps with an Azure DevOps pipeline and deploy sample application
I have modified the Terraform code into Terraform modules, a blog post I created a while back “Creating reusable Terraform with Terraform modules” details why you should create Terraform modules. Also with this; another I recommend you read, Creating templates in Azure DevOps Pipelines as the Azure DevOps Pipeline I use will be templated
Deploy using Azure DevOps
The Azure DevOps pipeline as mentioned above, will reference various templates. it is split into 5 stages with 3 potential actions
Actions
The actions are configured as to how you would deploy terraform with plan, apply & destroy
Stages
- terraform validate:- confirms the terraform that will be plan/applied/destroyed is valid format
- terraform plan:- Returns a plan of the terraform
- terraform apply:- Applies the terraform configuration
- bootstrap:- Once the AKS cluster is built, deploy sample application
- terraform destroy:- Destroys terraform which has been referenced in the .tfstate stored in the Azure Storage Account
Azure DevOps Pipeline Breakdown
Currently no triggers are set or any stages to run during a PR
Parameters setup are the Terraform commands: Plan, Apply or Destroy which show as below within the Azure DevOps Pipeline UI

Highlighted is the variables that can be changed to match additional Terraform setups
name: $(BuildDefinitionName)_$(date:yyyyMMdd)$(rev:.r)
trigger: none
pr: none
parameters:
- name: Action
displayName: Action
type: string
default: 'Plan'
values:
- Plan
- Apply
- Destroy
variables:
- name: backendServiceArm
value: 'thomasthorntoncloud'
- name: backendAzureRmResourceGroupName
value: 'thomasthorntoncloud'
- name: backendAzureRmStorageAccountName
value: 'thomasthorntontfstate'
- name: backendAzureRmContainerName
value: 'aksdeployazuredevops'
- name: backendAzureRmKey
value: 'terraform.tfstate'
- name: environment
value: 'production'
- name: terraform_version
value: '0.13.4'
- name: action
value: ${{ parameters.Action }}
Next is the 5 stages mentioned above
stages :
- stage: terraform_validate
condition: ne('${{ parameters.Action }}', 'Destroy')
jobs:
- template: templates/terraform-validate.yaml
parameters:
backendServiceArm: ${{ variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{ variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{ variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{ variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{ variables.backendAzureRmKey }}
environment: ${{ variables.environment }}
terraform_version: ${{ variables.terraform_version }}
- stage: terraform_plan
dependsOn: [terraform_validate]
condition: ne('${{ parameters.Action }}', 'Destroy')
jobs:
- template: templates/terraform-plan.yaml
parameters:
backendServiceArm: ${{ variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{ variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{ variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{ variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{ variables.backendAzureRmKey }}
environment: ${{ variables.environment }}
terraform_version: ${{ variables.terraform_version }}
- stage: terraform_apply
dependsOn: [terraform_plan]
condition: ne('${{ parameters.Action }}', 'Destroy')
jobs:
- template: templates/terraform-apply.yaml
parameters:
backendServiceArm: ${{ variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{ variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{ variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{ variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{ variables.backendAzureRmKey }}
environment: ${{ variables.environment }}
terraform_version: ${{ variables.terraform_version }}
- stage: bootstrap
dependsOn: [terraform_apply]
condition: ne('${{ parameters.Action }}', 'Destroy')
jobs:
- template: templates/az-cli.yaml
- stage: terraform_destroy
condition: contains('${{ parameters.Action }}', 'Destroy')
jobs:
- template: templates/terraform-destroy.yaml
parameters:
backendServiceArm: ${{ variables.backendServiceArm }}
backendAzureRmResourceGroupName: ${{ variables.backendAzureRmResourceGroupName }}
backendAzureRmStorageAccountName: ${{ variables.backendAzureRmStorageAccountName }}
backendAzureRmContainerName: ${{ variables.backendAzureRmContainerName }}
backendAzureRmKey: ${{ variables.backendAzureRmKey }}
environment: ${{ variables.environment }}
terraform_version: ${{ variables.terraform_version }}
As mentioned, the pipeline references templates which are found in this location

The bootstrap stage deploys a test application into AKS with Application Gateway ingress; configured within a job/AzureCLI@2 task as below:
jobs:
- job: azcli_resourcegroup_create
steps:
- task: AzureCLI@2
displayName: 'BootStrap AKS Cluster'
inputs:
azureSubscription: 'thomasthorntoncloud'
scriptType: bash
scriptLocation: inlineScript
addSpnToEnvironment: true
inlineScript: |
#!/usr/bin/env bash
set -x
AKS_RG="azuredevopsaksdeployaks-rg"
AKS_NAME="azuredevopsaksdeployaks"
# Get AKS Credentials
az aks get-credentials -g $AKS_RG -n $AKS_NAME --admin
# For AAD Pod Identity
kubectl create -f https://raw.githubusercontent.com/thomast1906/thomasthorntoncloud-examples/master/Azure-AKS-Deploy-Azure-DevOps/scripts/deployment.yaml
Once the bootstrap stage has been completed, the test application will be deployed. Viewing the services and ingresses in Azure, you will notice an ingress azure-vote-front with external IP

Accessing the External IP , will load the test voting application

Awesome! Successfully deployed Azure AKS using Azure DevOps and deploying sample application!
GitHub repository containing all the configuration – Check it out & thank you for viewing!
6 comments