Terraform is a well-known IaC tool from Hashicorp. Terragrunt is a thin wrapper of Terraform that provides extra tools for keeping your configurations DRY, working with multiple Terraform modules, and managing remote states. It is an open source project from Gruntwork, co-founded by Yevgeniy Brikman, author of Terraform up and running.
This article discusses common patterns in Infrastructure as Code (IaC) based on Terraform and Terragrunt examples and summarizes and highlights the design principle underlying good architecture.
We will present these patterns in an abstract way. Although the examples presented in this article use Terraform and Terragrunt, the main concepts apply to other popular IaC tools and platforms like Pulumi, AWS CDK, CloudFormation, Terraform Cloud, Atlantis, Spacelift, and Terraform Cloud alternatives.
Note: New versions of Terraform will be placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that will expand on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6. OpenTofu retained all the features and functionalities that had made Terraform popular among developers while also introducing improvements and enhancements. OpenTofu is the future of the Terraform ecosystem, and having a truly open-source project to support all your IaC needs is the main priority.
To put it simply, “managing collections of infrastructure resources” is an abstract concept. The technologies used to implement this concept include:
Git branch (allows you to create two branches, one called workspace_1, the other workspace_2, then relate remote state to each of the branches, which is actually a neat trick.)
In short, to utilize IaC is to describe your infrastructure with code. Coding infrastructure in HCL (Hashicorp Configuration Language) with Terraform is just like coding with any other programming language, like Python or Java. Programming technologies such as linter, IDE, unittest, documentation, and CICD, are all applicable to IaC. Additionally, a project layout is crucial to defining a structure to organize your code, dependencies, configuration files, etc.
One common pattern of the project layout for IaC is much like the following:
The basic principle is to:
- Separate the process of IaC into two phases: development and deployment.
- Separate the deployment environments into prod, qa, and stage, for example.
- Separate the cloud architecture into components according to projects, types of infrastructure, etc. Each component can be stored in a single “gigantic” repository or multiple “micro” repositories (see Pattern: Mono-repo vs Multi-repo).
The process of IaC usually consists of two phases: development and deployment (if you are interested in this topic, the GitOps and weaveworks articles are good sources of information).
In the development phase, one writes code to describe and configure the desired infrastructure. In the deployment phase, the written infrastructure code is applied.
The advantages of separation:
- Separation of Concern: developers who write IaC do not need to know the details of the deployment, and can work or test on a new version of the code without influencing the actual deployed infrastructure.
- Flexibility: a well-designed IaC codebase is modularized and parameterized. The same code snippet can be reused and deployed under different use cases in different environments with customized parameters.
Imagine you have an AWS cloud architecture for web services, including different AWS services like ECS, RDS, S3, IAM, SSM, Secret Manager, Cloudwatch, Cloudfront and so on. One option is to pack all of the components into a single Terraform remote state (main.tf). This is fine for quick and dirty prototyping, but not recommended for production.
Always cut your cloud architecture into meaningful components such as:
- Stateful components like database, filesystem, or S3.
- Stateless components like lambda, ECS, or API Gateway.
You can also cut it based on projects or functional areas (e.g. frontend, backend, data warehouse).
The advantages of separation:
- Avoids scanning the whole architecture if a change needs to be made only locally to one resource. One can enter the component folder which includes that resource, make changes there, and apply them. Scanning a fat cloud account can be time consuming on a large amount of cloud resources, even causing unnecessary API throttling.
- Avoids unnecessary touches on databases or other crucial infrastructure to prevent accidental destructive operations.
- Increases readability of the IaC.
Translate the above described project layout into folder structure:
The separation of the development and deployment process is implemented with two git repositories – env/ and modules/. Notice the .git in the folder. Here, Terragrunt.hcl is simply a configuration file for modules specifically in the language of Terragrunt. The separation of cloud architecture components is implemented with subfolders app/ , mysql/, vpc/. The separation of deployment environments is implemented with subfolders prod/ , qa/ , stage/.
Check out our Terragrunt vs. Terraform comparison.
One of the principles of software engineering is DRY (don’t repeat yourself). The same applies to IaC. There are at least two kinds of tricks you can use to DRY IaC:
- Configuration inheritance
A Terraform module is a way of creating a template of a cloud pattern, parameterizing, and reusing it. Like any other artifacts, modules can be stored and versioned in the registry. The official Terraform module registry is https://registry.Terraform.io/. You can also build your own private registry.
2) Configuration Inheritance
Configuration inheritance is a feature supported by Terragrunt. The concept is similar to class inheritance in object oriented programming, except that configuration inheritance serves the purpose of configuration reusability.
In the following example (also see the project layout above), we have a configuration file at the global level (global_vars.yaml) and configuration files at the environment level (environment_specific_vars.yaml).
In global_vars.yaml, variables like account_id, region_name and repository_url can be defined, and in environment_specific_vars, these global variables will be inherited and present, additionally defining new environment level variables like vpc_id (usually we use separate vpc for different environments), environment_name, etc.
In summary, configuration inheritance increases the reusability of configuration files and reduces the difficulty in maintenance.
With Spacelift Contexts, you can define a collection of variables and files and then attach these to Spacelift stack(s). Spacelift Policies allow you to build rules using the Open Policy Agent (OPA) to govern your infrastructure as code and its automation. For example, you may want to disallow a specific resource type or even a certain way of configuring that resource type – you can easily achieve such a thing using a Plan Policy. You can also use Spacelift to mix and match Terraform, Pulumi, and CloudFormation Stacks and have them talk to one another.
Last but not least, let’s review the concept of mono-repo and multi-repo. It has been pointed out by Hashicorp as a major architectural decision for IaC projects, a view I share.
The problem is, there are two ways to organize code. In simple terms, mono-repo stores all code into one git repository (for example with multiple subfolders), whereas multi-repo separates them into multiple repositories.
Analogously, the relationship between mono-repo and multi-repo can be compared to monolith software architecture and microservice architecture. Mono-repo is intended to perceive the architecture as a whole and as evolving together. It is usually more suitable for small projects or big projects in prototyping. Multi-repo, on the other hand, facilitates collaboration but with extra overhead. Naturally, the more repos you have, the higher the maintenance cost, but also the more autonomous and self-service each repository is.
In this article, we reviewed several patterns of IaC architecture in a technology-agnostic way. All of these patterns can be easily implemented in Spacelift, as the product works natively with both Terraform and Terragrunt.
Your workflows can be extended with other features like policies, and third-party integrations and you even have the possibility to bring your own runner image.
Spacelift’s flexibility extends to other popular IaC technologies like Pulumi, CloudFormation, Ansible, and Kubernetes, where the same patterns keep appearing in slight variations.
We rooted the principles of IaC architecture design in software engineering concepts like inheritance, DRY, project layout, parameterization, and so on. After all, Infrastructure, as Code is coding infrastructure, and recognizing common patterns, is the recommended way to learn and apply IaC.
The most flexible management platform for Infrastructure as Code
Spacelift is a sophisticated SaaS product for Infrastructure as Code that helps DevOps develop and deploy new infrastructures or changes quickly and with confidence.
Terraform Essential Components Cheatsheet
Whenever you're embarking on a new journey or seeking to refine your foundational knowledge.