Ansible : mode check

J’entends souvent affirmer que le mode check d’Ansible est compliqué et qu’il augmente le temps de développement des rôles. Pour ma part, j’estime que le bénéfice apporté par la suite est beaucoup plus important que le temps que vous croyez avoir perdu. Oui, car pouvoir simuler notre playbook Ansible sur l’environnement de production avant une mise en production est un luxe dont il est difficile de se passer après l’avoir essayé. Je sais, vous allez me dire que nous l’aurions testé sur les environnements d’anté production mais ce ne sera pas avec les mêmes variables et pas le même environnement. L’erreur est humaine, il est possible d’oublier une variable ou bien d’effectuer une action manuelle sur un serveur en pensant qu’il n’y aura pas de conséquence sur le déploiement. Le mode check vous permettra de les détecter. Lorsque vous aurez lu jusqu’au bout cet article, j’espère que je vous aurais convaincu.

Qu’est-ce que le mode check d’Ansible

En mode check, il y a trois comportements différents possibles sur les modules utilisés

  • Les modules qui effectuent une action de modification, d’ajout ou de suppression vont uniquement vérifier que la source et la destination sont présentes, l’action ne sera pas effectuée
  • Les modules qui effectuent peut-être des actions de modifications mais sans possibilité de contrôle seront ignorés, c’est le cas des modules command et shell.
  • Ceux qui ne font que rendre une information comme les modules find et stat seront exécutés.

Vous comprendrez que le but est d’exécuter le playbook sans impact sur l’environnement.

Comment activer le mode check

Le paramètre --check qui est égal à check_mode: true permet d’activer le mode.

ansible-playbook hello.yml --check

Effectuer une action en mode check

Il est possible d’exécuter une action en mode check mais attention à son utilisation car cela peut avoir un impact sur le fonctionnement de l’application en cours d’exécution.

- name: extract hello.tgz into /opt/hello 
  unarchive:
    src: hello.tgz
    dest: /opt/hello
  check_mode: no

Attention à l’ambiguïté que peut avoir le paramètre check_mode, le fait de mettre la valeur à no signifie que la tâche va être exécutée. Le mode check sera désactivé pour la tâche à laquelle il fait référence.

Voir les différences avec l’option diff

L’option --diff permet d’afficher les différences entre l’état du système et l’état souhaité avec Ansible. Cette option fonctionne également avec le mode check.

ansible-playbook hello.yml --check --diff

TASK [create directory] **********************************************************************************************************************************************************
--- before
+++ after
@@ -1,4 +1,4 @@
 {
     "path": "/opt/hello",
-    "state": "absent"
+    "state": "directory"
 }

Désactiver la vérification d’une tâche

- name: echo hello world
  command: "echo 'hello world'"
  register: result

- debug:
    msg: ""
  when: not ansible_check_mode

L’exemple ci-dessus montre le cas d’une tâche command qui, pour rappel, n’est pas exécutée en mode check. La variable result n’est donc pas créée. Le test not ansible_check_mode permet de ne pas vérifier la tâche debug. Sans cette instruction, la tâche debug serait tombée en erreur car la variable result n’existe pas.

Mauvaise prise en compte du mode check

Lorsque l’on veut prendre en compte le mode check, il faut se poser la question suivante pour chaque tâche. De quoi a besoin la tâche pour fonctionner ?

- name: extract hello.tgz into /opt/hello 
  unarchive:
    src: hello.tgz
    dest: /opt

- name: copy template properties
  template:
    src: "conf.properties.j2"
    dest: "/opt/hello/conf.properties"

À la première exécution en mode check, la copie du template sera en erreur car la tâche unarchive n’a pas été exécutée et le chemin /opt/hello non créé.

On peut résoudre ce problème avec l’exemple ci-dessous :

- name: extract hello.tgz into /opt/hello 
  unarchive:
    src: hello.tgz
    dest: /opt

- name: copy template properties
  template:
    src: "conf.properties.j2"
    dest: "/opt/hello/conf.properties"
  when: not ansible_check_mode

La tâche unarchive n’est pas exécutée, sa source et sa destination seront uniquement vérifiés en mode check. Il n’est donc pas certain que le répertoire /opt/hello existe. On décide de ne pas vérifier la tâche sur le template en mode check pour ne pas avoir d’échec. Mais c’est vraiment dommage de ne pas vérifier s’il ne manque pas de variable pour le template.

Une autre solution consisterait à exécuter la tâche unarchive comme dans l’exemple ci-dessous.

- name: extract hello.tgz into /opt/hello 
  unarchive:
    src: hello.tgz
    dest: /opt
  check_mode: no

- name: copy template properties
  template:
    src: "conf.properties.j2"
    dest: "/opt/hello/conf.properties"

On force l’exécution de la tâche unarchive en mode check. La copie du template fonctionnera car le chemin /opt/hello a été créé par l’extraction de l’archive. Mais c’est la chose à ne surtout pas faire car le mode check devient un vrai déploiement. Cela signifie que si on exécute le mode check en production cela aura un impact.

Bonne prise en compte du mode check

- name: create directory
  file:
    path: /opt/hello
    state: directory
  check_mode: no 

- name: extract hello.tgz into /opt/hello 
  unarchive:
    src: hello.tgz
    dest: /opt/hello

- name: copy template properties
  template:
    src: "conf.properties.j2"
    dest: "/opt/hello/conf.properties"

Dans cet exemple, on s’assure que le répertoire /opt/hello est bien présent même en mode check. Les tâches unarchive et template seront uniquement vérifiées, elles ne seront pas exécutées. Pour information, en mode check la tâche template vérifie l’existence des variables. Dans certains cas, il est également envisageable de passer par un répertoire temporaire si l’on a besoin d’effectuer certaines actions sur le contenu de l’archive.

Quelques pièges à éviter

Un piège à éviter concerne le register, lorsqu’il est utilisé sur un module qui est ignoré en mode check, la variable du register ne sera pas créée. L’exemple ci-dessous ne fonctionnera donc pas en mode check.

  - name: config.json to registered var
    command: cat /opt/hello/config.json
    register: config

  - debug:
      var: config

A contrario, l’exemple ci-dessous fonctionnera en mode check.

  - stat:
      path: /opt/hello/config.json
    register: st

  - debug:
      var: st

Les modules command et shell sont ignorés en mode check lorsque les paramètres creates et removes ne sont pas renseignés (Voir Doc Ansible pour plus de détails). Alors que les modules find et stat sont exécutés en mode check. L’utilisation du register aura un comportement différent en mode check selon le module utilisé.

Mise en place d’un check auto sur les environnements de recette

Inventaire des plateformes

├── platforms
│   ├── perf
│   │   ├── group_vars
│   │   ├── host_vars
│   │   └── inventory
│   ├── prod
│   │   ├── group_vars
│   │   ├── host_vars
│   │   └── inventory
│   ├── prp
│   │   ├── group_vars
│   │   ├── host_vars
│   │   └── inventory
│   └── recette
│       ├── group_vars
│           ├── all
│       ├── host_vars
│       ├── inventory
├── playbook.yml
└── requirements.yml

La solution consiste à avoir une branche git par plateforme sur le projet de l’inventaire d’Ansible. Les modifications de l’inventaire se feront sur la branche develop. Un Weh-hook déclenchera un merge de la banche develop vers les branches des plateformes. Suite à cela, le playbook sera lancé en mode check sur chaque plateforme. En cas d’erreur du playbook, le merge ne sera pas gardé.

Conclusion

Le mode check n’est pas très compliqué à mettre en place, il y a juste quelques concepts à appréhender. Il est préférable de le prévoir dès le début des développements des rôles. L’utilisation du mode check vous permettra de minimiser les erreurs de déploiements et les erreurs humaines. On peut comparer le mode check à des tests unitaires, ils prennent plus de temps en développement au début mais font gagner énormément de temps à l’arrivée.