CI/CD using Terraform and Azure Pipelines -ideation

After writing a few notes on “Azure DevOps and Terraform,” I thought of exploring the idea of integrating Azure DevOps and Terraform a little further.

Generally speaking, in Azure Pipelines (classic editor), a build definition (pipeline in Azure DevOps services) is used to compile and package an artifact. Then, under Releases, we have a release definition associated with the build definition used to deploy the artifacts across different environments like -Dev, Test, Stage, and Prod. These environments consist of different stages and can be mapped to separate deployment groups. The Azure DevOps pipeline enables us to build once and deploy multiple -across environments.

Note: I have a separate note where I write in detail about Pipelines and their components.

Terraform, as we know, is an infrastructure automation tool that follows the sequence of init -> validate -> plan -> apply (and if necessary, destroy too). So the idea was to use both Azure DevOps and Terraform to automate infrastructure provisioning across environments.

That meant that I had to map the Terraform commands into Azure pipelines build definition and release definition. In a traditional CI/CD approach, we compile and create the package in CI (continuous integration). However, in the case of Terraform, there is no code to compile. And then, in CD (continuous deploy), we apply the package to an environment. I realize that terraform plan and terraform apply belong to CD because they are environment specific and will vary from environment to environment. And hence, by the rule of elimination, terraform init and terraform validate belong to CI. There is no hard and fast rule as such. I attempt to retrofit terraform workflow with Azure DevOps.

In the case of an Azure DevOps build definition, the workflow would look something like below:
-checkout files from code repo,
-run terraform init, which downloads all dependencies and initializes the backend to store the remote state. It is at this stage that we also provide credentials (AWS or Azure) to be able to communicate with the remote state
-run terraform validate, which checks for correctness of syntax,
-create a package with the .terraform folder and the .tf files in it.
The package is then ready to have terraform plan and terraform apply run on them, which belongs to the continuous deploy step defined in a release definition.

So the release definition workflow would look like this:
-download the package from the associated build definition,
-run terraform plan
-run terraform apply

Conceptually, this would work but has certain limitations.
The idea of a CI/CD pipeline is to build once-deploy multiple. However, a terraform configuration has a remote state where terraform stores the state file of changes applied to an environment. Terraform stores the remote state configuration value in a file, and that file cannot have any variables.
Here is an example of a file in case of AWS:

terraform {
backend "s3" {
bucket = "$(BackendBucketName)" # the name of the S3 bucket that was created
encrypt = true
key = "$(PathToTFStateFile)" # the path to the terraform.tfstate file stored inside the bucket
region = "$(BucketRegion)" # the location of the bucket
dynamodb_table = "$(BackendLockTableName)" # the name of the table to store the lock

view raw
hosted with ❤ by GitHub

Effectively, there is an S3 bucket and a key, and these cannot be variables.

And here is an example of a file in case of Azure:

terraform {
backend "azurerm" {
resource_group_name = "$(ResourceGroupToStoreTerraformBackendResources)"
storage_account_name = "$(UniqueStorageAccountName)"
container_name = "$(StorageContainerName)"
key = "terraform.tfstate"
access_key = "$(StorageAccountAccessKey)"

view raw
hosted with ❤ by GitHub

Let me explain through an example. Say I have a terraform configuration to provision a virtual machine in AWS. The requirement is to provision the VM in the following four environments: Dev, Test, Stage, and Prod. Generally, it is a good security practice to map environments to separate AWS accounts. And when it comes to a CI/CD pipeline, the changes need to flow from environment to environment. Hence, a typical Azure DevOps CI/CD pipeline can have a workflow like: deploy to Dev environment -> validate the change in Dev environment->deploy to Test environment-> validate the change in Test environment ->deploy to Stage environment -> validate the change in Stage environment->deploy to Prod environment-> validate the change in Prod environment. By having separate stages, we can protect a higher environment from having un-validated changes introduced.
In such a scenario, how do we manage to apply the Terraform configurations across different environments?
And as I had mentioned, we cannot have variables in the, which implies that we cannot extend the remote state such that for the Dev environment, the remote state is such, and for Test, it is such. There can be only one value there.
Hashicorp had thought of this, and they introduced the concept of workspaces. A terraform workspace allows the backend to extend such that we can use it to store the remote state of different environments.

I hope this note helped you understand the limitation that I presented. Please read my following note where I do a deep dive on terraform workspace. I then implement the idea discussed using YAML-based Azure pipelines and Powershell in the next note titled  –CI/CD of Terraform workspace with YAML based Azure Pipelines.

2 thoughts on “CI/CD using Terraform and Azure Pipelines -ideation

Leave a Reply

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

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

Google photo

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

Twitter picture

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

Facebook photo

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

Connecting to %s