Deploy Terraform using Azure DevOps

Using Terraform to deploy your Azure resources is becoming more and more popular; in some instances overtaking the use of ARM to deploy into Azure. I will show you in this blog how you can deploy your Azure Resources created in Terraform using Azure DevOps finishing with an example .yml pipeline.

What is Azure DevOps?

Deploying resources already into Azure; you probably already have came across using Azure DevOps, it is a hosted service by Microsoft that provides an end-to-end DevOps toolchain for developing and deploying software, along with this – it is a hosted service to deploy CI/CD Pipelines

Initial requirements before you can begin deploying

There are some prior requirements you need to complete before we can get deploying Terraform using Azure DevOps. These are:-

  • Where to store the Terraform state file?
  • Azure DevOps Project
  • Azure Service Principal
  • Sample Terraform code

Lets have a look at each of these requirements; I will include an example of each and how you can configure.

Where to store the Terraform state file?

When deploying Terraform there is a requirement that it must store a state file; this file is used by Terraform to map Azure Resources to your configuration that you want to deploy, keeps track of meta data and can also assist with improving performance for larger Azure Resource deployments.

In this deployment, I want to store the state file remotely in Azure; I will be storing my state file in a Storage Account container called:- tfstatedevops

Lets deploy the required storage container called tfstatedevops in Storage Account tamopstf inside Resource Group tamopstf

Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.

#Create Resource Group
New-AzureRmResourceGroup -Name "tamopstf" -Location "eastus2"

#Create Storage Account
New-AzureRmStorageAccount -ResourceGroupName "tamopstf" -AccountName "tamopstf" -Location eastus2 -SkuName Standard_LRS

#Create Storage Container
New-AzureRmStorageContainer -ResourceGroupName "tamopstf" -AccountName "tamopstf" -ContainerName "tfstatedevops"


Azure DevOps Project

Deploying Terraform using Azure DevOps, requires some sort of project; in this blog I will create a new project

This is documented already by Microsoft here, I recommend this guide to show you how to setup a DevOps Project similar to mine below

The DevOps Project in my example will be called TamOpsTerraform as below

Azure Service Principal

A Service Principal (SPN) is considered a best practice for DevOps within your CI/CD pipeline. It is used as an identity to authenticate you within your Azure Subscription to allow you to deploy the relevant Terraform code.

In this blog, I will show you how to create this manually (there is PowerShell / CLI but within this example I want you to understand the initial setup of this)

To begin creation, within your newly created Azure DevOps Project – select Project Settings

Select Service Connections

Select Create Service Connection -> Azure Resource Manager -> Service Principal (Automatic)

For scope level I selected Subscription and then entered as below, for Resource Group I selected tamopstf which I created earlier

Once created you will see similar to below

You can select Manage Service Principal to review further

When creating this way, I like to give it a relevant name so I can reference my SPN easier within my Subscription. This is done within “Manage Service Principal”

Settings -> Properties and change Name as below

You can also reference your SPN easier if you want to give it further IAM control to your subscription, in this setup I also give the SPN “contributor” access to my subscription.

Documented role assignment here by Microsoft

Sample Terraform code

We’re now near ready to configure your DevOps pipeline; but first! Some sample Terraform code to deploy. In my example I will deploy a Storage Account tamopssatf inside a Resource Group tamops-tf (Notice the reference to the tfstate resource_group_name, storage_account_name and container_name

provider "azurerm" {
    # The "feature" block is required for AzureRM provider 2.x.
    # If you're using version 1.x, the "features" block is not allowed.
    version = "~>2.0"
    features {}
}

terraform {
  backend "azurerm" {}
}

data "azurerm_client_config" "current" {}

resource "azurerm_resource_group" "tamopsrg" {
  name     = "tamops-tf"
  location = "eastus2"
}

resource "azurerm_storage_account" "tamopssa" {
  name                     = "tamopssatf"
  resource_group_name      = azurerm_resource_group.tamopsrg.name
  location                 = azurerm_resource_group.tamopsrg.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

Deploy this into your repo

Deploying Terraform using Azure DevOps

The initial requirements now configured, time to setup Azure DevOps to deploy your Terraform into Azure.

Install the Terraform extension/task from here

The Terraform task enables running Terraform commands as part of Azure Build and Release Pipelines providing support for the following Terraform commands

  • init
  • validate
  • plan
  • apply
  • destroy

Once installed, we can now configure a pipeline

Select your Repo -> Setup Build

Select Starter Pipeline

Now you are Produced with an .yml format. Further understand documented here

YML example Pipelines and further Terraform info is found here. My example Pipeline consists of snippets from this GitHub

In my Pipeline, I have two Stages

Validate:- To Validate my Terraform code, if validation fails the pipeline fails (consists of Terraform init & validate)

Deploy:- if Validation is successful, it moves to next stage of pipeline which is Deploying the Terraform code to deploy required Azure Resources (consists of Terraform plan & deploy)

Throughout the Pipeline, notice my reference to the previously created Storage Account, Resource Group and container for the Terraform state file along with the newly created SPN? (extraction below)

          backendServiceArm: 'tamopstf'
          backendAzureRmResourceGroupName: 'tamopstf'
          backendAzureRmStorageAccountName: 'tamopstf'
          backendAzureRmContainerName: 'tfstatedevops'
          backendAzureRmKey: 'terraform.tfstate'

Full Azure DevOps Pipeline

stages :
  - stage: validate
    jobs:
    - job: validate
      continueOnError: false
      steps:
      - task: TerraformInstaller@0
        displayName: 'install'
        inputs:
          terraformVersion: '0.12.3'
      - task: TerraformTaskV1@0
        displayName: 'init'
        inputs:
          provider: 'azurerm'
          command: 'init'
          backendServiceArm: 'tamopstf'
          backendAzureRmResourceGroupName: 'tamopstf'
          backendAzureRmStorageAccountName: 'tamopstf'
          backendAzureRmContainerName: 'tfstatedevops'
          backendAzureRmKey: 'terraform.tfstate'
      - task: TerraformTaskV1@0
        displayName: 'validate'
        inputs:
          provider: 'azurerm'
          command: 'validate'
  - stage: deploy
    jobs:
    - deployment: deploy_terraform
      continueOnError: false
      environment: 'dev'
      strategy:
       runOnce:
         deploy:
            steps:
              - checkout: self
              - task: TerraformInstaller@0
                displayName: 'install'
                inputs:
                  terraformVersion: '0.12.3'
              - task: TerraformTaskV1@0
                displayName: 'init'
                inputs:
                  provider: 'azurerm'
                  command: 'init'
                  backendServiceArm: 'tamopstf'
                  backendAzureRmResourceGroupName: 'tamopstf'
                  backendAzureRmStorageAccountName: 'tamopstf'
                  backendAzureRmContainerName: 'tfstatedevops'
                  backendAzureRmKey: 'terraform.tfstate'
              - task: TerraformTaskV1@0
                displayName: 'plan'
                inputs:
                  provider: 'azurerm'
                  command: 'plan'
                  environmentServiceNameAzureRM: 'tamopstf'
              - task: TerraformTaskV1@0
                displayName: 'apply'
                inputs:
                  provider: 'azurerm'
                  command: 'apply'
                  environmentServiceNameAzureRM: 'tamopstf'

Once you configure & save the above pipeline, you will see it beginning to run and can review both stages

After a few minutes, the build Pipeline will run through and if both stages are successful you will see similar to below

Reviewing the job, you will see a more thorough breakdown of the tasks

Selecting for example plan, you will see what Azure Resources are planned to be deployed

Reviewing inside the Azure Portal, you will see the newly created Resource Group & Storage Account

Great work!

If you followed this blog post, you now have a good solid introduction into how you can create your Terraform code and run successfully using Azure DevOps to deploy Azure Resources!

The pipeline I showed was a simple execution, you can configure this further depending on your requirements but hopefully a good base-line to get you started!

32 comments

  1. Hi, I was following your instructions and they look pretty good, but I have gotten to the part of creating the repo and getting the example.tf file into it. Your instructions appear to be missing a step as I’m getting told to add some code in Devops in the repo but struggling to understand how as you haven’t explained. Have I done something wrong? missed something? wonder if you could help please?

    1. Hi Ashley, I had referenced undwr the Terraform code “Deploy this into your repo” – see “sample terraform code section”

      had wrote the blog in understanding that those who follow had worked with Azure Devops before.

      Heres a MS article to add code to repo:- https://docs.microsoft.com/en-us/azure/devops/repos/git/create-new-repo?view=azure-devops

      Feel free to reach out to me on Twitter to discuss further or reply to comment

      Thank you for reading the blog post, hope you enjoyed it

      1. Hi,
        I’m seeing the same issue. I have the “example.tf” file on Azure DevOps repo. However, I see “Error: No configuration files” in the deployment stage. Please help.

      2. Hi,

        Could mail me some screenshot and your Azure devops pipeline?

        Thanks

      3. Nevermind, I made a silly mistake, instead of “example.tf”, I had “example.cf”. After the change it worked as you outlined.

      4. Glad you got the issue resolved! Do reach out if you have any queries and feel free to check my other blog posts out 👍

    1. Thanks Kiran, good luck with your Azure DevOps & Terraform journey! Check out my other blog posts also.

  2. Can you explain how exactly the build environment uses the state file to only add the infrastructure changes but not deploy them all over again?

  3. Hi,
    Using your sample code, I was able to build a linux vm. Can you help me with post install script.
    Below doesn’t work.
    – task: SSH@0
    inputs:
    runOptions: ‘script’
    scriptPath: ‘new-node-setup.sh’
    readyTimeout: ‘20000’

      1. Just to make it clear: I have a script “new-node.sh” which is in my DevOps repo (along with my variables.tf and vm.tf) and I want to run after the node build is done within the same pipeline.

      2. Just to make it clear: I have a script “new-node.sh” which is in my DevOps repo and I want to run after the node build is done within the same pipeline.
        I’m using username/password stored in azure key vault. Once the node build is done I can login using these credentials.

  4. I think the backend block is incorrect, based on container created. I’m still somewhat new to Terraform, so could be wrong…but I think you provided what should be the “key” as the “container_name”.

    1. Hi Frank,

      Yes you are correct. I ran an update on this blog post recently and show its ended like that.

      Will update now,

      Thanks

  5. Nice article.

    One suggestion I would make – strongly – is to separate the plan and apply steps. I would never, ever do a terraform apply without first scutinising the plan because, you know, bitter experience 🙂

    1. Hi Peter,

      Thanks for the comment and feedback

      Ofcourse, in the Production world 99% of the time you will want to review the plan before the apply 🙂 This blog post caters for the initial deploying terraform using Azure DevOps. I’ve a number of posts similar which do show plan & apply at different stages – even using an approval gate

      Thanks

      Thomas

  6. Great article. However it is worth mentioning that Manage Service Principal is not supported on Azure DevOps server editions.

    1. Thanks for the message – this blog post was aimed solely at using Azure DevOps online/web based rather than server

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s