Outils pour utilisateurs

Outils du site


tech:bonnes_pratiques_ansible

Ceci est une ancienne révision du document !


Bonnes pratiques Ansible

Principes / Philosophie

Les 12 facteurs

Zen de Python

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 :

    - 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(command) ou 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 :

- 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”

Corriger le code pour chaque avertissement.

Nommer chaque tâche

Les noms de tâche devraient être uniques

Conformité ansible-lint

Conformité indentation yamllint

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

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)
  • 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

Playbook pouvant fonctionner un Dry-Run (--check)

  • check_mode: false quand nécessaire

Ne pas utilisez ignore_errors: true et encore moins pour un playbook entier. Préferez “failed_when:”

Éviter d’utiliser delegate_to surtout, dans les rôles

Bonnes pratiques AWX

Voir :

Ne pas utiliser M(vars_prompt) (remplacé par les “Surveys” et extra-vars)

Ne pas utiliser M(pause) sans timeout

Utiliser des inventaires dynamiques (If you have an external source of truth)

  • 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)

Ne pas utiliser Verbosity à 4 ou 5. Eviter d'augmenter la verbosité si l'inventaire est conséquent

Eviter le cache des facts coté serveur (Enable Fact Storage)

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.

Mettre en place des tests unitaires :

Utiliser le cache que cela est possible :

  • Pour les facts (facts caching)
  • Mais éviter “Enable Fact Storage” qui met les fact dans la base de données
  • Pour les inventaires (cache_plugin, Fact Cache)
[inventory]
cache = True
cache_plugin = memory
cache_timeout = 1800

Bonnes pratiques code

Éviter le code spaghetti

Assert extra_var, controler les inputs des utilisateurs

Assert sur les cibles

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 Use Source Control https://ansible.readthedocs.io/projects/awx/en/24.6.1/userguide/best_practices.html

Utiliser l'encodage UTF8

Remplacer les tabulations par 2 espaces (A config si ce n'est pas le cas dans l'IDE)

Factoriser - Éviter de dupliquer du code - Don't Repeat Yourself (DRY) 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)

Utiliser SonarQube ou équivalent

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à présent / ne pas réinventer la roue

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à

ansible-galaxy search reboot

Timeout après 5s (async)

---

- 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 :

- 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 :

- 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

when: inventory_hostname == ansible_play_hosts_all[0] 

Convention dev

Commencer chaque playbook par contrôler les entrées de l'utilisateur.

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

Utiliser no_log: true pour les taches utilisant des secrets

Troubleshooting untrusted templates

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

Tests

  • tests manuels
  • tests unitaire
  • tests fonctionnels

Autres

Ping

Convention de nommage

Si beaucoup de templates dans le role : utiliser une arborescence du style :

  • files/etc/nginx/sites-available/plop

Voir : https://github.com/openshift/openshift-ansible/blob/master/docs/style_guide.adoc

Convention pour les register

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

Préciser le owner/group/chmod

Préférer les variables a plat plutôt que les variables dictionnaires

endpoint_url:
endpoint_port:

plutôt que

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

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 :

- 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

  • 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
tech/bonnes_pratiques_ansible.1761743570.txt.gz · Dernière modification : de Jean-Baptiste

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki