Skip to content

Instantly share code, notes, and snippets.

@jamesmurdza
Forked from r33drichards/ansible thoughts.md
Created February 21, 2025 20:51
Show Gist options
  • Save jamesmurdza/ba30739e40872db328f0c9183c895f16 to your computer and use it in GitHub Desktop.
Save jamesmurdza/ba30739e40872db328f0c9183c895f16 to your computer and use it in GitHub Desktop.

Imperative Computer Configuration is Playing a Losing Game

I often categorize games into the buckets of winning and losing games. Some games are both winning and losing games simultaneously, depending on your skill level. An example of this is the difference between amateur and pro tennis. When playing amateur tennis, you are usually playing a losing game. That is to mean, don't lose, and you will win! Most games of amateur tennis are decided by the player who has the least un forced errors. You will win the game by making fewer mistakes than your opponent.

The pros, on the other hand, is a much different story. When you watch two pro tennis players face off, they tend to make very few mistakes. This then becomes a winning game where you have to take an action to acheive victory instead of just try to minimize your mistakes.

Using imperative style configuration management tools like Ansible and docker are ultimately losing games.

Wait! I thought Ansible was Declarative and Idempotent

this is not always true

Ansible playbooks are a series of irreversible actions taken on a computer. And each time you apply a playbook to a machine, you carry the baggage of the previous playbook application with you. This can leave room for nasty bugs when you run the playbook on a fresh machine and you expect a package to be installed but you didn't account for running the playbook on a brand new machine, and the package was installed on a dev machine using a command that for some reason no longer works. This operating environment forces you to take on a mindset of minimizing mistakes and provides less opportunity to take risks.

Docker is an improvement on Ansible but still falls to the same failure modes present with imperative configuration systems. With docker you have to be precise in the order you run commands so as to not invalidate the layer cache in a sub optimal way, resulting in a 20-minute build.

Compare this experience then to tools like Terraform.

With Terraform, instead of calling the AWS API using its CLI, I can use a configuration file to declare the state of resources in my AWS account. This automatically tracks all of my resources and allows me to freely make changes without worrying about modifying the state of my cloud in some way that leaves dangling or orphaned resources. If I don't want an ec2 instance anymore, I can comment it out of my config, and apply it. When I want it back, I uncomment it and then apply it again. Sure I can do the same thing with a shell script, but the declarative paradigm allows this concept to be generalized across all APIs in the case of terraform. I also gain a clear picture of resources and their dependencies that is not always present when developing on shell scripts, and I can guarantee that they are deployed together in the state that I desire them to be. If there is an error in my specification, I get that immediately. I don't have to start from scratch to make sure that the state of my system will resolve to the state that I have declared. Terraform can achieve true idempotency through this declarative model.

When you apply a nixos configuration, it creates a generation of a profile for that computer. If you make a mistake, you can atomically roll back the configuration to its previous state, without provisioning new compute, whether that is building a new container, or spinning up a vm. You don’t have to deal with the fear of making mistakes in this model because you can freely make changes and safely and deterministically guarantee their outcomes will happen again because of the declarative model. This frees you up to be able to experiment, tinker, debug, and iterate at a much faster rate than using traditional tools.

Why not make everything declarative? Lets get rid of the shell and never run commands again!

Except that wouldn't be a great experience either. Declarative APIs are constraints that you put on a system. Constraints are a good thing because of the no free lunch therom. If you add a constraint to a system, you get to unlock helpful properties. With nix, your build is constrained to a chroot sandbox with only your build inputs and no networking access. This constraint is what gives nix its fabled reliability as a build tool. Sometimes it’s a bit annoying to deal with this constraint, but you get a good bargain for the relative cost of paying that constraint in most scenarios.

If you thought this was interesting check out https://branislavjenco.github.io/desired-state-systems/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment