In different projects, we work with different teams together that use either Terraform or CloudFormation (sometimes also combined with the Serverless Framework) to manage their AWS infrastructure as code. With this post, I wanted to share my personal experience with these tools and explain why I would highly favor Terraform in any AWS project I architect. So, let’s look at the behavior of these IaC tools in different situations.
Handling of drifts
Terraform works with states that are JSON-formatted files stored locally or in S3 (of course, I would always use the S3 variant in production). If the resources described in the state are actually missing on AWS (for instance, deleted manually) or were changed, Terraform will attempt to re-create them or update the state accordingly. This is extremely useful, since sometimes manual changes are made by someone in the project team and is also a simple way of “unstucking” Terraform if it cannot update the resources properly (which may happen in rare cases such as re-creating target groups assigned to a listener of a load balancer).
CloudFormation, on the other hand, will just fail to perform an update if a resource described in the stack is missing. One would have to manually delete the resource description from the stack, update the stack and then redefine the resource (https://aws.amazon.com/premiumsupport/knowledge-center/failing-stack-updates-deleted/). This is quite annoying compared to the way Terraform does it. The Serverless Framework that provides a nice wrapper around CloudFormation also seems to not help with this issue.
It is also always possible (albeit should be used with caution) to edit the terraform state manually and remove the “broken” resources and then continue working with the remaining stack.
Referring to resources created in other projects
Sometimes, some of resources that are relevant to a particular project (stack) are created by another one (for instance, you may want to re-use a KMS key created for one S3 bucket with another one that is created in the current project to save costs). In this case, “pure” (not with Serverless Framework) CloudFormation would require to use exports (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-stack-exports.html). At the first glance, it sounds like a nice solution and works just fine. The problems start once the resource that is exported needs to be updated, because it can’t! To fix this, multiple manual steps are required (https://aws.amazon.com/premiumsupport/knowledge-center/cloudformation-stack-export-name-error/).
In Terraform, remote state (https://www.terraform.io/docs/providers/terraform/d/remote_state.html) can be used for similar purposes and this does not have a similar disadvantage as in the case of CloudFormation. The Serverless Framework provides also a better solution than CloudFormation itself, since it is possible to refer to regular outputs using Serverless (https://www.serverless.com/framework/docs/providers/aws/guide/variables/#reference-cloudformation-outputs).
Replacing a certain resource
Sometimes, it is desirable to force a replacement of a certain resource (for instance, if an EC2 instance is misbehaving or was manually brought to a bad state). With Terraform, a simple “terraform taint” does the trick, but in the case of CloudFormation, a sequence of manual steps is required to achieve the same (https://serverfault.com/questions/539959/is-it-possible-to-force-re-creation-of-ec2instance-or-rdsdbinstance-in-amazo).
Introduction of new AWS Services
It seems, that if a new AWS Service is introduced, it takes the CloudFormation development team longer to provide the corresponding resource than it is in the case of Terraform. Of course, this can be amended by creating a custom resource and using the AWS API directly, but this requires more work than to just use Terraform.
This is the area where Serverless Framework shines: it has multiple plugins than make life of Lambda developers easier by quickly providing the resources they need. Also, the log groups and the corresponding permissions are created by default. Terraform can create all these things too, but provision of Lambdas with Terraform requires low level descriptions of all resources (log groups, permissions, the .zip file that is uploaded, etc.). Of course, there are also ready Terraform modules that could be used for that. “Pure” CloudFormation would also require low level descriptions, so it does not offer any advantages over Terraform for Lambda development like Serverless Framework does.
In summary, both CloudFormation (especially, enhanced by the Serverless Framework) and Terraform can get the job done and provide your Infrastructure as Code, but Terraform seems (at least to me) to be way more robust, versatile, and comfortable. This is why I would always pick Terraform in AWS projects if I have the choice.