In the beginning
A couple of times I have seen Terraform used to deploy the software that a company is producing. I did it as well the first time I got into contact with Terraform (in part, because I basically translated a set of scripts for the deployment into Terraform). I believe this is wrong. But because I haven’t put together the ideas before, it was difficult for me to defend my position. So here are a few points about why I consider it a non-optimal practice.
One last point, just in case: I love Terraform. I think a few times before I have singed praises to it.
The concept is the concept
Infrastructure and Software are two different concepts, even if nowadays we talk a lot about infrastructure as code. They are related, they work together, but you shouldn’t mix them. They are different layers of your system. There is a n-to-m relation between software and infrastructure, that is: a piece of software can be deployed in multiple infrastructure setups, and a single infrastructure setup can host multiple software artifacts. Trying to couple one to the other always ends raising issues (as seen below)
Infrastructure changes, even on the cloud, are always more complicated than software changes, due to the way they affect all other systems, the speed at which this changes take effect, and the fact that there are more likely to be destructive in nature.
The blast radius
You are deploying a small change and a faulty assumption, or a small error on the infra or software makes your system crash. Rolling back is not always possible. The more things get bundled together, the more likely the scenario happens. Trying to revert or fix becomes complicated because the multiple systems and levels involved.
The speed of changes
Usually infrastructure changes much slower than the software. Trying to tie both together means that you are forcing one to be deployed/released at the pace of the other.
The speed of deployment
When you run Terraform, it has to check what is the current state with the expected state, to see if any change is needed. And then do changes if needed. This takes time. This is, in fact, much slower than deploying software. With software you don’t care about the current state, you just blast on top (well, not completely true, but close enough). If you are deploying using Terraform, each single of your deploys will be slower. How much slower will depend on the amount of infrastructure associated with Terraform. Easiest way to get your CI running quicker: separate infrastructure from software deployment.
There are two different issues happening here. One is a consequence of the speed of changes. You will version your software based on its changes. But the infrastructure doesn’t change. This looks like a minor issue. Until you need to start looking for the time when an issue with the infra was introduced. Even with the help of tools like git-blame (what an awful name), you probably have an order of magnitude (or two or three depending on your commit style) more of commits to check due to mixing two different layers.
Another point is that certain cloud services (Elastic Beanstalk and AWS Lambdas, for example) have the ability to keep track of versions, so is easy to rollback versions using the UI or CLI. Then you deploy a change on your infra, and the version gets changed. Not ideal. If I remember correctly, this happened to me and is when finally took the steps to separate one layer from the other.
There are a few cases where maybe you want to tie them together. But I will always ask myself first, do I really need to do this?
Being able as part of your test suite to create a test environment on the fly, do the test, and take it down is superb. Linking here infra and software makes things easier when setting up any kind of build script around it. Yet, … why don’t do the linking through the build system, rather than using Terraform to deploy the artifact?
This is the case in which I am more comfortable with tying software and infrastructure. Systems like the Serverless Framework can be used to deploy AWS Lambdas or Azure Functions and any associate piece of infrastructure. Of course, you will want to deploy infrastructure that is only used by that specific lambda/function. You have the 1-2-1 relationship where is useful, as soon as you move from it (i.e., a queue that one system uses to send messages and another system receives messages from) the creation of the infrastructure should be separated.
First presentation I ever did at the London Software Crafstmanship Community was about using the right tool for the job, you know:
hammer, everything, nail. Terraform, ARM, CloudFormation, … are tools used to create infrastructure. They can deploy software. But that shouldn’t be your go to. Maybe if you are the single person using that tool for some personal project, then use it however you see fit. As soon as you want to do some commercial product … move away.
A small note: As I was writing this I realized that some of the reasons I put not to link infra and software are reasons to use microservices. Of course, there is more there than just these points. The reasoning behind going into a microservices architecture are far more varied than what I have written in here.