RousselTMLEARNING

Automation

Ansible best practices: structure, idempotence, security and quality

Ansible best practices: project organization, naming, FQCN, idempotence, modules rather than shell, variables and precedence, Vault, tags, tests, linting, performance and code review.

1. Organizing and naming #

Readable by the whole team.

Repository structure #

Ansible best practices
The six pillars of a maintainable Ansible project.
infra/
├── ansible.cfg
├── requirements.yml           # collections et rôles épinglés
├── inventories/{dev,preprod,prod}/{hosts.yml,group_vars/,host_vars/}
├── playbooks/*.yml
├── roles/<composant>/{tasks,handlers,defaults,templates,meta}
└── .ansible-lint, .yamllint, .gitlab-ci.yml
  • One task = one explicit name describing the result (« Install nginx », not « task 1 »).
  • Use fully qualified FQCN names: ansible.builtin.apt, community.general.ufw.
  • Prefix a role's variables with its name (nginx_port); default values go in defaults/.
  • One inventory per environment; do not duplicate code, only data (group_vars).

2. Idempotence and modules #

Describing the desired state.

Avoiding shell and command #

# ❌ Non idempotent, aucun contrôle d'état
- ansible.builtin.shell: apt-get install -y nginx && systemctl enable nginx

# ✅ Modules d'état
- ansible.builtin.apt: { name: nginx, state: present, update_cache: true, cache_valid_time: 3600 }
- ansible.builtin.service: { name: nginx, state: started, enabled: true }

# Si command/shell est inévitable : rendre le résultat fiable
- name: Initialiser la base
  ansible.builtin.command: /opt/app/init-db.sh
  args: { creates: /var/lib/app/.initialized }          # ne s'exécute qu'une fois
- name: Lire la version
  ansible.builtin.command: app --version
  register: version
  changed_when: false                                   # une lecture ne modifie rien
ansible-playbook site.yml --check --diff          # simulation
ansible-playbook site.yml                            # 1re exécution : changed
ansible-playbook site.yml                            # 2e exécution : changed=0 (idempotent)
Good practice: An idempotent playbook shows changed=0 on the second run. Make this criterion an automatic test in the CI (Molecule checks it).

3. Security #

Secrets and privileges.

The essentials #

# Secrets chiffrés + masqués
ansible-vault encrypt_string 'S3cr3t!' --name vault_db_password
- ansible.builtin.template: { src: app.env.j2, dest: /etc/app/app.env, mode: "0600", owner: app }
  no_log: true

# Privilèges ciblés : become uniquement où nécessaire
- hosts: webservers
  tasks:
    - ansible.builtin.copy: { src: motd, dest: /etc/motd }
      become: true
  • Dedicated SSH keys (ed25519), host_key_checking = True, no plain-text passwords.
  • Pin collection and role versions (requirements.yml) for reproducible runs.
  • Never put a plain-text secret in Git or in CI logs.

4. Quality and performance #

Lint, test, measure.

Tools #

ansible-lint --profile production            # profils : min, basic, moderate, safety, shared, production
yamllint . && ansible-playbook site.yml --syntax-check
molecule test                                         # create, converge, idempotence, verify, destroy

# ansible.cfg : performance
[defaults]
forks = 30
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/facts
callbacks_enabled = profile_tasks
[ssh_connection]
pipelining = True
Anti-patternAlternative
ignore_errors: true everywhereTargeted failed_when / block-rescue
Variables defined in 5 placesOne source per variable (group_vars) and known precedence
500-line « catch-all » roleShort roles, one component each
Handlers never tested--check and Molecule on notification paths
Plain-text sudo passwordRestricted passwordless sudo or --ask-become-pass

Reference diagram and quick check #

Visualize the principle and check it from the command line.

Reference diagram #

Ansible delivery cycle
Each step gates the next one.
ansible-lint && molecule test && ansible-playbook site.yml --check --diff

Related articles

Ansible: the inventory (static, dynamic, groups and variables)

Understand and master the Ansible inventory: INI and YAML files, groups, host and group variables, patterns, …

Ansible: ad-hoc commands

Run immediate actions on a fleet of servers with ansible: the ping, command, shell, copy, apt, service and se…

Ansible: structuring your code with roles

Create, organize and reuse Ansible roles: directory layout, defaults, tasks, handlers, templates, dependencie…

Keep learning

Support us

♥ SUPPORT US

Support RousselTM Learning

Courses, articles, labs and certificates stay free with no intrusive ads. A voluntary contribution, of any amount you choose, pays for hosting, lab environments and new content.

Contribute with PayPal Any amount · card or PayPal accountLearn more
QR code PayPal
Or scan this QR code with your phone

Thank you, every contribution counts!