Configurations I recommend you setup to deploy your Terraform into Azure at scale using GitHub Actions : Thomas Thornton
by: Thomas Thornton
blow post content copied from Thomas Thornton – Microsoft Azure MVP – HashiCorp Ambassador
click here to view original post
This blog post delves into the world of Terraform deployments in Microsoft Azure and explores how GitHub Actions can be harnessed to streamline the process, particularly when dealing with larger-scale deployments across various terraform resources, components and environments. We’ll walk through the essential configurations and best practices that I recommend for deploying Terraform at scale with Azure using GitHub Actions.
This post is part of the Azure Back to School event – certainly check it out, lots of awesome content available: https://azurebacktoschool.github.io/ – I want to extend my heartfelt thanks to Dwayne Natwick for tirelessly organizing this event year after year!
What will we cover?
I will split this blog post into multiple sections and will cover:
- Structuring your GitHub Repository
- Standard GitHub Repository recommended settings
- GitHub Repository Environment Secrets and Variables
- GitHub Action Setup
- GitHub Action Templating
- Using matrixes to deploy multiple components to multiple environments within GitHub Actions
- Dependabot Setup
What will be deployed?
The basis of this post is to show configurations I recommend you setup to deploy your Terraform into Azure at scale using GitHub Actions, the Terraform that will be deployed is example purpose only, but we will be deploying:
An Azure Platform consisting of the below, deployed into two environments development & production:
- core: A resource group
- logging: Log Analytics workspace
- networking: Virtual network and private dns zones
This is show the recommended folder structure, utilising this will scale to multiple more environments..
1. Structuring your GitHub Repository
Creating a well-organized folder structure for your Terraform GitHub repository is essential for maintaining a clean and manageable codebase. Below is an example folder structure that I recommend, which you can adapt further to your needs:
my-terraform-repo/
├── .github/
│ └── workflows/
│ └── deploy.yaml # GitHub Actions core deployment workflow file
│ └── template-terraform.yaml # GitHub Actions template workflow file for terraform
│ └── dependabot.yaml # Dependabot yaml
├── environments/
│ ├── development/
│ │ ├── development.tfvars # Development environment .tfvars file
│ ├── production/
│ │ ├── main.tf # Development environment configuration
├── modules/
│ ├── log_analytics/ # Log analytics module
│ │ ├── init.tf
│ │ ├── log_analytics_solutions.tf
│ │ ├── log_analytics.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ ├── private_dns_zone/ # Private dns zone module
│ │ ├── init.tf
│ │ ├── private_dns_zone.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ ├── virtual_network/ # Virtual network module
│ │ ├── init.tf
│ │ ├── virtual_network.tf
│ │ ├── subnets.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
├── platform/ # Folder containing the required deployments for the Azure environment
│ ├── core/ # Containing the core components
│ │ ├── data.tf # Data references needed for the component
│ │ ├── main.tf # Configuration of the component
│ │ ├── providers.tf # Relevant Providers
│ │ ├── variables.tf # Variables specific to the component
│ ├── logging/ # Containing the logging components
│ │ ├── data.tf # Data references needed for the component
│ │ ├── main.tf # Configuration of the component
│ │ ├── providers.tf # Relevant Providers
│ │ ├── variables.tf # Variables specific to the component
│ ├── network/ # Containing the network components
│ │ ├── data.tf # Data references needed for the component
│ │ ├── main.tf # Configuration of the component
│ │ ├── providers.tf # Relevant Providers
│ │ ├── variables.tf # Variables specific to the component
├── README.md # Project documentation
Here’s an explanation of the key components in this folder structure:
- .github/workflows/ : This directory contains your GitHub Actions workflow files for deploying your automated Terraform deployments.
- environments/ : This directory holds subdirectories for each environment (e.g., development, production). Each environment directory contains its specific Terraform configuration .tfvars files
- modules/ : In this directory, you define reusable Terraform modules for creating infrastructure components. Modules encapsulate resource definitions, variables, and outputs.
- platform/ : Contains the relevant components required to deploy the Azure environment successfully
This structure helps you maintain a clean and organised Azure Terraform project, making it easier to collaborate, manage different environments, and scale your infrastructure as needed. Remember that you can adapt this structure to fit your specific project requirements.
2. Standard GitHub Repository recommended settings
Enable a branch protection policy for your repository, branch protection policies in GitHub are a set of rules and settings that control what actions can be performed on a branch and under what conditions. These policies are essential for maintaining code quality, security, and collaboration in your repository.
Branch protection policies can really vary betewen organisations and teams, I recommend checking out this article further for you to decide on which settings are best for your branch protection policy – https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches
In this I will enable the setting Require a pull request before merging
3. GitHub Repository Environment Secrets and Variables
Deploying between environments, we need to have a way to potentially use different secrets and variables between these, along with this we want to streamline deployments while enhancing security. Time to explore how leveraging these GitHub features can benefit your Terraform deployments.
GitHub Repository Environment Secrets are encrypted variables that you can store at the repository level. These secrets are designed to securely store sensitive information like API keys, credentials, or other private data your Terraform code requires during deployment within GitHub Actions. While these secrets can be utilised in your Terraform deployments, it’s often advisable to reserve Azure Key Vault for Terraform-specific secrets, leveraging them through data key_vault_secret
blocks for added security and ease of management.
GitHub Repository Environment Variables complement secrets by providing a way to manage non-sensitive configuration settings.
For multi-environment projects (e.g., development, staging, production), environment-specific variables streamline configurations. You can set variables to change based on the target environment, making deployment across different stages seamless.
Now that you understand the use of both environment secrets and variables, let’s see how to implement these features in your Terraform workflow:
1. Storing Secrets
- Navigate to your GitHub repository.
- Click on “Settings” at the top of the repository.
- In the left sidebar, click on “Secrets.”
- Click “New repository secret” to add your secret. Give it a name and value.
- In your GitHub Actions workflow, reference the secret using
$
.
2. Using Variables
- In your GitHub repository, go to “Settings.”
- In the left sidebar, click on “Environment.”
- Click on the environment for which you want to define variables.
- Click “Add environment variable” and provide a name and value.
- In your GitHub Actions workflow, access the variable using
$
.
3. Incorporating into GitHub Actions
In your GitHub Actions workflow files, use the $
syntax for secrets and $
for variables wherever needed.
What are they used for as part of this?
I recommend using Github repository secrets to store the relevant secrets that the GitHub Workflow requires, which are:
- CLIENT_ID: Client ID of the Azure AD Service Principal that will be used to deploy the Terraform
- CLIENT_SECRET: Client Secret of the Azure AD Service Principal that will be used to deploy the Terraform
- DEPLOYMENT_SUBSCRIPTION_ID: Azure subscription ID of where the Terraform storage account is situated to store the .tfstate file
- SUBSCRIPTION_ID: Azure subscription ID of where the Terraform code will be deployed to
- TENANT_ID: Azure AD tenant ID
4. GitHub Action Setup
Lets review the core GitHub Action ( .github/workflows/deploy.yaml
)
name: 'Terraform Deploy'
on:
push:
branches:
- main
pull_request:
jobs:
terraform:
uses: ./.github/workflows/template-terraform.yaml
strategy:
matrix:
environment: ['development','production']
platform_directory: ['core','logging','network']
fail-fast: true
max-parallel: 1
with:
environment: $
backend_tf_rg: thomasthorntoncloud
backend_tf_sa: thomasthorntontfstate
backend_tfstate_name: $-tf-github$
tfstate_container: github-tf-at-scale-$
platform_directory: $
secrets:
CLIENT_ID: $
CLIENT_SECRET: $
SUBSCRIPTION_ID: $
TENANT_ID: $
DEPLOYMENT_SUBSCRIPTION_ID: $
If we look. at the above, its relatively lightweight but very effective, which I will discuss by looking at the key areas of the pipeline:
- Lines 3-7 : The trigger for the action is when code is pushed to the “main” branch or when a pull request is opened.
Screenshot below shows the steps during a pull request, notice Terraform Apply
is skipped. as mentioned above
- Line 9-11 : Beginning. ofthe terraform job that uses a template file (more on that below)
- Lines 12-17 : Using a series of strategy matrixes, these are awesome! The job is run with a matrix strategy that defines different values for the “environment” and “platform_directory” variables. The “fail-fast” and “max-parallel” options are also set. (section on that below)
- Lines 18-30 : Mixture of concatenated variables and secrets being used from the created GitHub repository secrets, notice line 19 determines the environment? By this, it decides to use either the development or production environment secrets. The “with” section defines the input variables for the custom action. The “environment” and “platform_directory” variables are set based on the matrix strategy. Other variables, such as the name of the Terraform state file and the name of the storage container, are also set. The “secrets” section defines the secrets that are required for the deployment, such as the client ID, client secret, subscription ID, and tenant ID. These secrets are stored securely in GitHub and are not visible in the code.
5. GitHub Action Templating
GitHub Action Templating involves creating reusable templates for your workflows. These templates encapsulate common actions, steps, and configuration settings that you can apply across various repositories or workflows. Instead of rewriting the same workflow code repeatedly, you define it once in a template and reuse it wherever needed. On the templating topic, I did write a similar post sometime ago for Azure DevOps – Creating templates in Azure DevOps Pipelines
As your Terraform projects grow in scale and complexity, so do your automation needs. Templating simplifies the management of intricate workflows by breaking them down into modular, reusable components. A few benefits as to why I recommend using templates in GitHub Actions:
- Consistency and standardisation : Consistency is key in automation. Templating ensures that your workflows adhere to a predefined standard. Whether you have a single repository or multiple projects, you can maintain uniformity in your CI/CD processes.
- Increases Efficiency and Productivity : With templating, you avoid redundant code. This not only saves time but also reduces the likelihood of errors. You can swiftly create, modify, and deploy workflows, boosting productivity.
- Easier to maintain : When you need to update your automation logic, a single change to the template propagates to all workflows using it. This centralised management simplifies maintenance and minimises the risk of overlooking critical updates.
- Reusability : Once you create a template, you can reuse it across multiple repositories.
With this GitHub repository – I created a template to deploy terraform with, used by multiple components and environments all deployed from the same location. ( .github/workflows/template-terraform.yaml
)
name: Reusable workflow example
on:
workflow_call:
inputs:
backend_tfstate_name:
required: true
type: string
environment:
required: true
type: string
backend_tf_rg:
required: true
type: string
backend_tf_sa:
required: true
type: string
platform_directory:
required: true
type: string
tfstate_container:
required: true
type: string
secrets:
CLIENT_ID:
required: true
CLIENT_SECRET:
required: true
SUBSCRIPTION_ID:
required: true
TENANT_ID:
required: true
DEPLOYMENT_SUBSCRIPTION_ID:
required: true
jobs:
deploy-terraform:
name: $
environment: $
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.3
terraform_wrapper: false
- name: Terraform Init
id: init
run: terraform init -backend-config="resource_group_name=$" -backend-config="storage_account_name=$" -backend-config="container_name=$" -backend-config="key=$.tfstate"
env:
ARM_CLIENT_ID: $
ARM_CLIENT_SECRET: $
ARM_SUBSCRIPTION_ID: $
ARM_TENANT_ID: $
working-directory: ./platform/$
shell: bash
- name: Terraform Plan
id: plan
run: terraform plan -no-color -input=false -var deployment_subscription_id=$DEPLOYMENT_SUBSCRIPTION_ID -var-file="../../environments/$/$.tfvars"
env:
ARM_CLIENT_ID: $
ARM_CLIENT_SECRET: $
ARM_SUBSCRIPTION_ID: $
ARM_TENANT_ID: $
DEPLOYMENT_SUBSCRIPTION_ID: $
working-directory: ./platform/$
shell: bash
continue-on-error: false
- name: Terraform Apply
id: apply
run: terraform apply -auto-approve -var deployment_subscription_id=$DEPLOYMENT_SUBSCRIPTION_ID -var-file="../../environments/$/$.tfvars"
if: github.ref == 'refs/heads/main'
env:
ARM_CLIENT_ID: $
ARM_CLIENT_SECRET: $
ARM_SUBSCRIPTION_ID: $
ARM_TENANT_ID: $
DEPLOYMENT_SUBSCRIPTION_ID: $
working-directory: ./platform/$
shell: bash
continue-on-error: false
Key Points in the above pipeline
- Workflow Name and Trigger: This workflow is named “Reusable workflow example” and is triggered by a
workflow_call
event. - Input Parameters: This workflow accepts several input parameters, including
backend_tfstate_name
,environment
,backend_tf_rg
,backend_tf_sa
,platform_directory
, andtfstate_container
. All of these inputs are required and have specified types and all all inputted from the core pipeline (.github/workflows/deploy.yaml
) - Secrets: This workflow requires several secrets, including
CLIENT_ID
,CLIENT_SECRET
,SUBSCRIPTION_ID
,TENANT_ID
, andDEPLOYMENT_SUBSCRIPTION_ID
. These secrets are essential for authenticating and interacting with Azure services. - Jobs: The workflow contains a single job named “deploy-terraform.” This job will run on an
ubuntu-latest
runner. - Steps: Within the job, there are multiple steps that set up the environment for Terraform, initialize it, perform a plan, and apply changes if the branch is ‘main’. These steps ensure that Terraform is configured and run correctly for the specified environment.
- The workflow starts by checking out the repository (
actions/checkout@v2
). - Then, it sets up Terraform using the
hashicorp/setup-terraform@v2
action. - The
Terraform Init
step initializes Terraform with the specified backend configuration. - The
Terraform Plan
step creates an execution plan. - The
Terraform Apply
step applies changes to the infrastructure but only if the branch is ‘main’.
- The workflow starts by checking out the repository (
Each step is configured with the necessary environment variables and working directories to ensure that Terraform operates correctly for the specified platform and environment.
6. Using matrixes to deploy multiple components to multiple environments within GitHub Actions
GitHub Actions offers a feature known as matrixes to address the complexities of multi-environment deployments.
A matrix strategy lets you use variables in a single job definition to automatically create multiple job runs that are based on the combinations of the variables. For example, you can use a matrix strategy to test your code in multiple versions of a language or on multiple operating systems.
https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs
A matrix in GitHub Actions is a mechanism that allows you to run jobs or workflows with multiple configurations concurrently. This is invaluable when you need to deploy various components across different environments, each with its specific parameters. In this case, multiple Terraform components across various environments
Diving further into the matrix being used in this workflow:
strategy:
matrix:
environment: ['development','production']
platform_directory: ['core','logging','network']
fail-fast: true
max-parallel: 1
I have leveraged the matrix variables to customise my Terraform deployments for each component-environment combination.
With the above, the key pieces:
- Matrix Variables: The
matrix
block defines two matrix variables:environment
andplatform_directory
. These variables are used to create combinations of values for parallel job execution.
This configuration creates a matrix with six combinations: [development, core]
, [development, logging]
, [development, network]
, [production, core]
, [production, logging]
, and [production, network]
.
- Parallel Job Execution: The
max-parallel
option is set to1
, which means that only one combination of the matrix values will run in parallel at a time. This is useful for scenarios where you want to limit the number of concurrent deployments, ensuring that they do not overwhelm resources. - The
fail-fast
option is set totrue
. If any job within the matrix fails, the entire matrix job will be marked as failed. This can help catch and address issues early in the deployment process. - This matrix configuration is designed to execute Terraform deployments for different combinations of environments and platform directories in a controlled and sequential manner, with a focus on minimising parallel execution and ensuring early detection of failures.
GitHub Actions matrixes are a powerful tool for managing multi-environment and multi-configuration Terraform deployments efficiently. They enable parallelised deployments, promote consistency, and simplify workflow configurations. With matrixes, you can embrace the complexity of modern infrastructure management while keeping your deployment process organized and efficient. By harnessing the power of matrices in GitHub Actions, you can optimise your Terraform deployments and streamline your infrastructure as code workflows.
7. Dependabot Setup
Dependabot enforces consistency across your Terraform codebase by applying updates consistently across all parts of your project. This prevents issues that can arise from inconsistent or mismatched dependencies.
Dependabot can be integrated into GitHub Actions to automate the dependency update process. To enable Dependabot on GitHub:
- Go to the “Security” tab of your GitHub repository.
- Under “Code Security and analysis“
- Choose “Dependabot” as the provider.
- Configure the settings for your repository in Dependabot version updates & enable Dependabot security updates
Use this Dependabot config:
version: 2
updates:
- package-ecosystem: "terraform" # See documentation for possible values
directory: "/modules/log_analytics"
schedule:
interval: "weekly"
- package-ecosystem: "terraform" # See documentation for possible values
directory: "/modules/private_dns_zone"
schedule:
interval: "weekly"
- package-ecosystem: "terraform" # See documentation for possible values
directory: "/modules/virtual_network"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/.github/workflows"
schedule:
interval: "weekly"
This configuration automates dependency management for Terraform modules in different subdirectories, each scheduled for weekly updates. Additionally, it manages GitHub Actions workflows in the “/.github/workflows
” directory, also scheduled for weekly updates. Dependabot will regularly check for and create pull requests to update dependencies in these specified directories.
Rounding up
Thank you for reading and hopefully using this blog post to assist you and your journey of deploying into Azure at scale using Terraform and GitHub Actions. There is lots of various settings and configurations you can use, this is the basis to start you on your journey.
The complete repository is found here and as always, any questions – do reach out to me
September 01, 2023 at 02:44PM
Click here for more details...
=============================
The original post is available in Thomas Thornton – Microsoft Azure MVP – HashiCorp Ambassador by Thomas Thornton
this post has been published as it is through automation. Automation script brings all the top bloggers post under a single umbrella.
The purpose of this blog, Follow the top Salesforce bloggers and collect all blogs in a single place through automation.
============================
Post a Comment