On connait tous l’intérêt et la puissance de l’Infrastructure as Code (IaC) et de Ansible, nous n’y reviendrons donc pas dans cet article.

Ansible est pour vous une évidence mais vous ne savez pas comment tester facilement et proprement l’exécution du playbook de 300 lignes que vous venez de rédiger.

Comment tester mon playbook ?

La première approche (ne mentez pas, on l’a tous fait au début) va consister à exécuter le playbook directement sur la machine cible et à le modifier jusqu’à ce que celui-ci réponde à vos attentes. Deux problèmes se posent :

  1. Si la VM est déjà utilisée (en production, ou même en interne par d’autres développeurs) vous risquez de la rendre inutilisable ou dans le meilleur des cas instable ;
  2. Pour revenir à l’état d’origine de la VM, avant que vous n’exécutiez le playbook (nécessaire pour s’assurer du bon déroulement de celui-ci) vous allez devoir restaurer la VM à partir d’un snapshot ou annuler toutes les commandes précédemment exécutées (rmdir, yum remove, etc.).

Cette approche devient vite pénible et coûteuse.

Vagrant à la rescousse

Afin de simuler au plus prêt l’environnement cible, rien de mieux qu’une machine virtuelle (Virtual Machine).

Pour s’éviter trop de configuration et faciliter son utilisation nous utiliserons Vagrant :

$ vagrant init centos/7
$ vagrant up

Maintenant, ça se complique un peu : pour se connecter en SSH à une machine Vagrant, habituellement c’est la commande vagrant ssh qui est utilisée. Avec Ansible ce n’est pas possible. De plus, la VM tourne localement avec pour IP par défaut 127.0.0.1. Il faut donc dire à Ansible d’exécuter localement le playbook mais pas sur notre machine.

Pour avoir les paramètres de connexion SSH à utiliser par Ansible :

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile .vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

Quatre informations importantes :

  1. Le HostName : 127.0.0.1;
  2. L’utilisateur : vagrant ;
  3. Le port : 2222 ;
  4. L’emplacement de la clé privée.

Rien de mieux pour tester ces informations que de tenter une connexion manuelle :

$ ssh vagrant@127.0.0.1 -p 2222 -i .vagrant/machines/default/virtualbox/private_key

Maintenant que les paramètres de connexion sont validés appliquons les à Ansible :

$ ansible-playbook playbook.yaml -i 127.0.0.1, -e ansible_ssh_port=2222 -e ansible_ssh_private_key_file=.vagrant/machines/default/virtualbox/private_key

Il est tout à fait envisageable de définir un nouvel host dans le fichier d’inventaire d’Ansible mais cette option est à éviter pour deux raisons :

  1. Ajouter un host local à l’inventaire, qui sera ensuite versionné, n’a que peu de sens ;
  2. L’ajouter temporairement pour le retirer avant de commit est sujet à oubli.

Ça y est, vous pouvez maintenant tester localement et de manière efficace votre playbook.

Pour revenir à l’état initial de la VM :

$ vagrant destroy
$ vagrant up

Attention, « delegate_to » vous attend en embuscade

A priori, tout fonctionne correctement. A priori seulement, car si votre playbook est composé de tâches déléguées à la machine l’exécutant, ça se complique.

Prenons le playbook suivant :

- hosts: all
  user: admin
  become: true
  tasks:
  - name: Download artifact from Nexus
    maven_artifact:
      repository_url: https://nexus.ineat-conseil.fr/repository/{{ repo_type }}/
      group_id: "{{ group_id }}"
      artifact_id: "{{ project_name }}"
      version: "latest"
      dest: "./{{ project_artifact }}"
      extension: tar.gz
      username: jenkins
      password: "{{ jenkins_password }}"
    delegate_to: localhost
    become: false

Une simple tâche : le téléchargement d’un artéfact à partir d’un Nexus. La subtilité se trouve dans le delegate_to: localhost. La tâche est déléguée à la machine exécutant le playbook, d’où le « localhost ». Sauf que, lorsque nous exécutons le playbook dans la VM Vagrant, nous avons spécifié pour inventaire 127.0.0.1 (« -i 127.0.0.1, »), qui est identique à localhost.

Il faut donc réussir à distinguer notre machine locale de la VM tournant également en local. Encore une fois, il existe plusieurs façon de procéder.

La première, qui semble la plus simple, est de modifier la configuration du Vagrantfile pour changer l’IP SSH de la VM. Vous pouvez essayer, de mon côté, sur macOS, ça s’est avéré plus compliqué qu’il n’y parait.

Autre façon : réaliser la distinction des deux machines directement avec Ansible. Cela se fait en créant un hostname fictif et en lui assignant les bons paramètres de connexion :

$ ansible-playbook playbook.yaml -i vagrant, -e ansible_ssh_host=127.0.0.1 -e ansible_ssh_port=2222 -e ansible_ssh_private_key_file=.vagrant/machines/default/virtualbox/private_key

Par rapport à la commande précédente les différences sont minimes :

  1. L’inventaire a changé, pour passer de « 127.0.0.1 » à « vagrant » ;
  2. Le paramètre « ansible_ssh_host » a été ajouté.

D’après le manuel de « ansible-playbook », voici la documentation de l’option -i, raccourci pour --inventory :

specify inventory host path or comma separated host list

Mais du coup, à quoi fait référence l’host « vagrant » ? A rien pour le moment puisque celui-ci n’est défini nul part. Techniquement cependant, cela reste un hostname valide. C’est avec la combinaison des arguments ansible_ssh_host et ansible_ssh_port que Ansible est capable de se connecter à la machine et de lui associer le hostname « vagrant ».

Lorsque le playbook est à nouveau exécuté cette fois-ci la distinction entre « localhost » et la VM Vagrant est bien effective.

Mais pourquoi pas Docker ?

Lorsque vous avez lu le titre de cet article ou ses premiers paragraphes, vous vous êtes peut-être dit : pourquoi s’embêter avec une VM quand on peut utiliser un conteneur Docker en quelques secondes ?

Les raisons sont nombreuses mais les deux plus importantes sont, selon moi, les suivantes :

  1. La philosophie de Docker veut qu’un conteneur fournisse un service, contrairement à une VM qui embarque un système d’exploitation et gère plusieurs services en parallèle, ce qui est dans la majorité des cas la cible de votre playbook ;
  2. Les images Docker sont, pour la plupart, les plus minimalistes possibles. Tous les paquets dont vont dépendre votre Playbook ne seront pas forcément présents.

Bonus : configurer la VM Vagrant avec Ansible

Si le playbook que vous testez ne configure pas la machine mais a pour objectif de déployer une application sur un environnement déjà configuré (par exemple), avoir une VM vide ne sera pas l’idéal pour travailler, surtout à chaque destruction de celle-ci.

En alliant Ansible à Vagrant (cet article devient meta !) il devient facile de provisionner une VM grâce au Ansible Provisioner.

Dans le Vagrantfile :

config.vm.provision "ansible" do |ansible|
  ansible.verbose = "v"
  ansible.playbook = "playbook.yaml"
end

Au premier démarrage de la VM, celle-ci se configurera automatiquement à partir du playbook que vous avez indiqué dans sa configuration.