tech:bonnes_pratiques_ansible
Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédenteProchaine révision | Révision précédente | ||
| tech:bonnes_pratiques_ansible [2025/11/03 10:55] – Jean-Baptiste | tech:bonnes_pratiques_ansible [2026/06/03 09:41] (Version actuelle) – Jean-Baptiste | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| + | < | ||
| + | {{tag> | ||
| + | # Bonnes pratiques Ansible | ||
| + | |||
| + | Voir : | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | |||
| + | ## Principes / Philosophie | ||
| + | |||
| + | ### Les 12 facteurs | ||
| + | |||
| + | ### Zen de Python | ||
| + | |||
| + | https:// | ||
| + | |||
| + | ~~~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:// | ||
| + | method: POST | ||
| + | force: true | ||
| + | force_basic_auth: | ||
| + | user: username | ||
| + | password: ' | ||
| + | validate_certs: | ||
| + | # body_format: | ||
| + | body_format: | ||
| + | headers: | ||
| + | Content-Type: | ||
| + | body: | | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | status_code: | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | Éviter autant que possible l' | ||
| + | Éviter d' | ||
| + | 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)**, | ||
| + | * `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: | ||
| + | changed_when: | ||
| + | check_mode: false | ||
| + | | ||
| + | - name: systemd-escape | ||
| + | ansible.builtin.command: | ||
| + | register: unit_systemd | ||
| + | changed_when: | ||
| + | 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: | ||
| + | |||
| + | |||
| + | ### Ansible-lint | ||
| + | |||
| + | Ne pas utiliser de module obsolète " | ||
| + | |||
| + | 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/ | ||
| + | |||
| + | |||
| + | |||
| + | ### Encodage fichiers | ||
| + | |||
| + | |||
| + | Si beaucoup de données préférer les déplacer files/ plutôt que d' | ||
| + | |||
| + | 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 " | ||
| + | find . -type f -wholename " | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | |||
| + | * Systématiquement vérifier le hash, par exemple en utilisant A(checksum) avec M(copy) et M(get_url) | ||
| + | * Voir https:// | ||
| + | * 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: | ||
| + | - ' | ||
| + | - distribution | ||
| + | ~~~ | ||
| + | |||
| + | Playbook pouvant fonctionner un Dry-Run (`--check`) (RA_TEST_N2) | ||
| + | * `check_mode: | ||
| + | |||
| + | |||
| + | Ne pas utilisez `ignore_errors: | ||
| + | Préferez " | ||
| + | |||
| + | |||
| + | É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: | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | ### 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:// | ||
| + | |||
| + | 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' | ||
| + | |||
| + | ~~~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[' | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | |||
| + | ## Bonnes pratiques AWX | ||
| + | |||
| + | Voir : | ||
| + | * https:// | ||
| + | |||
| + | Ne pas utiliser `M(vars_prompt)` (remplacé par les " | ||
| + | |||
| + | 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, | ||
| + | |||
| + | |||
| + | 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' | ||
| + | |||
| + | Ne pas mettre les facts des nœuds dans la base de données - Ne pas activer " | ||
| + | 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`, | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ## 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:// | ||
| + | |||
| + | Tester les playbooks sur un environnement hors prod. | ||
| + | * https:// | ||
| + | |||
| + | Mettre en place des tests unitaires : | ||
| + | * https:// | ||
| + | * Molecule | ||
| + | |||
| + | |||
| + | Utiliser le cache que cela est possible : | ||
| + | * Pour les facts (facts caching) (RA_PERF_N3) | ||
| + | * Mais éviter " | ||
| + | * Pour les inventaires (cache_plugin, | ||
| + | ~~~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' | ||
| + | Playbook dédié : `ansible.builtin.import_playbook: | ||
| + | |||
| + | Utiliser un logiciel de gestion de versions tel que Git (RA_GEN_N1) | ||
| + | Use Source Control https:// | ||
| + | |||
| + | Utiliser l' | ||
| + | |||
| + | 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' | ||
| + | Car "à la pureté, privilégie l' | ||
| + | * 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 `< | ||
| + | |||
| + | 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' | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | ~~~bash | ||
| + | ansible-galaxy search reboot | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | Timeout après 5s (`async`) | ||
| + | ~~~yaml | ||
| + | --- | ||
| + | |||
| + | - name: Test | ||
| + | hosts: localhost | ||
| + | |||
| + | tasks: | ||
| + | - name: Sleep | ||
| + | ansible.builtin.command: | ||
| + | async: 10 | ||
| + | # poll: 5 | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | Voir : https:// | ||
| + | |||
| + | Éviter `command: cat`. Préférez : | ||
| + | ~~~yaml | ||
| + | - name: get actual effective params | ||
| + | slurp: | ||
| + | src: / | ||
| + | become: true | ||
| + | register: all_current_activ_params | ||
| + | |||
| + | - name: show effective params | ||
| + | debug: | ||
| + | msg: "{{ all_current_activ_params.content | b64decode }}" | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | |||
| + | ### run_once | ||
| + | |||
| + | https:// | ||
| + | |||
| + | 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: / | ||
| + | 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' | ||
| + | ~~~yaml | ||
| + | when: inventory_hostname == ansible_play_hosts_all[0] | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ## Convention dev | ||
| + | |||
| + | Commencer chaque playbook par contrôler les entrées de l' | ||
| + | * Grâce au M(assert) et M(fail) | ||
| + | * Grâce à `argument_specs.yml` | ||
| + | * voir https:// | ||
| + | |||
| + | 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 ' | ||
| + | ~~~ | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | ## Sécurité | ||
| + | |||
| + | Mettre les `become` que sur les tâches nécessitant les privilèges, | ||
| + | |||
| + | |||
| + | * RBAC CRUD | ||
| + | * Logs | ||
| + | * AWX | ||
| + | * Callback | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * 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:// | ||
| + | export _ANSIBLE_TEMPLAR_UNTRUSTED_TEMPLATE_BEHAVIOR=fail | ||
| + | |||
| + | https:// | ||
| + | export ANSIBLE_DISPLAY_TRACEBACK=always | ||
| + | |||
| + | |||
| + | ## Performance | ||
| + | |||
| + | Voir : | ||
| + | * https:// | ||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | * 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, | ||
| + | |||
| + | * Do not use special characters other than underscore in variable names, even if YAML/JSON allow them. | ||
| + | |||
| + | Source : https:// | ||
| + | |||
| + | |||
| + | ### name | ||
| + | |||
| + | For example, if you have a task named **Restart server** inside a file named `tasks/ | ||
| + | Source : https:// | ||
| + | |||
| + | ### 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:// | ||
| + | |||
| + | |||
| + | ### 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:// | ||
| + | |||
| + | |||
| + | ### Extra_vars | ||
| + | |||
| + | Voir : https:// | ||
| + | |||
| + | Exemple : `cli_plop` | ||
| + | |||
| + | |||
| + | ### Register | ||
| + | |||
| + | Convention pour les **register**. | ||
| + | Exemple `r_foo` | ||
| + | |||
| + | |||
| + | ### Autres | ||
| + | |||
| + | Les listes seront nommées avec un **s** finals. L' | ||
| + | |||
| + | 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' | ||
| + | |||
| + | 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' | ||
| + | |||
| + | Exemple : | ||
| + | ~~~yaml | ||
| + | - include_tasks: | ||
| + | loop: | ||
| + | - 1 | ||
| + | - 2 | ||
| + | - 3 | ||
| + | loop_control: | ||
| + | loop_var: outer_item | ||
| + | ~~~ | ||
| + | |||
| + | Utiliser `label` dans `loop_control` pour rendre l' | ||
| + | |||
| + | |||
| + | ### Fichiers / templates | ||
| + | |||
| + | Pour les roles contenants beaucoup de fichiers dans " | ||
| + | * files/ | ||
| + | * files/ | ||
| + | * templates/ | ||
| + | |||
| + | |||
| + | |||
| + | ## Annexes | ||
| + | |||
| + | Outils | ||
| + | * Formation | ||
| + | * Ansible-lint | ||
| + | * yamllint | ||
| + | * Sonarqube | ||
| + | * CI/CD Gitlab-CI | ||
