The Plan
In this post we will be going through the process of setting up automated Azure infrastructure deployments using Terraform and GitGub Actions. We will also be implementing some integration tests that will be run as an Action prior to the deployment. This means the model of deployment will be:
- Create a branch from
master
- Make changes in new branch
- Create a pull request to
master
- Integration tests Action will run - Once tests have passed, merge the pull request - Deploy Action will run
We will end up with something like this:
Setting up GitHub Project
There are a few things we need to do to get our GitHub project setup for GitHub actions to be able to authenticate with Azure.
Create Azure AD App Registration
If you look at the Terraform documentation for the Azure provider you will notice there are numerous methods that can be used for Authentication. In this case we will be using a Service Principal with a Client Secret
and generating the credentials via an Azure AD App Registration. This way we can have a set of credentials specifically for Terraform. To do this:
- Go to the
Subscriptions
service and take note of your targetSubscription ID
- Browse to
App registration
in theAzure Active Directory
service and selectNew registration
- Set the name to something like
Terraform
- Leave the
Supported account types
as the defaultSingle tenant
option - We can ignore the
Redirect URI
- Hit
Register
to create the App Registration
Once you have registered the application there will be a few values to take note of:
- Application (client) ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- Directory (tenant) ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
You will then need to go to Certificates & secrets
section of your app registration and create a new client secret. Take note of this value also.
GitHub Secrets
Once you have the required ID and Secrets the next step is to add them the secret store in your GitHub project. You can name these whatever you like, I have named mine like this:
Creating a GitHub Action for Integration Testing
All GitHub actions live in your projects .github/workflows/
folder. For this integration testing we will create a tests.yml
file in this folder. This action will use Hashicorp’s terraform actions to perform terraform fmt, validate and plan. The action below assumes your terraform is in a directory called terraform
.
This is configured to run on a pull request. Below we will configure a deploy job to run on a merge to master.
name: 'Validate and plan deploy'
on:
- pull_request
jobs:
validate-terraform:
name: 'Validate and plan deploy'
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@master
- name: 'Terraform Format'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.24
tf_actions_subcommand: 'fmt'
tf_actions_working_dir: "./terraform"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Init'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.24
tf_actions_subcommand: 'init'
tf_actions_working_dir: "./terraform"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Validate'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.24
tf_actions_subcommand: 'validate'
tf_actions_working_dir: "./terraform"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Plan'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.24
tf_actions_subcommand: 'plan'
tf_actions_working_dir: "./terraform"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Note in the env
block, if you have named your secrets in GitHub differently to mine you will need to update the references here.
Creating a GitHub Action for the Deployment
For this one we will create a deploy.yml
in the .github/workflows/
directory. This is configured to run on a merge to master. Again, you may need to update the GitHub secret references.
name: 'Deploy'
on:
push:
branches:
- master
jobs:
deploy-to-azure:
name: 'Deploy to Azure'
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@master
- name: 'Terraform Init'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.24
tf_actions_subcommand: 'init'
tf_actions_working_dir: "./terraform"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Apply'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.24
tf_actions_subcommand: 'apply'
tf_actions_working_dir: "./terraform"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Setting up Terraform State in Azure
Since we are deploying into Azure via automation, we need to configure Terraform to store its state in Azure. This way no matter who triggers a deployment the state is consistent. This is configured in the backend block of the terraform deployment, in this case we are specifying an azure storage container for the state to be in. The key needs to be unique for each deployment:
terraform {
backend "azurerm" {
resource_group_name = "AZURE-TERRAFORM-STATE"
storage_account_name = "azureterraformstate"
container_name = "tfstate"
key = "prod.terraform.blog.tfstate"
}
}
Putting it all Together
Now that we have some GitHub action created and some terraform to deploy. The whole thing can be put together by:
- Create a branch from
master
- Make changes in new branch
- Create a pull request to
master
- Integration tests Action will run - Once tests have passed, merge the pull request - Deploy Action will run
The results of the tests.yml
action will be posted to the pull request so it can be easily reviewed before merging and triggering a deploy.