Ansible

Un outil d'automatisation bien pratique - Partie 1

Changeons de sujet par rapport à mes précédents articles. Travaillant dans le domaine de l'intégration et déploiement continu (CICD), j'ai eu envie de faire découvrir des outils que j'utilise au quotidien. Le premier est Ansible, un outil de déploiement que j'affectionne particulièrement.

Ce premier article sur Ansible vous présentera son histoire, ses concepts, ses principes, et les commandes.

ansibl

Introduction

Ansible est un outil d'automatisation et de déploiement créé en février 2012 par Michael DeHaan. Son nom s'inspire d'un dispositif présenté dans le roman Le Monde de Rocannon écrit par Urusula K. Le Guin dans lequel l'ansible est un moyen de communication plus rapide que la lumière. L'entreprise AnsibleWorks Inc. créée pour apporter un support commercial à l'outil, a été rachetée en 2015 par Red Hat. Depuis, Ansible est intégré à l'offre de l'éditeur spécialisé dans le logiciel libre.

Développé principalement en Python, il est fourni sous licence GNU General Public Licence 3 et s'installe nativement sur les principales distributions Linux du marché telles que Fedora, RHEL, CentOS, Debian, Ubuntu, etc.

Ansible travaille majoritairement avec le monde Linux et d'autres Unix-Like tels que les BSD et MacOS, mais aussi avec Microsoft Windows.

Les concepts

L'un des points clés d'Ansible est que son architecture repose sur l'absence d'agent local sur les cibles. C'est très simple : une machine avec Python installé dessus et pouvant se connecter en SSH sur les cibles pourra faire office de contrôleur. Et voilà !

L'architecture décentralisée d'Ansible fait qu'il ne nécessite aucune machine dédiée pour faire un déploiement. Si le code est versionné par un gestionnaire de sources tel que Git, celui-ci pourra être exécuté depuis n'importe quel machine ayant la possibilité de se connecter en SSH aux noeuds. Ansible dispose de son propre langage déclaratif pour écrire les scripts de déploiement.

Un des défauts de cette architecture cependant est qu'Ansible ouvre et ferme une connexion SSH à chaque action. Par conséquent, il peut devenir assez lent quand on gère un parc conséquent de machines. A ce moment-là, séparer les exécutions ou utiliser des plugins d'optimisation peut avoir un intérêt. Dans la section "plugins" on parlera de Mitogen qui permet d'accélérer les perfs de ce dernier.

Les principes clés d'Ansible sont :

  • Conception minimaliste : les systèmes gérés ne requièrent aucun composant supplémentaire. Seul le contrôleur peut avoir besoin de modules complémentaires selon les besoins. (exemple : modules python-docker pour piloter des containers de ce type)
  • Homogénéité : un déploiement Ansible permet de déployer des environnements homogènes et cohérents.
  • Securité : Ansible ne nécessite pas d'agent sur les noeuds, il n'a besoin que d'une connexion SSH et Python installé dessus.
  • Fiabilité : Ansible repose sur le principe d'indempotence et l'écriture des playbooks se doit d'appliquer cette règle pour garantir que l'exécution sur un noeud aura systématiquement le même résultat.
  • Apprentissage rapide : Les playbooks Ansible sont écrits en YAML et en templates Jinja, des syntaxes strictes mais simples à maîtriser et faciles à lire même pour un non initié.

Voyons plus en détails les concepts et mots clés qui ont été balancés comme ça.

Le contrôleur

Le contrôleur est la machine qui lance un déploiement Ansible. Il peut s'agir de n'importe quel serveur, PC, etc, sur lequel Ansible est installé et qui invoque les commandes /usr/bin/ansible ou /usr/bin/ansible-playbook. On peut avoir plusieurs contrôleurs.

Noeuds managés

Il s'agit des machines qui seront ciblées par le déploiement Ansible, elles sont définies dans l'inventaire qu'il utilise. On parle principalement de "hosts".

L'inventaire

L'inventaire est une liste de hosts, aussi appelé "hostfiles". Il peut s'agir d'une simple liste d'adresses IP ou de noms de machines, mais aussi d'un fichier plus hiérarchisé avec des groupes, des dépendances, ou encore des variables. On le détaillera plus loin dans la section "Construire son inventaire".

Les Modules

Les Modules sont le code qu'Ansible exécute. Un module est un outil dont le but est de remplir une action particulière en prenant des paramètres d'entrées qui peut ensuite retourner un résultat. Les modules d'Ansible vont du simple ping à la gestion d'interfaces réseau, de bases de données, de fichiers, ou encore de commandes système.

Et comme si cela ne suffisait pas, en plus des modules officiels, il est possible d'étendre les possibilités avec des modules communautaires.

Un module peut être invoqué unitairement via la commande ansible ou être exploité dans un ensemble de tâches écrites dans un playbook exécuté par ansible-playbook.

Les tâches

Une tâche est une unité d'action dans Ansible. Elle appelle un module et peut être soit lancée via un Playbook, soit via la ligne de commande directement.

Les Playbooks

Les Playbooks sont un ensemble de tâches ordonnées qui seront exécutées dans l'ordre du script. Ils peuvent également contenir des variables en plus des tâches. Ils sont écrits en YAML et sont faciles à lire, écrire et comprendre. Les playbooks sont lancés par la commande ansible-playbook.

Les rôles

Les rôles sont un ensemble de différents fichiers contenant des tâches, mais aussi des variables, des templates à déployer, des fichiers, des déclencheurs, etc. Un rôle est un moyen d'écrire un code réutilisable et pouvant être contextualisé selon l'inventaire utilisé.

Démarrage rapide

Nous allons rapidement installer Ansible sur notre contrôleur et exécuter notre première commande ad-hoc.

Prérequis

Les prérequis d'Ansible sont simples, mais indispensables.

Sur le contrôleur :

  • OS basé sur Linux (REHL, Debian, CentOS, Fedora...) ou Unix-like (BSD, MacOS...)
    • A ce jour, Windows n'est pas officiellement supporté comme noeud de contrôle.
  • Python 2.7 ou Python 3.5+
  • Une certaine proximité avec les hosts contrôlés permet d'améliorer les performances. S'il est parfaitement possible d'attaquer des serveurs Cloud depuis son PC avec Ansible sur sa connexion domestique, un contrôleur dans le même réseau sera plus efficace.
  • Certains modules peuvent requérir un complément pour fonctionner. Ceci doit être indiqué dans leur documentation.

Sur les cibles :

  • Possibilité de se connecter dessus en SSH. Ansible utilise par défaut SFTP pour transférer ses scripts, mais il peut être configuré pour SCP si besoin.
  • Python 2.6+ ou Python 3.5+
  • Si SELinux est activé, le package libselinux-python doit être présent sur le noeud. Il est parfaitement possible de l'installer via Ansible avant d'utiliser des modules en ayant besoin (comme les modules de copie ou de template notamment).
  • Par défaut, Ansible utilise l'interpréteur Python /usr/bin/python, mais certains peuvent avoir python3 directement. Il est possible de changer ça dans la config sur le contrôleur.

Installer Ansible

Pour ma part, étant sous Fedora, Ansible est disponible directement dans les dépôts de la distribution.

dnf install ansible

Autrement vous pouvez l'installer :

  • Par le gestionnaire de paquets de votre distribution
  • Via l'utilitaire pip de Python
  • Via les sources

Voir les méthodes possibles sur la documentaire officiel.

Configurer Ansible

La configuration d'Ansible se fait au moyen d'un fichier ansible.cfg dont le chargement est défini par un ordre de priorité :

  1. variable d'environnement ANSIBLE_CONFIG avec l'emplacement du fichier
  2. ansible.cfg dans le répertoire courant où est exécuté la commande ansible
  3. ~/.ansible.cfg depuis le répertoire home
  4. /etc/ansible/ansible.cfg

Le contenu du fichier de configuration peut aussi être surchargé directement en variables d'environnement.

Par exemple l'emplacement par défaut de l'inventaire /etc/ansible/hosts peut être changé en modifiant le paramètre inventory dans le fichier ansible.cfg, mais aussi surchargé via la variable d'environnement ANSIBLE_INVENTORY sur son profil.

Pour ma part, j'ai pour habitude de mettre un fichier ansible.cfg avec le code de mes playbooks pour être sûr d'avoir les mêmes pramètres selon l'endroit depuis lequel ils sont exécutés. J'y mets à minima cette valeur pour avoir un récapitulatif à la fin de la durée des tâches :

[defaults]
callback_whitelist = profile_tasks

Ce qui donne à la fin le résultat suivant par exemple :

Tuesday 03 March 2020  18:19:54 +0100 (0:00:01.062)       0:01:00.064 *********
===============================================================================
Install packages-------------------------------------------------------- 37.30s
Gathering Facts --------------------------------------------------------- 9.44s

Lancer une première commande

Tout d'abord, on va créer un inventaire.

Dans un emplacement de votre choix, créez un fichier nommé par exemple inventory. Mettez dedans des machines distances, IP ou FQDN, peu importe.

Exemple :

192.168.1.20
192.168.1.22

Assurez-vous que vous pouvez bien vous connecter en SSH sur ces machines, de préférence avec un échange de clés permettant d'éviter la saisie du mot de passe.

Lançons ensuite un simple ping :

ansible -i inventory all -m ping

Explication de la commande :

  • ansible est la commande ad hoc permettant de faire une exécution unitaire d'une tâche
  • L'argument -i inventory lui indique de charger le fichier d'inventaire que nous avons précédemment créé
  • all lui demande de prendre tous les noeuds de l'inventaire
  • -m ping lui dit de lancer le module "ping"

Résultat :

192.168.1.20 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
192.168.1.22 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Le ping Ansible est très pratique car avec cette rapide commande vous pouvez valider que :

  • Votre inventaire fonctionne
  • Votre connexion SSH fonctionne
  • L'exécution d'un script par Ansible fonctionne

Soit ... Le nécessaire :)

Comprendre l'élévation de privilège

Allons un peu plus loin pour lui faire exécuter une commande permettant de comprendre l'élévation de privilège.

# je suis connecté sur mon contrôleur avec user1
[user1@localhost ~]$ ansible -i inventory all -m command -a "whoami"

De la même manière que le ping, je demande à exécuter le module command en lui donnant l'argument -a pour qu'il lance la commande whoami afin de savoir avec quel utilisateur Ansible s'est connecté.

192.168.1.20 | CHANGED | rc=0 >>
user1
192.168.1.22 | CHANGED | rc=0 >>
user1

Le problème, c'est que mon profil user1 est vite limité car il ne pourra pas faire d'action administrative de lui-même... Cependant, il dispose d'un sudoers lui permettant de passer avec le compte root pour faire des actions spéciales. Cela se traduit dans Ansible par l'argument become et become-user.

[user1@localhost ~]$ ansible -i inventory all -m command -a "whoami" --become
192.168.1.20 | CHANGED | rc=0 >>
root
192.168.1.22 | CHANGED | rc=0 >>
root

Par défaut, --become fait un sudo root. Mais on peut lui spécifier un utilisateur précis.

[user1@localhost ~]$ ansible -i inventory all -m command -a "whoami" --become --become-user jeanmichel
192.168.1.20 | CHANGED | rc=0 >>
jeanmichel
192.168.1.22 | CHANGED | rc=0 >>
jeanmichel

Les commandes Ansible

Nous avons vu la commande ad hoc ansible et parlé rapidement de ansible-playbook, mais ce ne sont pas les seules. Pour les détails sur les arguments, suivez le lien ci après.

ansible

Commande de base permettant d'exécuter un module sur un ensemble de noeuds d'un inventaire.

Exemple pour redémarrer le service HTTPD des serveurs de notre inventaire membres du groupe webservers.

ansible -i inventory webservers -m service -a "name=httpd state=restarted"

ansible-config

La commande ansible-config permet par exemple de consulter le contenu du fichier de configuration actuellement utilisé par Ansible. Elle peut aussi générer un fichier de config en concaténant les différentes disponibles.

ansible-console

Un outil très pratique pour administrer rapidement un ensemble de noeuds ! A la même manière que la commande ad hoc ansible, la console permet cependant de rester en mode intéractif et diffuse les appels sur tous les noeuds concernés.

Exemple pour lancer une session interactive sur tous les Webservers :

ansible-console -i inventory webservers

ansible-doc

Permet de retourner la documentation d'un module ou d'un plugin et génère directement un code exemple pour l'utiliser.

Exemple pour le module yum

# en mode info sur le module

ansible-doc yum
> YUM    (/usr/lib/python3.7/site-packages/ansible/modules/packaging/os/yum.py)

        Installs, upgrade, downgrades, removes, and lists packages and groups with the `yum' package manager. This module only works on Python 2. If you require Python 3 support see the
        [dnf] module.

  * This module is maintained by The Ansible Core Team
  * note: This module has a corresponding action plugin.

OPTIONS (= is mandatory):

- allow_downgrade
        Specify if the named package and version is allowed to downgrade a maybe already installed higher version of that package. Note that setting allow_downgrade=True can make this module
        behave in a non-idempotent way. The task could end up with a set of packages that does not match the complete list of specified packages to install (because dependencies between the
        downgraded package and others can cause changes to the packages which were in the earlier transaction).
        [Default: no]
        type: bool
        version_added: 2.4


# en mode générateur de code

ansible-doc yum -s
- name: Manages packages with the `yum' package manager
  yum:
      allow_downgrade:       # Specify if the named package and version is allowed to downgrade a maybe already installed higher version of that package. Note that setting allow_downgrade=True can make this module behave in a non-
                               idempotent way. The task could end up with a set of packages that does not match the complete list of specified packages to install (because dependencies between the
                               downgraded package and others can cause changes to the packages which were in the earlier transaction).
      autoremove:            # If `yes', removes all "leaf" packages from the system that were originally installed as dependencies of user-installed packages but which are no longer required by any such package. Should be used alone or
                               when state is `absent' NOTE: This feature requires yum >= 3.4.3 (RHEL/CentOS 7+)

ansible-galaxy

Ansible Galaxy est un service complémentaire sur lequel la communauté peut publier du contenu Ansible (playbooks, rôles, etc).

Cette commande permet donc d'obtenir ou d'y pousser son contenu. Elle est aussi pratique pour générer en automatique l'arborescence et la structure d'un rôle ansible avec tous les fichiers pré renseignés. C'est principalement l'utilisation que j'en fais.

Pour initialiser l'arborescence d'un rôle :

ansible-galaxy init mon_nouveau_role --offline
- Role mon_nouveau_role was created successfully

tree mon_nouveau_role/
mon_nouveau_role/
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

ansible-inventory

La commande ansible-inventory permet d'afficher l'inventaire et les variables associées de la manière qu'Ansible le voit lors d'une exécution. Elle peut servir pour contrôler la qualité d'une config par exemple.

ansible-playbook

L'une des commandes les plus utilisées, il s'agit là du lanceur d'un playbook Ansible. L'appel ressemble à la commande ad hoc ansible à la différence que vous n'avez pas besoin de spécifier sur quel groupe de hosts faire l'action. En effet, ceci est spécifié dans le playbook.

Exemple :

ansible-playbook -i inventory mon_playbook.yml

ansible-pull

Cette commande sert à exécuter des scripts Ansible en les récupérant depuis un gestionnaire de sources. Je ne m'en sers jamais, utilisant directement Git pour ça.

ansible-vault

Ansible Vault est une fonctionnalité permettant de chiffrer un fichier avec Ansible. Ceci peut être utile par exemple pour vos fichiers de variables qui contiendraient des mots de passe ou des données sensibles. Il est par contre nécessaire de fournir le mot de passe Vault pour exécuter le code.

Exemple pour chiffrer l'inventaire :

# on affiche l'inventaire actuel

cat inventory

192.168.1.20
192.168.1.22

# on le chiffre en specifiant un mot de passe

ansible-vault encrypt inventory --ask-vault-pass

# on reaffiche le fichier

cat inventory

$ANSIBLE_VAULT;1.1;AES256
643834373838393832346161393539(...)

Vault peut chiffrer aussi un playbook entier, ou seulement une unique variable dans le contenu. Par contre cela devient vite peu pratique car il faut éditer le fichier avec la commande Vault ou le déchiffrer/rechiffrer. On peut également créer un fichier à charger contenant le mot de passe pour l'exécution. Bien évidemment, il convient de ne pas pousser ce fichier de mot de passe sur un repository quelquonque, sans quoi l'intérêt devient nul.

Cette commande dépanne pour un besoin rapide, mais l'utilisation d'un vrai gestionnaire de secrets est recommandée pour des besoins plus conséquents ou dynamiques (rotation de secrets, etc). Ansible dispose de plugins permettant de s'interfacer avec différents gestionnaires du marché, comme Hashicorp Vault ou ceux fournis sur les services Cloud d'Azure, AWS et compagnie par exemple.

Attention ! Vault permet seulement de chiffrer les fichiers. Si vous demandez à Ansible d'afficher le secret dans une tâche debug, il vous l'affichera ! (pensez à utiliser l'action "no_log: true" pour censurer une tâche qui retournerait du contenu sensible)

Fin première partie

Ce premier chapitre vous a présenté les principes d'Ansible et les commandes pour l'exploiter. Le prochain article vous présentera de manière plus détaillée l'inventaire Ansible.