TL:DR
Terraform is a tool that made it easy, efficient and fast for us to build out our Infrastructure as a Code (IaaC). However, when it came to Day 2 operations, we were running into some issues where Terraform was destroying our resources, causing us headaches that were slowing us down. Here is a recap of the problems we are facing and how we plan to avoid these issues in the future by effectively using loops and choosing the right keys.
The Problem: Terraform Destroying Resources
Terraform is an Infrastructure as a Code (IaaC) tool widely used to manage infrastructure. It allows you to define required resources in a declarative manner. All our cloud infrastructure such as VPCs, Kubernetes Clusters, RDS, S3 Buckets, Service Accounts are provisioned and maintained by Terraform.
Our product is growing at a rapid pace and Terraform allows us to build our IaaC in a fast and easy manner. But on Day 2, we faced an issue that Terraform was taking unexpected actions that were destroying our resources. We were using Loops incorrectly, and were not able to change existing resources anymore, just add new ones. We refactored our code and would like to share it to help you avoid a headache.
Using Loops in Terraform
Sometimes we need to create multiple resources at once. So what do we do?
I'm kidding, of course, we would not make it like this as it would break the DRY principle. We use loops instead.
Let’s make a small recap about available loops in Terraform/HCL:
- Count Loops: The simplest way to make a loop based on predefined or counted numbers to create resources.
- For_each Loops: This loop allows you to iterate over variables in maps and sets based on keys to create resources.
- For Loops: These loops don’t allow you to directly create resources, but allow you to filter or transform lists, sets, tuples, or maps.
Count Loop
Let's look for some examples of how we can create multiple S3 buckets with the count loops:
After terraform apply our S3 buckets are created. We can see them listed in our state:
But later, we decided that we don't need the demo1 bucket anymore, let's delete it:
Running terraform apply again, and what do we see?
But wait, why would Terraform to destroy 2 resources when we only deleted 1?
So demo1 and demo2 will be deleted, and only after that demo2 will be recreated.
The reasons is key of state objects. When we use count it marks aws_s3_bucket.bucket[X] with numbered index. And object with index 1 had a different name before.
It might be ok for some stateless resources, but not for S3 buckets with data. So how can this be avoided?
For_each Loop
Let’s try switching to another loop: for_each
But this snippet will not work, because for_each can only iterate over a map or a set of strings. Here we should use a for loop, which can help us transform tuple to map where key would be presented as bucket.name.
The above for loop transformed the buckets variable:
State Migration
However, if we apply these updates, it still will lead to deleting all our buckets. To avoid it, we need to migrate our state first:
Let’s check the state list:
Now if we apply a new code we will get:
Change as expected
The key right now is a bucket name that we specified via the for loop, so if we delete demo1 bucket now, other buckets will stay untouched as we expected.
Conclusion
We can also do more complex keys. For example:
We are striving to keep keys simple but unique. Mostly we use a combination of name + namespace, but you can also add your domain or environment name. Keep in mind, changing the key requires either deletion or one more state movement, so I don’t recommend including frequently changing parameters such as tags or access policies to your keys.
As you have seen before, choosing the right key can help you avoid state manipulation in the future. Chose them carefully from the beginning.
Install in minutes and instantly receive actionable intelligence.