My first Ansible playbook was a single file. It installed packages, created users, copied configuration, and restarted services. It was a reasonable starting point. The problem began when every change required editing the same file, which had quietly become a miniature operating system.
That was my signal to introduce structure, but not to turn a small playbook into a framework.
Start with a clear boundary#
One workable layout is:
ansible/
ansible.cfg
inventories/
lab/hosts.ini
production/hosts.ini
playbooks/
site.yml
roles/
common/
defaults/main.yml
handlers/main.yml
tasks/main.yml
templates/
kafka/
defaults/main.yml
handlers/main.yml
tasks/main.yml
templates/The playbook should compose roles. A role should expose a small set of variables and own the files it manages.
---
- name: Configure the Kafka platform
hosts: kafka_brokers
become: true
roles:
- common
- kafkaDefaults are the public interface#
Put safe, documented defaults in roles/kafka/defaults/main.yml:
---
kafka_user: kafka
kafka_data_dir: /var/lib/kafka
kafka_service_name: kafkaVariables that must change by environment belong in inventory or an encrypted variable file. Keep role tasks independent of a particular hostname or IP address.
Handlers describe consequences#
Configuration changes should notify a handler rather than restarting a service unconditionally:
- name: Install broker configuration
ansible.builtin.template:
src: server.properties.j2
dest: /etc/kafka/server.properties
owner: root
group: kafka
mode: "0640"
notify: Restart Kafka- name: Restart Kafka
ansible.builtin.service:
name: "{{ kafka_service_name }}"
state: restartedHandlers run once at the end of the play, even if several tasks notify the same handler. That reduces unnecessary restarts.
Use tags sparingly#
Tags are useful for focused operations such as --tags config or --tags packages. They become confusing when every task has several tags. Prefer role boundaries and --limit first; add tags only for a repeatable operational workflow.
Validate the role#
Before applying a role to a cluster:
ansible-playbook -i inventories/lab/hosts.ini playbooks/site.yml --syntax-check
ansible-playbook -i inventories/lab/hosts.ini playbooks/site.yml --check --diffIllustrative second-run summary:
PLAY RECAP **********************************************************
kafka01 : ok=7 changed=0 unreachable=0 failed=0Molecule or a disposable virtual machine can test service startup and idempotence. The most useful test is often simple: run the role twice and confirm that the second run reports no changes.
Good structure is not about creating more directories. It is about defining ownership: inventories describe environments, playbooks describe orchestration, and roles describe reusable capabilities.
