Contactez-nous

Privilégier les modules Ansible plutôt que `command` ou `shell` pour l'idempotence et la portabilité

Découvrez pourquoi privilégier les modules Ansible natifs aux commandes `command` ou `shell` est crucial pour garantir l'idempotence, la portabilité et la robustesse de vos playbooks.

La puissance des modules dédiés : pourquoi éviter `command` et `shell` autant que possible

Ansible met à disposition une vaste bibliothèque de modules conçus pour interagir avec divers aspects de vos systèmes : gestion des paquets, des services, des fichiers, des utilisateurs, etc. Ces modules sont le coeur de la philosophie d'Ansible. Parallèlement, Ansible propose les modules command et shell, qui permettent d'exécuter des commandes arbitraires directement sur les noeuds gérés, comme vous le feriez dans un terminal. Bien que ces derniers offrent une grande flexibilité et puissent sembler être une solution rapide pour des tâches spécifiques, leur utilisation devrait être limitée et considérée avec prudence.

Privilégier systématiquement les modules Ansible dédiés plutôt que command ou shell est une bonne pratique fondamentale pour plusieurs raisons critiques : l'idempotence, la portabilité, la lisibilité, la maintenabilité et la sécurité. Comprendre ces distinctions est essentiel pour écrire des playbooks robustes et professionnels.

Cette section explique en détail pourquoi les modules natifs sont préférables et dans quels cas (rares) l'utilisation de command ou shell pourrait être envisagée, tout en soulignant les précautions à prendre.

L'idempotence : la promesse d'Ansible tenue par les modules

L'idempotence est l'un des concepts clés d'Ansible. Une opération idempotente est une opération qui, si elle est appliquée plusieurs fois, produit le même résultat que si elle n'avait été appliquée qu'une seule fois. En d'autres termes, après la première exécution qui amène le système à l'état désiré, les exécutions suivantes ne devraient plus apporter de modifications.

Les modules Ansible dédiés sont, pour la plupart, conçus pour être intrinsèquement idempotents. Par exemple :

  • Le module apt ou yum, avec state: present, n'installera un paquet que s'il n'est pas déjà installé. S'il est déjà là, la tâche rapportera ok sans rien faire.
  • Le module file, avec state: directory, ne créera un répertoire que s'il n'existe pas.
  • Le module lineinfile ne modifiera une ligne dans un fichier que si elle ne correspond pas déjà au critère désiré.

Lorsque vous utilisez les modules command ou shell, vous perdez cette garantie d'idempotence intégrée. La commande que vous exécutez sera lancée à chaque fois que la tâche s'exécute, que l'action soit nécessaire ou non. Par exemple, si vous utilisez shell: touch /tmp/monfichier, le fichier sera "touché" (sa date de modification mise à jour) à chaque exécution, même s'il existe déjà. Cela peut entraîner des notifications de handlers inutiles ou des comportements inattendus.

Pour rendre une tâche utilisant command ou shell idempotente, vous devez ajouter une logique supplémentaire, souvent via les clauses creates, removes, ou des conditions when basées sur la sortie d'une autre commande. C'est plus complexe, plus sujet aux erreurs et moins lisible que d'utiliser un module dédié qui gère cela pour vous.

# Mauvaise pratique : non idempotent par défaut
- name: Créer un fichier avec shell
  shell: touch /opt/fichier_cree_par_shell.txt

# Mieux, mais plus verbeux que le module 'file'
- name: Créer un fichier avec shell (tentative d'idempotence)
  shell: touch /opt/fichier_cree_par_shell_idem.txt
  args:
    creates: /opt/fichier_cree_par_shell_idem.txt

# Bonne pratique : utiliser le module dédié
- name: Créer un fichier avec le module file
  file:
    path: /opt/fichier_cree_par_module.txt
    state: touch

Portabilité et abstraction : s'adapter aux différents systèmes

Les modules Ansible sont conçus pour abstraire les différences entre les systèmes d'exploitation sous-jacents. Par exemple, le module package peut être utilisé pour installer des paquets sur différentes distributions Linux (Debian, Red Hat, etc.) sans que vous ayez à vous soucier d'appeler apt-get, yum, ou dnf spécifiquement. Ansible détecte le gestionnaire de paquets du système cible et utilise le module approprié en arrière-plan (apt, yum, etc.).

Si vous utilisez command: apt-get install monpaquet, votre playbook ne fonctionnera que sur les systèmes basés sur Debian/Ubuntu. Pour le rendre portable, vous devriez ajouter une logique complexe avec des conditions when basées sur la variable ansible_os_family.

# Mauvaise pratique : non portable
- name: Installer un paquet avec command
  command: apt-get install -y mon_paquet_exemple
  when: ansible_os_family == "Debian"

# Bonne pratique : portable grâce au module 'package'
- name: Installer un paquet avec le module package
  package:
    name: mon_paquet_exemple
    state: present

Cette abstraction fournie par les modules dédiés rend vos playbooks plus robustes, plus faciles à maintenir et capables de fonctionner sur une plus grande variété de systèmes sans modification.

Lisibilité, maintenabilité et sécurité

L'utilisation de modules dédiés améliore la lisibilité de vos playbooks. La structure des paramètres des modules est standardisée et bien documentée (ansible-doc). Il est plus facile de comprendre l'intention d'une tâche utilisant un module user que de déchiffrer une longue commande useradd avec de multiples options passées via le module shell.

En termes de maintenabilité, les modules Ansible sont maintenus par la communauté et les développeurs d'Ansible. Ils sont testés et mis à jour pour suivre les évolutions des systèmes d'exploitation et des logiciels. Si vous écrivez vos propres scripts shell complexes, vous êtes responsable de leur maintenance et de leur adaptation aux changements.

Concernant la sécurité, les modules Ansible sont généralement écrits en Python et sont conçus pour manipuler les arguments de manière plus sûre que de construire des chaînes de commandes shell, ce qui peut être sujet à des injections de commandes si les variables ne sont pas correctement échappées. Bien que ce risque soit plus prononcé avec le module shell (qui interprète les pipes, les redirections, etc.) qu'avec command (qui n'interprète pas ces caractères spéciaux du shell), l'utilisation de modules dédiés qui valident leurs entrées est généralement plus sûre.

De plus, les modules Ansible fournissent souvent une meilleure gestion des erreurs et des retours d'état. Ils sont conçus pour indiquer clairement si un changement a été effectué (changed) ou non (ok), et fournissent des messages d'erreur structurés en cas d'échec. Obtenir un retour d'état aussi précis avec command ou shell nécessite souvent d'analyser le code de retour et la sortie standard/erreur de la commande, ce qui alourdit le playbook.

Quand `command` ou `shell` peuvent-ils être nécessaires ?

Malgré tous ces avantages, il existe des situations où l'utilisation des modules command ou shell peut être justifiée :

  • Aucun module existant : S'il n'existe absolument aucun module Ansible pour effectuer une tâche très spécifique ou interagir avec un outil propriétaire. Avant d'en arriver là, explorez bien Ansible Galaxy pour des collections communautaires qui pourraient contenir le module adéquat.
  • Commandes simples et ponctuelles : Pour une commande très simple dont l'idempotence n'est pas critique ou est facile à gérer avec creates/`removes`, et pour laquelle créer un module personnalisé serait excessif.
  • Utilisation de fonctionnalités du shell : Si vous avez besoin impérativement de fonctionnalités du shell comme les pipes (|), les redirections (>, <), ou l'expansion de variables d'environnement du shell, alors le module shell est nécessaire (le module command n'interprète pas ces éléments).

Si vous devez utiliser command ou shell :

  • Documentez pourquoi c'est nécessaire dans un commentaire.
  • Faites tout votre possible pour rendre la tâche idempotente en utilisant args: {creates: /chemin/vers/indicateur} ou args: {removes: /chemin/vers/indicateur_avant_action}, ou des conditions when basées sur le résultat d'une commande de vérification préalable (enregistrée avec register).
  • Soyez extrêmement prudent avec les variables injectées dans les commandes pour éviter les problèmes de sécurité, surtout avec le module shell.
  • Utilisez l'option warn: no dans args: pour désactiver l'avertissement qu'Ansible affiche par défaut lors de l'utilisation de ces modules, mais seulement si vous êtes conscient des implications.

En résumé, la règle d'or est : toujours chercher un module Ansible dédié en premier. Ce n'est que si aucune autre solution n'est viable que vous devriez envisager command ou shell, en prenant toutes les précautions nécessaires.