At my first tech job, I attempted to change proverbial castes. To go from my lowly, near-subhuman station as an “implementation consultant” to something with status: an engineer (ooh, ahh). In particular, I went after an entry-level DevOps engineer role, where I would take on the grunt work of systems administration, infrastructure provisioning, and access requests.
After I got the offer I had a heart-to-heart with the hiring manager, and he said something that’s always stuck with me. What I saw as my prestigious debut into the ranks of the engineer, he wanted to make sure I understood as essentially high-risk manual labor. How thankless success could feel, no matter how much work I’d put in, but more importantly, how severe — and public — the consequences of my errors would be.
“DevOps is a sharp blade”, he said.
By which he meant that when you slip up, when you accidentally remove access, overload a database, or delete an S3 bucket, it cuts deep. When Netflix or GitHub goes down, it's generally not because a front-end engineer made a button the wrong color or because a backend engineer forgot about daylight savings. It is, without fail, usually a fuckup at the infrastructure level. These mistakes are catastrophic.
Infrastructure as Code (IAC) frameworks, which have gone from turbo-nerd side projects to ubiquitous in only a few years, help the people who build and maintain infrastructure make fewer of these costly and embarrassing mistakes. As the volume of code shipped explodes with the rise of AI-powered tooling, having dependable infrastructure to run it on only gets more important. As such, it has a special place in my heart, which is why I’m writing about it.
Before we get into how IAC helps, let's back up and refresh ourselves on the basics of provisioning cloud infrastructure and how easy it is to get in trouble in the AWS console.
Ye Olde Way: Become Ungovernable#
Your garden-variety application these days runs on infrastructure hosted by cloud service providers like AWS and Azure. Most applications need a database to store and retrieve data, a web server to host the web app you click around in, another server to host the API, and a long tail of other things like file storage, search indexes, network load balancers, etc. This plethora of infrastructure needs is exactly why AWS is such a big business; their customers need a lot of things.
You need a database? Sure, I’ll make you a database. On AWS, this is what it looks like to spin up a database.
There are similar menus for the other server types we need, and yet more menus to ensure they can talk to each other over the network and are securely open to the internet so your users can… use them. These menus exist because infrastructure is essentially infinitely configurable. There are simply very many knobs and buttons because every customer needs something slightly different. So you end up with a screen like this.
If your application requirements never change — you never need to handle more traffic, you never add more features, you never need to fix bugs or patch security vulnerabilities — this system works quite well. For my app that predicts how long Chicago train delays will last, this is exactly what I did! I clicked through menus to get the infra I needed and never touched it again.
But real applications differ in (at least) two key ways from my train delay app.
They evolve. New features, growth, and software updates all require changes to infrastructure. A series C company I talked to recently said they make about five changes to their infrastructure a week.
There are many people involved. Different teams need to make infrastructure changes at different times. That same company I talked to has one infra team member for each of their eight engineering teams.
If the infra team in question were to log into the AWS console to create, change, and maintain infrastructure 5 times a week for 8 different teams, a few things would break down badly. And as we know, when things at the infra layer break down, bad, bad, stuff happens. Here’s what you can expect to go down if you stick with click-driven infra management:
Collisions and chaos: Without a centralized and auditable place to make infra decisions, people step on each other’s toes. What to one team may be a routine database version bump may break core functionality in another team’s features. There is no way to tell what some configurations depend on, and nothing to stop two engineers from going in and making the changes that are optimal for their team at the expense of another one.
Quality issues: In application code, the pull request serves as the official proposal of a change, which almost always requires a review and approval before merging in. Provisioning infrastructure via the console lacks any review process, and thus bad configs can sneak in and go straight to production. You are just a single click away from disaster. This is pretty perverse given how dangerous mistakes at this layer can be.
Cost drift: Humans tend to create the infrastructure they need eagerly and tear down the infrastructure they don’t need lazily (shout out my old unused Google Cloud API server, which I get billed 53 cents for every month, luv u). Without a system to track what’s actually needed, resources tend to accumulate, and that costs money.
Unrecoverable disasters: When things do go wrong, and they always will, click-driven infra lacks a coherent way to step back through the changes that were made to return to a stable state. If you’re lucky, someone wrote down the plan and took notes on what they did. But let me ask you. Do you feel lucky?
The point here is that in ye olde days (or today for some companies), infrastructure lacked governance. Which is to say there was no central system to review and approve proposed changes, implement those changes, observe them, and roll them back if needed in a cost-efficient way. To solve this problem, infrastructure engineers borrowed from the very masters they serve: coders.