drio

The wonders of provisioning: Make or Ansible?

I had a discussion recently about doing provisioning. By provisioning I mean automating the process of installing and configuring software in servers. It doesn't have to be only for big, distributed systems. Things can get fragile even with a single machine. You still need some type of automation to manage it.

For that I used a combination of make and shell scripts for a while. That is fine for small projects but it can get out of control pretty quickly. I believe make is a wonderful tool albeit it has a very terse and unforgiving syntax.

When looking for alternatives I found Ansible. I liked the fact that it did not require any process in the server to do its job. I have been using it for a while now (although I am not an expert at all). So which one works best for me? All of them actually.

Ansible provides the basic foundational abstractions that you need for the job: installing software via the package manager, controlling services, managing docker. But at some point you hit a task that requires a level of granularity that can only be achieved using a shell script. Granted, it could perfectly be that I am not versed in Ansible.

And then I use a Makefile to glue everything together. Typically to document how to run the playbooks or other smaller tasks that I don't run via Ansible.

➜ make
Targets:
  play       : Makesure tailscaled is running
  tag/version: To get os and ts version
  tag/install: Install tailscale
  tag/status : Try to connect to the psql port
  tag/up,down: To take TS up or down

And the actual Makefile (very simple one):

HOST?=
USER?=zuviz
INVENTORY?="--inventory-file=./inventory.ini"

help:
	@echo "Targets: "
	@echo "  play       : Makesure tailscaled is running"
	@echo "  tag/version: To get os and ts version"
	@echo "  tag/install: Install tailscale"
	@echo "  tag/status : Try to connect to the psql port"
	@echo "  tag/up,down: To take TS up or down"

play:
	ansible-playbook ${INVENTORY} main.yml 

ansible/requirements:
	ansible-galaxy collection install -r requirements.yml

tag/%:
	ansible-playbook ${INVENTORY} main.yml --tags "$*"

Makefiles are great for capturing bits of knowledge. Sometimes I may perform a task manually against the server and when I repeat it more than once, it goes into the Makefile. I gain two things: first, it offloads the cognitive load of having to remember how to perform that task. Second, the task is now be automated.

Practical nuggets of knowledge

One concrete tip I have for writing Makefiles in this context is to "name space" your targets using names like: "<task_group>/<concrete_task>:". For example, you may group all the docker compose tasks under dc and then have specific tasks under it, like: up, down, status .... So targets look like: dc/up, dc/down, etc...

When working with Ansible, I group tasks with tags. Then, when I run the playbook I can tell Ansible to run only certain groups of tasks. In the previous example, when I run the playbook in the "default mode" it only runs a couple of tasks to make sure the tailscaled service is installed and running properly.

For tasks that I don't want to always run, I tag them with never so it forces you to explicitly tell Ansible you want to run those.

What are you using to automate the process of installing, configuring and capturing operative knowledge in your projects?