|
| 1 | +--- |
| 2 | +title: "From Chaos to Clarity: Managing Environments with Terragrunt" |
| 3 | +authorId: "nimisha" |
| 4 | +date: 2024-09-10 |
| 5 | +draft: true |
| 6 | +featured: true |
| 7 | +weight: 1 |
| 8 | +--- |
| 9 | + |
| 10 | +Managing multiple environments was a never-ending headache for me. Like many others in the DevOps world, I was responsible for deploying applications across various environments—production, staging, and development. Each of these environments required the same infrastructure, but I found myself writing the same Terraform code over and over again in different folders. The repetition felt inefficient, and the potential for human error only grew with each tweak I had to make for a specific environment. |
| 11 | + |
| 12 | +## The Repetition Trap |
| 13 | + |
| 14 | +At first, my solution was simple: copy-paste. I created three separate folders: one for production, one for staging, and one for development. In each folder, I had the same Terraform files with minor tweaks like different variables or configurations for each environment. It worked, but I soon found myself dealing with the chaos of managing three sets of nearly identical infrastructure code. |
| 15 | + |
| 16 | +Let’s say I needed to update a configuration for my database service. This meant that I had to open each folder, make the same change in three different places, and run Terraform multiple times for each environment. It was a tedious and error-prone process, and as my infrastructure grew, so did the complexity. I knew there had to be a better way. |
| 17 | + |
| 18 | +> My Initial Setup was like this |
| 19 | +
|
| 20 | +```text |
| 21 | +. |
| 22 | +├── production |
| 23 | +│ ├── app.tf |
| 24 | +│ ├── database.tf |
| 25 | +│ └── redis.tf |
| 26 | +├── qa |
| 27 | +│ ├── app.tf |
| 28 | +│ ├── database.tf |
| 29 | +│ └── redis.tf |
| 30 | +└── staging |
| 31 | + ├── app.tf |
| 32 | + ├── database.tf |
| 33 | + └── redis.tf |
| 34 | +``` |
| 35 | + |
| 36 | +The terraform files are contains same resources in different environment but the only difference was the values of the variables. |
| 37 | +That is lot of Repeated Code. |
| 38 | + |
| 39 | +## The Terragrunt Revelation |
| 40 | + |
| 41 | +That’s when I discovered Terragrunt. Terragrunt is a thin wrapper for Terraform that provides extra tools for keeping your Terraform code DRY (Don’t Repeat Yourself). It allows you to manage multiple Terraform modules in a single repository and reuse code across different environments. Terragrunt’s ability to manage remote state, generate configurations, and apply configurations across multiple environments made it the perfect solution for my environment management woes. |
| 42 | + |
| 43 | +How we reduce the repeated code using Terragrunt: |
| 44 | +First step was to make a module of the resources with all the changeable variables exposed, Next was to use them in each environment. |
| 45 | + |
| 46 | +> The new setup with Terragrunt |
| 47 | +
|
| 48 | +```text |
| 49 | +. |
| 50 | +├── terragrunt.hcl |
| 51 | +├── production |
| 52 | +│ ├── app |
| 53 | +│ │ └── terragrunt.hcl |
| 54 | +│ ├── database |
| 55 | +│ │ └── terragrunt.hcl |
| 56 | +│ └── redis |
| 57 | +│ └── terragrunt.hcl |
| 58 | +├── staging |
| 59 | +│ ├── app |
| 60 | +│ │ └── terragrunt.hcl |
| 61 | +│ ├── database |
| 62 | +│ │ └── terragrunt.hcl |
| 63 | +│ └── redis |
| 64 | +│ └── terragrunt.hcl |
| 65 | +└── qa |
| 66 | + ├── app |
| 67 | + │ └── terragrunt.hcl |
| 68 | + ├── database |
| 69 | + │ └── terragrunt.hcl |
| 70 | + └── redis |
| 71 | + └── terragrunt.hcl |
| 72 | +``` |
| 73 | + |
| 74 | +In the `terragrunt.hcl` file, we define the module to use and the |
| 75 | +variables to pass to the module. |
| 76 | + |
| 77 | +```hcl |
| 78 | +# terragrunt.hcl |
| 79 | +terraform { |
| 80 | + source = "gittfr:///exampleorg/app/aws?version=5.8.1" |
| 81 | +} |
| 82 | +inputs = { |
| 83 | + app_name = "my-app" |
| 84 | + environment = "production" |
| 85 | +} |
| 86 | +``` |
| 87 | + |
| 88 | +Similarly in the other terragrunt files we define the module to use and the variables to pass to the module. |
| 89 | +and now for the storing the state file we can use the remote state file. |
| 90 | + |
| 91 | +```hcl |
| 92 | +# terragrunt.hcl |
| 93 | +generate "backend" { |
| 94 | + path = "backend.tf" |
| 95 | + if_exists = "overwrite_terragrunt" |
| 96 | + contents = <<EOF |
| 97 | +terraform { |
| 98 | + backend "s3" { |
| 99 | + bucket = "my-terraform-state" |
| 100 | + key = "${path_relative_to_include()}/terraform.tfstate" |
| 101 | + region = "us-east-1" |
| 102 | + encrypt = true |
| 103 | + dynamodb_table = "my-lock-table" |
| 104 | + } |
| 105 | +} |
| 106 | +EOF |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +This tells terragrunt to create a new file `backend.tf` with the contents of the `contents` block, Even if backend.tf already exists it will overwrite the contents because we have specified `overwrite_terragrunt`. |
| 111 | +This is like a defining a function, Now we need to call it in the files i.e `terragrunt.hcl` files. |
| 112 | + |
| 113 | +> This would look like this |
| 114 | +
|
| 115 | +```hcl |
| 116 | +include "root" { |
| 117 | + path = find_in_parent_folders() |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +here the `find_in_parent_folders` returns the absolute path to the first `terragrunt.hcl` file it finds in the parent folders above the current `terragrunt.hcl` file. |
| 122 | +and the include block tells terragrunt to include all the cofigurations from the file with the path returned by the `find_in_parent_folders` function. |
| 123 | + |
| 124 | +## The Terragrunt Workflow |
| 125 | + |
| 126 | +Here is a high-level overview of the terragrunt workflow: |
| 127 | + |
| 128 | +> We run the command `terragrunt run-all plan` in the root directory. |
| 129 | +
|
| 130 | +This will run the terragrunt plan in each sub directory it finds, Then it reads the `terragrunt.hcl` file in the current directory and does the following: |
| 131 | + |
| 132 | +1. Terragrunt reads the `terraform` block and downloads the specified Terraform module |
| 133 | +2. Terragrunt reads the `inputs` block and passes the variables to the Terraform module |
| 134 | +3. Terragrunt reads the `generate` block and creates the `backend.tf` file |
| 135 | +4. Terragrunt runs Terraform with the specified configurations |
| 136 | +5. Terragrunt stores the Terraform state in the remote state file |
| 137 | +6. Terragrunt returns the output of the Terraform run |
| 138 | + |
| 139 | +This workflow allows me to manage multiple environments with ease. I can make changes to my infrastructure code in a single place and apply those changes across all environments with a single command. Terragrunt’s ability to keep my Terraform code DRY has saved me time and effort, and I no longer have to worry about managing multiple sets of nearly identical infrastructure code. |
| 140 | + |
| 141 | +## How Terragrunt worked for me |
| 142 | + |
| 143 | +1. Centralized Modules: I now have a shared repository of modules that are environment-agnostic. The modules are reusable and cover things like VPCs, ECS clusters, RDS instances, and IAM roles. I no longer need to maintain three different sets of Terraform code. |
| 144 | + |
| 145 | +2. Per-Environment Configurations: For each environment, I have a terragrunt.hcl file that passes environment-specific configurations like the number of instances or database types. These configurations override default values in the shared modules, ensuring that each environment gets what it needs without redundant code. |
| 146 | + |
| 147 | +3. Automated Initialization: Running Terraform in multiple environments is now a breeze. Terragrunt automatically handles running terraform init, terraform plan, and terraform apply for each environment. No more manually navigating into each folder and executing commands multiple times. Terragrunt takes care of initializing each environment’s backend configuration (e.g., for state storage in S3 and locking with DynamoDB). |
| 148 | + |
| 149 | +4. Dependency Management: Another Terragrunt superpower is managing dependencies between infrastructure components. For example, my ECS service depends on the VPC and subnets being provisioned first. Terragrunt’s dependencies block ensures that I deploy resources in the correct order, so my ECS service doesn’t attempt to launch before the network is ready. |
| 150 | + |
| 151 | +## Final Thoughts |
| 152 | + |
| 153 | +Terragrunt isn’t just about making Terraform easier to use. It’s about enabling you to manage infrastructure at scale without the chaos. I no longer have to worry about accidentally mismatching configurations between environments or spending hours applying changes manually. |
| 154 | + |
| 155 | +If you’re tired of duplicating Terraform code across environments, I highly recommend giving Terragrunt a try. It’s been a game-changer in my workflow, and it just might bring the clarity you need in your infrastructure management journey. |
0 commit comments