1. Organizing and naming #
Readable by the whole team.
Repository structure #
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
namedescribing 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 indefaults/. - 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 rienansible-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-pattern | Alternative |
|---|---|
ignore_errors: true everywhere | Targeted failed_when / block-rescue |
| Variables defined in 5 places | One source per variable (group_vars) and known precedence |
| 500-line « catch-all » role | Short roles, one component each |
| Handlers never tested | --check and Molecule on notification paths |
| Plain-text sudo password | Restricted passwordless sudo or --ask-become-pass |
Reference diagram and quick check #
Visualize the principle and check it from the command line.
Reference diagram #
ansible-lint && molecule test && ansible-playbook site.yml --check --diff