Outils pour utilisateurs

Outils du site


tech:bonnes_pratiques_ansible

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
tech:bonnes_pratiques_ansible [2025/11/04 14:36] Jean-Baptistetech:bonnes_pratiques_ansible [2026/06/03 09:41] (Version actuelle) Jean-Baptiste
Ligne 1: Ligne 1:
 +<!DOCTYPE markdown>
 +{{tag>Brouillon CA Ansible}}
  
 +# Bonnes pratiques Ansible
 +
 +Voir :
 +* https://docs.ansible.com/ansible/latest/tips_tricks/ansible_tips_tricks.html
 +* https://www.ansible.com/blog/ansible-best-practices-essentials
 +* https://www.redhat.com/sysadmin/10-great-ansible-practices
 +* https://redhat-cop.github.io/automation-good-practices/
 +* https://www.gaige.net/separating-ansible-roles-for-fun-and-profit.html
 +* https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_reuse_roles.html
 +* https://ansible.readthedocs.io/projects/awx/en/24.6.1/userguide/best_practices.html
 +* https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html
 +* https://docs.ansible.com/ansible/2.8/user_guide/playbooks_best_practices.html
 +* https://timgrt.github.io/Ansible-Best-Practices/ansible/playbook/
 +* https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_advanced_syntax.html
 +* https://spacelift.io/blog/how-to-improve-ansible-performance
 +
 +
 +## Principes / Philosophie
 +
 +### Les 12 facteurs
 +
 +### Zen de Python
 +
 +https://fr.wikipedia.org/wiki/Zen_de_Python
 +
 +~~~python
 +import this
 +~~~
 +
 +
 +## Bonnes pratiques propres à Ansible I
 +
 +### Idempotents
 +
 +* Privilégier les modules natifs
 +* Utiliser les handlers pour le redémarrage de services
 +* Utiliser `changed_when` **A(creates)** ou **A(removes)**
 +
 +
 +Codes de retour corrects \\
 +Change_when \\
 +Si Appel API -> Changed (par défaut)
 +
 +Exemple :
 +~~~yaml
 +    - name: call api
 +      register: plop
 +      uri:
 +        url: https://tower.acme.fr/api/v2/job_templates/93/launch/
 +        method: POST
 +        force: true
 +        force_basic_auth: true
 +        user: username
 +        password: 'P@ssw0rd'
 +        validate_certs: false
 +        # body_format: form-urlencoded
 +        body_format: json
 +        headers:
 +          Content-Type: "application/json"
 +        body: |
 +          {
 +              "extra_vars": {
 +                  "git_path": "/project/plop",
 +                  "git_user": "gittoken"
 +              }
 +          }
 +        status_code: 201
 +~~~
 +
 +
 +Éviter autant que possible l'appel au **M(shell)** \\
 +Éviter d'utiliser les **M(script)** et **M(command)** \\
 +Privilégier **M(command)** à **M(shell)** \\
 +Si variables Jinja en argument à M(shell) : utiliser `quote` pour échapper les caractères spéciaux.
 +Pour **M(command)**, **M(shell)**, **M(script)**, contrôler le code de retour avec 
 +* `register`
 +* `failed_when`
 +* `changed_when`
 +ou bien : penser à utiliser **A(creates)** ou **A(removes)**
 +
 +
 +Exemple :
 +~~~yaml
 +- name: Check if my_package is installed
 +  command: dpkg-query -W my_package
 +  register: my_package_check_deb
 +  failed_when: my_package_check_deb.rc > 1
 +  changed_when: my_package_check_deb.rc == 1
 +  check_mode: false
 +  
 +- name: systemd-escape
 +  ansible.builtin.command: /usr/bin/timeout --kill-after=15 10 /usr/bin/systemd-escape -p --suffix=mount {{ CIFS_MOUNT_PATH | quote }}
 +  register: unit_systemd
 +  changed_when: false
 +  check_mode: false
 +  failed_when:
 +    - unit_systemd.rc != 0       # OK
 +    - unit_systemd.rc != 124     # Timeout. SIGTERM
 +    - unit_systemd.rc != 137     # Timeout. SIGKILL
 +~~~
 +
 +Utiliser les **handlers** pour redémarrer les services
 +Mais attention avec les modules (import*), les handlers ne sont pas déclenchés par défaut. Faire `ansible.builtin.meta: flush_handlers`
 +
 +
 +### Ansible-lint
 +
 +Ne pas utiliser de module obsolète "Deprecated" (RA_QUA_N2)
 +
 +Corriger le code pour chaque avertissement.
 +
 +Nommer chaque tâche
 +
 +Les noms de tâche devraient être uniques (RA_QUA_N3)
 +
 +Conformité ansible-lint
 +
 +Conformité indentation yamllint
 +
 +Préciser le owner/group/chmod pour M(copy), M(template)...
 +
 +
 +
 +### Encodage fichiers
 +
 +
 +Si beaucoup de données préférer les déplacer files/ plutôt que d'utiliser M(copy) A(content)
 +
 +Deux types de fichiers :
 +* Fichier texte de tailles réduite
 +  * Taille < 100K ou mime-encoding = us-ascii / utf-8
 +* Binary large object (BLOB)
 +  * Taille > 100k ou mime-encoding = binary
 +
 +
 +
 +#### BLOB - fichiers binaires et fichier textes volumineux
 +
 +~~~bash
 +find . -type f -wholename "*/files/*" -size +100k
 +find . -type f -wholename "*/files/*" -size -100k -exec file --mime-encoding {} + | awk -F: '/binary/ { print $1 }'
 +~~~
 +
 +
 +
 +* Systématiquement vérifier le hash, par exemple en utilisant A(checksum) avec M(copy) et M(get_url)
 +  * Voir https://github.com/openshift/openshift-ansible/blob/master/docs/best_practices_guide.adoc
 +* Devrait être dépôt dédié sépare de Git 
 +  * Exemple : Artifact / S3 (MinIO) / NAS (NFS) / HTTP
 +* Garder une arborescence similaire aux petits textes : le chemin doit contenir le nom de rôles ou du playbook
 +* Une référence unique. Un seul rôle (ou un seul playbook) fait appel directement à ce fichier.
 +* Si deux roles ou besoin de ce même fichier : créer un 3em rôle et faire un dépendance de rôle.
 +
 +
 +
 +## Bonnes pratiques propres à Ansible II
 +
 +Si pas besoin des facts : mettre gather_facts à false (RA_PERF_N3)
 +
 +Si besoin de facts récuper seulement les facts utiles (RA_PERF_N3)
 +
 +~~~yaml
 +    - name: Get minimal facts
 +      ansible.builtin.setup:
 +        gather_subset:
 +          - '!all'
 +          - distribution
 +~~~
 +
 +Playbook pouvant fonctionner un Dry-Run (`--check`) (RA_TEST_N2)
 +* `check_mode: false` quand nécessaire
 +
 +
 +Ne pas utilisez `ignore_errors: true` et encore moins pour un playbook entier.
 +Préferez "failed_when:" (RA_QUA_N1)
 +
 +
 +Éviter d’utiliser `delegate_to` surtout, dans les rôles (RA_QUA_N1)
 +
 +
 +Pour les templates et les fichiers, systématiquement sauf si non applicable mettre en commentaire que ce fichier est géré par Ansible. (RA_QUA_N1)
 +
 +Exemple :
 +`all.yml`
 +~~~yaml
 +ansible_managed: This file is managed by ansible. Manual changes are likely to be overwritten !
 +~~~
 +
 +
 +### Pièges
 +
 +#### Run_once
 +
 +
 +
 +run_once will be executed at each serial execution in the play. That means, if you choose serial = 1, it will be asked to confirm as many times as the quantity of targets on the play.
 +
 +Check Ansible docs: https://docs.ansible.com/ansible/latest/user_guide/playbooks_strategies.html#running-on-a-single-machine-with-run-once
 +
 +When used together with serial, tasks marked as run_once will be run on one host in each serial batch. If the task must run only once regardless of serial mode, use `when: inventory_hostname == ansible_play_hosts_all[0]` construct.
 +
 +Attention aux slicing !
 +
 +
 +
 +### Limiter l'usage des ressources
 +
 +~~~yaml
 +- name: Installation d'un logiciel sur plusieurs serveurs avec throttle
 +  ansible.builtin.apt:
 +    name: nginx
 +    state: present
 +  async: 600  # Exécution en mode asynchrone avec un délai maximum de 10 minutes
 +  poll: 5     # Vérification toutes les 5 secondes
 +  throttle: 3  # Limite à 3 installations simultanées
 +  when: inventory_hostname in groups['webservers']
 +~~~
 +
 +
 +
 +## Bonnes pratiques AWX
 +
 +Voir :
 +  * https://ansible.readthedocs.io/projects/awx/en/24.6.1/userguide/best_practices.html
 +
 +Ne pas utiliser `M(vars_prompt)` (remplacé par les "Surveys" et extra-vars) (RA_QUA_N1)
 +
 +Ne pas utiliser `M(pause)` sans timeout (RA_QUA_N1)
 +
 +Utiliser des inventaires dynamiques (If you have an external source of truth) (RA_QUA_N3)
 +* single source of truth (SSOT) architecture, or single point of truth (SPOT)
 +
 +
 +Variable Management for Inventory - Keeping variable data along with the hosts and groups definitions (see the inventory editor) is encouraged, rather than using group_vars/ and host_vars/
 +
 +Autoscaling - Using the “callback” feature to allow newly booting instances to request configuration is very useful for auto-scaling scenarios or provisioning integration.$
 +
 +Larger Host Counts - Consider setting “forks” on a job template to larger values to increase parallelism of execution runs. Voir : Strategy, Mitogen, Slicing, Async (Asynchronous) (RA_PERF_N3)
 +
 +Ne pas utiliser Verbosity à 4 ou 5. Eviter d'augmenter la verbosité si l'inventaire est conséquent
 +
 +Ne pas mettre les facts des nœuds dans la base de données - Ne pas activer "Enable Fact Storage"
 +Le cache des facts doit être sur les managed_hosts et non coté serveur (RA_GEN_N1)
 +
 +Ne pas faire de `command: ansible-galaxy` ni de `shell: ansible-galaxy`, mais utiliser la manière native de AAP / AWX (RA_QUA_N1)
 +
 +
 +
 +
 +## Bonnes pratiques IT
 +
 +
 +Logger (via AWX, ou via un callback plugin, ARA Records Ansible...). Et utiliser la directive `no_log:` pour les secrets. 
 +Voir aussi : https://docs.ansible.com/ansible/latest/reference_appendices/config.html
 +
 +Tester les playbooks sur un environnement hors prod.
 +* https://www.commitstrip.com/fr/2013/04/16/tester-ou-ne-pas-tester-telle-est-la-question/?
 +
 +Mettre en place des tests unitaires :
 +* https://www.commitstrip.com/fr/2017/02/08/where-are-the-tests/
 +* Molecule
 +
 +
 +Utiliser le cache que cela est possible :
 +* Pour les facts (facts caching) (RA_PERF_N3)
 +* Mais éviter "Enable Fact Storage" qui met les fact dans la base de données
 +* Pour les inventaires (cache_plugin, Fact Cache)
 +~~~ini
 +[inventory]
 +cache = True
 +cache_plugin = memory
 +cache_timeout = 1800
 +~~~
 +
 +
 +## Bonnes pratiques code
 +
 +Éviter le code spaghetti
 +
 +Assert extra_var, controler les inputs des utilisateurs (RA_SEC_N1)
 +
 +Assert sur les cibles (RA_SEC_N2)
 +
 +Faire les contrôle de plus tôt possible.
 +
 +Exemple :
 +Contrôle avant d'effectuer la connexion : playbook localhost avec gather_facts=false
 +Playbook dédié : `ansible.builtin.import_playbook: _assert_extra_vars.yml`
 +
 +Utiliser un logiciel de gestion de versions tel que Git (RA_GEN_N1)
 +Use Source Control https://ansible.readthedocs.io/projects/awx/en/24.6.1/userguide/best_practices.html
 +
 +Utiliser l'encodage UTF8 (RA_GEN_N1)
 +
 +Remplacer les tabulations par 2 espaces (A config si ce n'est pas le cas dans l'IDE) (RA_GEN_N1)
 +
 +Factoriser - Éviter de dupliquer du code - Don't Repeat Yourself (DRY) (RA_GEN_N3)
 +Dans la mesure du possible, seulement se répéter est mieux que d'écrire un code difficilement lisible.
 +Car "à la pureté, privilégie l'aspect pratique."
 +* En créant des rôles en les appelant avec des arguments / variables
 +* `module_defaults` (If you frequently call the same module with the same arguments)
 +* En utilisant import* et include* (attention aux notify avec include)
 +* En utilsant `block`
 +
 +Utiliser SonarQube ou équivalent (RA_GEN_N4)
 +
 +
 +
 +Petits textes
 +* Utiliser le charset `utf-8` (ou `us-ascii` si aucun caractère unicode)
 +* Convertir le charset `iso-8859-1` et autres en `utf-8`
 +* Devrait être dans `<role_name>/files/` (ou dans `files/` si appelé directement depuis un playbook)
 +
 +Encodage fichier
 +* UTF-8
 +* Pas de tabulation (Utiliser un IDE avec greffon pour Ansible ou configurer son IDE pour remplacer les tabulations par deux espaces)
 +
 +
 +
 +### Utiliser les modules déjà existant / ne pas réinventer la roue (RA_GEN_N1)
 +
 +
 +
 +~~~bash
 +ansible-doc -l |grep reboot
 +ansible-doc ansible.builtin.reboot
 +~~~
 +
 +
 +Si pas présent, chercher si une collection / un rôle n'existe pas déjà 
 +* https://docs.ansible.com/ansible/latest/collections/index_module.html
 +* https://catalog.redhat.com/en/platform/red-hat-ansible
 +* https://galaxy.ansible.com/ui/
 +
 +~~~bash
 +ansible-galaxy search reboot
 +~~~
 +
 +
 +Timeout après 5s (`async`)
 +~~~yaml
 +---
 +
 +- name: Test
 +  hosts: localhost
 +
 +  tasks:
 +    - name: Sleep
 +      ansible.builtin.command: /bin/sleep 60
 +      async: 10
 +      # poll: 5
 +~~~
 +
 +
 +Voir : https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_checkmode.html#check-mode-dry
 +
 +Éviter `command: cat`. Préférez :
 +~~~yaml
 +- name: get actual effective params
 +  slurp:
 +    src: /proc/cmdline
 +  become: true
 +  register: all_current_activ_params
 +
 +- name: show effective params
 +  debug:
 +    msg: "{{ all_current_activ_params.content | b64decode }}"
 +~~~
 +
 +
 +
 +### run_once
 +
 +https://github.com/ansible/ansible-lint/issues/1026#issuecomment-685849603
 +
 +Ansible ignores `run_once` with the free strategy which means your tasks are run many times, once for each valid inventory host
 +
 +Si `run_once`, toujours préciser le `delegate_to` ou `when: inventory_hostname == `
 +
 +Il est aussi possible de faire quelque chose comme :
 +~~~yaml
 +- command: /opt/application/upgrade_db.py
 +  when: inventory_hostname == webservers[0]
 +~~~
 +
 +Les tâches marquées comme `run_once` seront exécutées sur un hôte dans chaque série de lot. Si la tâche ne doit s'exécuter qu'une seule fois quel que soit le "serial" mode , utilisez
 +~~~yaml
 +when: inventory_hostname == ansible_play_hosts_all[0] 
 +~~~
 +
 +
 +
 +
 +
 +
 +## Convention dev
 +
 +Commencer chaque playbook par contrôler les entrées de l'utilisateur. \\
 +* Grâce au M(assert) et M(fail)
 +* Grâce à `argument_specs.yml`
 +  * voir https://pypi.org/project/ansible-argument-spec-generator/
 +
 +Contrôler que la cible correspond bien \\
 +Exemple : 
 +* Bonne version OS
 +* Agent pas déjà installé via autre autre procédure...
 +* Espace disque et autres ressources disponibles sur la cible
 +
 +
 +
 +
 +## Portée des variables
 +
 +`DEFAULT_PRIVATE_ROLE_VARS`
 +
 +M(ansible.builtin.include_roles) and M(ansible.builtin.import_roles)
 +C(public)
 +
 +### Import vs include
 +
 +Modules :
 +* ansible.builtin.import_playbook
 +* ansible.builtin.import_role
 +* ansible.builtin.import_tasks
 +* ansible.builtin.include_role
 +* ansible.builtin.include_tasks
 +* ansible.builtin.include_vars
 +
 +~~~
 +You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.
 +~~~
 +
 +
 +
 +
 +## Sécurité
 +
 +Mettre les `become` que sur les tâches nécessitant les privilèges, et non sur tous le playbook (RA_SEC_N1)
 +
 +
 +* RBAC CRUD
 +* Logs
 +  * AWX
 +  * Callback
 +    * https://toshio.fedorapeople.org/ansible/latest/plugins/callback.html
 +    * https://docs.ansible.com/ansible/latest/collections/index_callback.html
 +    * community.general.syslog_json
 +
 +Utiliser `no_log: true` pour les taches utilisant des secrets (RA_SEC_N1)
 +
 +Pour les données sensibles utiliser ansible-vault ou les Crendential AWX (RA_SEC_N1)
 +
 +Troubleshooting untrusted templates 
 +# https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_12.html
 +export _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR=fail
 +
 +https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_core_2.19.html#untrusted-templates
 +export ANSIBLE_DISPLAY_TRACEBACK=always
 +
 +
 +## Performance
 +
 +Voir :
 +* https://www.reddit.com/r/ansible/comments/11jc4c0/mitogen_speedup_the_actual_value/
 +* https://prohoster.info/fr/blog/administrirovanie/uskoryaem-ansible-s-pomoshhyu-mitogen
 +* https://docs.redhat.com/fr/documentation/red_hat_ansible_automation_platform/2.6/html-single/performance_tuning_for_ansible_automation_platform/index
 +
 +* Mettre en cache les facts (fact_caching) (mais sur les noeuds, pas en DB) (RA_PERF_N3)
 +* Analyser les temps d’exécution anormalement long `callback_whitelist = timer, profile_tasks`
 +* Utiliser Mitogen
 +* Garder les tunnels SSH ouverts
 +
 +
 +Eviter de boucler innutilement - vérifier si le module prends des listes (RA_PERF_N2)
 +
 +~~~yaml
 +- name: Install packages
 +  ansible.builtin.package:
 +    name: "{{ item }}"
 +    state: present
 +  loop:
 +    - curl
 +    - wget
 +~~~
 +
 +~~~yaml
 +- name: Install packages
 +  ansible.builtin.package:
 +    name:
 +      - curl
 +      - wget
 +~~~
 +
 +
 +### Tests
 +
 +* tests manuels
 +* tests unitaire
 +* tests fonctionnels
 +
 +## Autres
 +
 +### Ping
 +
 +Voir [[Ansible Ping ICMP]]
 + 
 +
 +
 +## Convention de nommage 
 +
 +### Naming things
 +
 +* Use valid Python identifiers following standard naming conventions of being in snake_case_naming_schemes for all YAML or Python files, variables, arguments, repositories, and other such names (like dictionary keys).
 +
 +* Do not use special characters other than underscore in variable names, even if YAML/JSON allow them.
 +
 +Source : https://redhat-cop.github.io/automation-good-practices/#_naming_things
 +
 +
 +### name
 +
 +For example, if you have a task named **Restart server** inside a file named `tasks/deploy.yml`, this rule suggests renaming it to **deploy | Restart server**
 +Source : https://ansible.readthedocs.io/projects/lint/rules/name/#nameprefix
 +
 +### role-name
 +
 +(snake case)
 +Role names must contain only lowercase alphanumeric characters and the underscore _ character. Role names must also start with an alphabetic character.
 +Source : https://ansible.readthedocs.io/projects/lint/rules/role-name/
 +
 +
 +### var-naming
 +
 +...
 +Variable names must contain only lowercase alphanumeric characters and the underscore _ character. Variable names must also start with either an alphabetic or underscore _ character.
 +...
 +role_name_ as a prefix
 +...
 +Source : https://ansible.readthedocs.io/projects/lint/rules/var-naming/
 +
 +
 +### Extra_vars 
 +
 +Voir : https://github.com/openshift/openshift-ansible/blob/master/docs/style_guide.adoc
 +
 +Exemple : `cli_plop`
 +
 +
 +### Register 
 +
 +Convention pour les **register**.
 +Exemple `r_foo`
 +
 +
 +### Autres 
 +
 +Les listes seront nommées avec un **s** finals. L'emploi du pluriel indique plusieurs éléments possibles
 +
 +Définir et respecter une convention de nommage
 +
 +
 +Convention pour les variables, il doit être possible de distinguer deux types (fonctionnel) de variables : 
 +* Entrées utilisateurs
 +* Variables internes
 +
 +Nommer les templates Jinja avec l’extension **j2** (RA_CONV_REQ)
 +
 +
 +Préférer les variables a plat plutôt que les variables dictionnaires (RA_CONV_OPT)
 +~~~yaml
 +endpoint_url:
 +endpoint_port:
 +~~~
 +
 +plutôt que
 +
 +~~~bash
 +endpoint:
 +  url:
 +  port:
 +~~~
 +
 +
 +### Boucles
 +
 +De préférence nommer la variable de boucle (`loop_var`) à la place d'utiliser le nom par défaut `item` (RA_CONV_OPT)
 +
 +C'est plus lisible, et cela permet un fonctionnement non équivoque en cas de boucles imbriqués.
 +
 +Il est recommandé de définir une convention de nommage pour la variable de boucle. Par exemple d'avoir toujours un suffixe **_item**
 +
 +Exemple :
 +~~~yaml
 +- include_tasks: inner.yml
 +  loop:
 +    - 1
 +    - 2
 +    - 3
 +  loop_control:
 +    loop_var: outer_item
 +~~~
 +
 +Utiliser `label` dans `loop_control` pour rendre l'affichage plus lisible
 +
 +
 +### Fichiers / templates
 +
 +Pour les roles contenants beaucoup de fichiers dans "files/" privilégier une arborescence comme suit (RA_CONV_OPT)
 +* files/etc/nginx/sites-available/plop
 +* files/etc/systemd/system/plop.service
 +* templates/etc/nginx/site-aviable/plop.j2
 +
 +
 +
 +## Annexes 
 +
 +Outils
 +* Formation
 +* Ansible-lint
 +* yamllint
 +* Sonarqube
 +* CI/CD Gitlab-CI

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki