Ask HN: Why was Terraform created?
But in majority of the cases developers are very much aware of environments their code run on - they know that their containers are stored in ECR, ran in ECS, their data is stored in S3 and RDS.
It is trivial to build a container, upload it to ECR and then deploy it to ECS from a shell script. And it is a lot more readable and comprehensible for a person not familiar with the tool.
Maybe I am the problem and I just don't get the declarative style, where you only describe the wanted state, not the steps you take to achieve that state?
If we assumed by default that our cloud infrastructure provider is AWS, wouldn't it be simple to write a shell script that would call `aws-cli` few times?
I came to that question, when discussing a problem my friend, a DevOps engineer, was having - he wasn't able to get the Azure Resource Group from his `foobar.tf` files and he ended up with something like the following:
cat << EOF
{
"aksvnet": "$(az resource list --resource-group $1 | grep -i \"aks-vnet- | cut -d ":" -f2 | tr -d '", ')"
}
EOF
And what is it? It is a shell script inside of a JSON that was created in the shell! What for are these layers of abstraction? Why does he have to wrap the Resource Group name in a JSON? Why couldn't it be just piped in plaintext format, as all the tools that try to be POSIX-compatible do?UPD: This question is for environments where all the engineers are (to some extent) familiar with %CloudProviderSDK% and bash. And, in my opinion, it's a lot easier to pick up bash and %CloudProviderSDK%, as those are imperative therefore closer to engineers' daily routine, as opposed to Terraform's declarative style. Shell scripts, in my opinion, are just more intuitive by default.
63 comments
[ 0.26 ms ] story [ 110 ms ] threadHaving said that, I (so far) like terraform for the same reason you noted: it's more readable and there is great tooling around it. I like state management and the ability to invoke lower-level components (as the shell breakout in your example) when you really have to.
edit: > the declarative style, where you only describe the wanted state, not the steps you take to achieve that state
That is a good and useful thing. It's called "desired configuration management", Ansible works the same way. When the underlying tool works well, it decides on its own how to implement what you want. If you ever watch terraform deploy a complex (10+ dissimilar resources) infrastructure it comes close to magic how it discovers what already has been done and what still needs to be done and in which order.
As long as you manage to pick it up, which I'm struggling with - you have to approach the problem from an entirely different angle with an entirely different paradigm. I'm struggling with adopting Nix on my personal machine for the same reason - I get `dnf install foo bar` but I don't get `I want foo and bar installed on my machine.` I want my machine to do the stuff only when I explicitly ask it to do stuff and only when I explicitly tell it when to do it.
UPD:
>If you ever watch terraform deploy a complex (10+ dissimilar resources) infrastructure it comes close to magic how it discovers what already has been done and what still needs to be done and in which order.
But why would you have half-ready environments you aren't even aware of state of? Isn't our job about having as little magic, mysteries and unknown variables as possible?
I'm a big opponent of magic and, probably, one of bigger fans of KISS - simple implementation, not simple usage.
Abstractions are the art of revealing the right information.
With an abstraction, you don't pay attention to lower-level details that aren't related to what you're interested in. This makes it much easier to write complex systems.
When someone teaches you how to "hello world", you're able to go and write programs without needing to understand NAND gates & how a CPU works.
I think "terraform lets you work at a level of abstraction above needing to care about what is deployed in what order" is a point in terraform's favour above bash-scripts.
It was also made for non-developers to be able to deploy what someone else built "anywhere"
Also, as stated in a sibling comment, all the commands, expressions and capabilities of Terraform differ based on your $CLOUD, so in the end it's not that universal.
In fact, it's not different from bash - bash is always the same, you just type `az` instead of `aws-cli`.
>In fact, it's not different from bash - bash is always the same, you just type `az` instead of `aws-cli`.
Terraform works on many platforms (https://developer.hashicorp.com/terraform/downloads)
Not all have (or default to) bash
Whereas Terraform is "the same" everywhere
I am using Terraform because using AWS directly was too cumbersome, and I'm happy with it.
The utility of Infrastructure as Code (IAC) is not “you don’t have to understand the target cloud platform”, which is why cloud providers often have their own IAC solutions (e.g., AWS CloudFormation, and the additional abstractions–Serverless Application Model, Cloud Development Kit–built on top of CloudFormation); Terraform adds multiple cloud support on top, which is useful for people with workloads in different clouds or when working with a vendor whose in-house solution is inferior, but that’s not the whole point.
The utility of IAC is that you don’t have to write custom code based on the current and desired state for each state transition change. You write a description of the target state, and the engine handles determining the current state, what transition code is needed, and executing the transition code.
So, if you have terraformed a load balancer balancing load between 2 machines, and change your terraform to declare a load balancer balancing load between 3 machines, it won’t destroy two machines, destroy the load balancer, create a new load balancer, and then create 3 machines.
Instead, it will create a new machine and change the load balancer to know about it, so that your service is uninterrupted.
Problem is that the above isn’t quite true.
Firstly, comparing with the current state is slow, so terraform has a cache of what it thinks the current state is. If they get out of sync, things can get interesting.
Secondly, all changes are done by plugins of varying quality. Your cloud provider may, for example, support reconfiguration of a load balancer, but if the plug-in doesn’t, terraform will destroy and create a new one.
While there are platform specific alternatives like Cloud Formation, learning a new system for each platform would be a pain, and frankly, things like Cloud Formation just aren't as nice to work with compared to Terraform.
But a lot of applications have infrastructure far, far more complex than a single service running in a container and S3/RDS. It may involve a large number of lambdas, networks, API gateways, firewalls, proxies, certificates, etc.
Past a certain point, you need a way of managing all that complexity, keeping things consistent across environments/regions, ensuring all infrastructure changes are tracked and audited, and making it easier to update lots of resources at once, among other things. That's where Terraform helps.
I mean, the engineers could probably convert source into machine into machine instructions, too, but I bet your company would rather leave that to a compiler so that they could work on product.
I would caution against this, even if you solely work on AWS. CloudFormation is much, much slower and has failure modes where it can take hours before the CF service returns to a manageable state. This is slightly better now that they added the ability to ignore failures so you can delete resources manually and then restart the CF rollback process but it’s still much more prone to getting into states where someone knows exactly what needs to be done but the tool won’t let them do it.
We switched to Terraform about half a decade ago over that and it was such a great removal of friction from your development cycle. I tried a small CF project last summer and had to help multiple experienced AWS users with the same class of problems within a week.
Based on what I've read, while Hashicorp tools may look like their only contribution is platform agnostic tooling, a deep dive into the docs reveals a focus on dynamically changing architecture and providing tooling to scale dynamically changing architectures in short time scales to any number of resources (i.e. not just machines / VM / containers / compute resources, but resources like users, user-generated resources, user-generated secrets etc.)
My impressions thus far is that Hashicorp is aware of the variety of alternative tools, that's why their certifications / training / professional services are only available for the tools truly core to supporting dynamic architectures : Terraform, Consul and Vault.
https://www.hashicorp.com/customer-success/professional-serv...
https://www.hashicorp.com/customer-success/enterprise-academ...
https://www.hashicorp.com/certification
Terraform helps you to have a unified way to manage your resources, sure the bash scripts works for you, but what happens if you leave the company? Somebody else has to maintain your shell script.
What happens if somebody else is changing the infrastructure and they're not familiar with your shell script, they need time to dig in to figure things out and then update it, and in best case test it.
And you need to keep your scripts up to date, you need to build in fault tolerance, you need to think how you're going to deploy new resources. How are you going to handle destroying resources?
And on top of that you also need to learn the cloud Provider CLI tools or API to know what kind of calls to execute.
It just provides a standardised way to manage your infra.
(infra engineer in a previous life when Terraform was first released)
Now the problem has grown from just write a few lines of bash script to, "create a script that can handle failure and reverts it so a known state", this is a more complex problem than just creating a resource. And now multiply this for all different resources, EC2, AKS, RDS, Security Groups ... and keep up with the API.
And if somebody joins your team, and wants to contribute to the solutions, they're going to have to understand the codebase.
The reality is infrastructure is commonly in unknown states, whether we like it or not.
You still need to write how to do it, how to bootstrap it and why you do things.
We have a basic tf layer, which does make it well documented, easily extendable and repeatable.
Yes we do destroy the whole setup and recreate it. Often no but still.
After the tf layer, there is only k8s which is also 100% IaC.
Also sry to say but we are experts, learning something like tf should not be a big hurdle.
What I saw in old sysadmin setups: tons of snow flake VMS no one knows why they exist, random setups different security versions on it.
If you don't have any tool to automate things you will not do it.
Feel free to create a small infra setup manually if you prefer, I prefer to codify it once and can recreate it instead of documenting it in some word doc.
Tf is not perfect btw.
An engineers job is to get stuff done and not fight an ideological battleground. The world is impure, and requires impure solutions. Terraform is a tool to get things done, and given the realities of managing stateful infrastructure it does a pretty good job of addressing the failure states of it.
I would suggest that the onus of learning this isn't on other people though. You can start with a baseline assumption that there is a reason these decisions are made. Assume other people are smart, rational actors, and that certain pressures and tradeoffs lead to the belief that the current strategies are the best we have for the state of things overall.
The reason I say this, in particular, is that you questions and answers in this topic have felt close minded and defensive. I say this not as an insult, but highlighting that this is often how I approached things when I was early on in my career. I felt overwhelmed by all the things I needed to know, and wasn't even close to having a mental model of how all the things in a fully working piece of production software fit together. That imposter syndrome led to me discounting things I didn't understand because it was emotionally safer to feel like I knew better than to accept I was barely floating along. Was this thread about a good faith effort to understand why terraform is needed, or is it a rant for why you're mad you have to learn it?
Edit: Phrasing.
However, in the mean time, it's always worth coming at these things with the generosity of mind to assume that smart people made tradeoffs for real reasons, and for your "day job" work in the frameworks adopted until you understand the problemspace well enough to give truly informed feedback on why to do something differently.
Or you could just describe and maintain the desired state in cloudformation, terraform, etc. and let the associated runtime handle termining the changes to apply.
I know which I prefer, but you do you.
You have to be careful not to run your bash script twice or you get another instance/vpc/loadbalancer or whatever.
You run "terraform apply" twice and it does nothing on the second run.
If you start implementing that in your shell scripts you start implementing terraform in bash.
This has absolutely not been my experience. I've worked with a few devs who might be curious to know how everything worked. Most devs I've worked with focus solely on the code they write.
I've also inherited many systems over the years and I'd take the ones managed with tf over bash every single time.
A non exhaustive list of what tf helps with.
1. Being able to know what has changed and what needs to change before you run
2. Managing infra outside of the large cloud providers and being able to combine the two
3. Quickly being able to add a new environment or region to an existing cluster
4. Some requirement has changed and some new policy/tool needs to be stitched in across all your environments
I don't think having a unified interface is the motivation behind Terraform. You still need to understand the underlying resources you are dealing with, Terraform doesn't abstract that at all. The big idea behind Terraform is procedural vs declarative. You can write scripts to bring up all of your infrastructure but what if one of your scripts fails in the middle? What parts of it actually went into effect and which didn't? Can you just re-run it or will the first part now fail because the infrastructure already exists? What if you have several engineers working on the same environment applying scripts that may interfere with one another? What if there was an incident and you made some manual changes and now production is out of sync with what is represented in the script? What if you made some complicated infrastructure changes and you broke something and want to bring everything back to exactly how it was before?
Declarative infrastructure answers all of those questions. It lets you keep track of what the current state of your infrastructure is, and what you want it to be. It automatically identifies areas where the two don't match up and serves as a forcing function for documenting changes to your infrastructure. Declarative infrastructure is more complicated than procedural because bringing up infrastructure is a procedural process so you need a tool to make it into a declarative one and that is not always easy. But if your team's needs get complex enough the tradeoff is well worth it. I honestly can't even imagine life without it.
As a bonus it makes it easy to ship complete infrastructure solutions as re-usable modules that you can compose.
IIRC, it is part of the motivation for Terraform specifically, though not for the broader class of Infrastructure as Code tools, some of which are provider-specific (but also likely quicker to support new platform features.)
In case you need to get the metadata of a resource group you can use this: https://registry.terraform.io/providers/hashicorp/azurerm/la...
I am a very happy Terraform user, here are the benefits for me:
* Very simple workflow that helps prevent unintended consequences - first you write your code, generate a plan, inspect it carefully and only then apply. It is easy to work in a team setting where you can have one person write modules and others supply variables to them.
* I personally don't want to burden myself with Azure Resource Manager, CloudFormation or any other vendor specific IAC tool.
* I don't like other people's bash; there are tools like shell check, but usually a larger infra codebase becomes an awful ad-hoc mess of ENV variables and clever hacks. And infrastructure code is nasty to test and refactor.
Try to keep it simple as possible; anytime you are fighting Terraform it usually means there is a much simpler way to do it. And if there is inherent complexity it could be the wrong thing to do.
In case you need very dynamic behaviour (basically a part of an application) I advise the following - put in terraform the things that are not likely to change often or where the cost of breakage is higher - your virtual networks, DNS configuration, Load Balancers, VPNs, Autoscaling groups, important alerts, etc. Manage more ephemeral workloads in a more general purpose language if there is no straightforward way to do it in the official APIs. I am also very happy user of the AWS CLI in some cases + the cognitect aws libraries for Clojure. However if you need to do something very dynamic it is also likely to be wrong.
Be aware that this experience differs wildly by terraform provider. I can very easily demonstrate that `terraform plan` for AWS consults absolutely zero of the AWS infrastructure, relying solely upon terraform.tfstate. So, great if everyone in the entire organization is entirely disciplined, but in a less disciplined environment terraform's aws provider does nothing to help warn the user of the demonstrably false plan. I don't know what the situation is with the other cloud providers in order to know if that's just "the terraform way" or what
Terraform gives you a commmon language to make sense of it all that can grow as your cloud infra does.
When combined with git and CI/CD it's also an amazing self-service experince. For example you can put the Terraform code that describes your environment in a git repo, and allow any employee to open pull requests, and deploy changes on merge automaically, and require IT approval to merge. Now any engineer can self-service request access to a prod environment (by modifying IAM in Terraform), or configure a production deployment without ever needing actual access to prod. IT gets an audit log, they get a control gate (the code review), and engineers get to self-service changes which reduces the load on IT.
For one, building out anything of complexity in Terraform often involves some trial and error to make sure all the resources are wired together correctly.
For two, there are some Terraform changes that can incur downtime if you just let Terraform destroy and create resources. The solution I employ for this is what I not-so-affectionately call "Terraform surgery". It usually entails standing up the new resource(s), cutting over to them, and then destroying the old ones. Sometimes I will manually remove some stuff from the state file and delete it once Terraform has performed the cutover.
That stuff is possible to do in CI, but it would be inefficient and painful.
To me, the biggest benefit of Terraform is managing changes to cloud resources. You know what resources are added when, why, and by whom. You have a mechanism for promoting changes between environments confidently. And if your infra ever blew up, you would be able to put stuff back.
So engineers open a PR with terraform changes, the CI runs `terraform plan`, and comments on the PR with that diff. So you can see if your terraform is valid, if you're going to be destroying things you shouldn't, etc.
Larger, more complex testing is generally done by locally invoking your terraform on a testing environment. How this is setup kind of depends, but I've seen this work well for testing out complex changes before going to the PR & Plan stage.
This seems like misattribution: you do that everywhere when you’re doing something new - the problem is the underlying gap between the understanding of the problem which you started with and what the underlying services actually require. You can avoid most of that by using modules to reduce the amount of new structure you need to develop and static linters (tflint, tfsec, etc.) to flag things in advance.
> For two, there are some Terraform changes that can incur downtime if you just let Terraform destroy and create resources.
This should never be necessary due to Terraform - that’s one of the major advantages over older tools like AWS CloudFormation. If you’re seeing this as a problem where it’s not imposed by the platform (e.g. many AWS resources won’t allow you to change the name after creation) you can use lifecycle rules to avoid forcing re-creation where that makes sense or to create the replacement first & switch over before deleting the old resource. If you relocated the definitions in your code, you can tell Terraform how to migrate old state non-destructively.
At Nimbus[1], we have been trialling using Terraform for template definitions as users are mostly familiar with it, and it allows them to integrate more easily with existing CI/CD processes. They can easily just add a new Nimbus Workspace to their Terraform and have it spin up a new development environment when their CI requires it.
[1]: https://usenimbus.com - Easy remote development infra for teams
The shell script needs to determine, for each resource, whether it exists; if it does exist, what changes to make and how to translate those into API calls, or if it doesn't exist, how to create it, and to clean up any resources no longer in the desired state.
Attributes of some resource that might exist only after creation need to be fed into other resources…
For even a single resource, over the lifetime of the many changes and adjustments to the resource, that is extremely complicated to do correctly in shell alone.
The declarative "desired state" style is more useful since the steps required to be undertaken often depend on the state of the infrastructure that exists, or doesn't exist.
(additionally, you'll also need to notate state about what infra exists and what doesn't, and store that somewhere, and transmit that state to coworkers … and TF handles that, too. While "its obvious" for some infra — i.e., the resource has a natural key — not all resources do, and often you have to deal with unmanaged resources and not decide to delete them simply because they're not part of your desired state.)
Lastly, you have to handle bugs and design flaws in the APIs. I've worked with a number of platforms where two, valid calls to the API in a shell script are a race condition because the API doesn't support read-your-writes.
All this reinvents the wheel that is TF.
There's also "why does this infra exist?": I can comment TF, I get a commit history and rationales for why infra exists. Shell scripts really push people towards "I'll just #yolo this small change to the infra" … and now, I don't know why the infra is the way it is. Often, I find dev/prd have drifted, or two prod instances of the "same" thing are really different. Comments cut down on this, TF modules really cut down on it, etc.
> And what is it? It is a shell script inside of a JSON that was created in the shell! What for are these layers of abstraction? Why does he have to wrap the Resource Group name in a JSON? Why couldn't it be just piped in plaintext format, as all the tools that try to be POSIX-compatible do?
JSON is a text format. Your shell scripter has piped that into what amounts to a buggy, broken, 5% reimplementation of a JSON parser. Pipe that to `jq`, instead. (You can also use --query on az to reduce the output to something that will be more easily handled by `jq`, but anything --query can do, jq can too, pretty much, and it might be better to have all the code in one language.)
Or just request that data from terraform, by accessing the appropriate attribute of the that resource.