Ansible, WTF

Posted on mer. 29 novembre 2017 in adminsys

Ansible est l'une des techno à la mode en ce moment dans le monde de l'IT. Je bosse avec depuis quelques semaines et j'ai quelques griefs à son encontre que je vous propose de voir ici.

NOTE : Ce blogpost est loin d'être impartial

ELK "as a service"

Au niveau professionel, j'opère une plateforme ELK. Son déploiement s'est effectué avec des scripts bash, il y a quelques années. Il m'a été demandé il y a quelques semaines de revoir notre procédure d'installation de cette plateforme pour automatiser cela avec Ansible.

Notre infrastructure est basée sur celle décrite dans mon blogpost idoine. Nous sommes passé en 3 ans d'une infra à 5 serveurs à une infra à 17 serveurs (dont 12 serveurs pour le cluster Elasticsearch), avec une vision sur l'année prochaine de tout redéployer from scratch pour tout mettre à jour et monter ça à 20 serveurs (+3 pour le cluster ES).

Notre équipe installe cette infrastructure et la maintient en condition de fonctionnement, mais elle est ouverte à l'usage pour les équipes connexes à la notre. Notre chef n+2 aime parler de "as a service".

Pour ce "as a service", les autres équipes peuvent envoyer leurs logs dans notre ELK et les parser. Le point particulier du parsage est qu'il est de leur responsabilité d'écrire ces règles, et de les tester, avant de faire une pull request sur un dépôt git contenant toutes les règles de parsage utilisées.

Ce point est important pour la suite car il a été le déclencheur de ce billet d'humeur.

Ansible est (très) verbeux

Installer logstash, elasticsearch ou kibana en ansible, ça se fait. Surtout qu'on n'a que des serveurs Ubuntu, donc, pas besoin de s'occuper de la distro-agnosticité de notre ansible.

Il faut juste écrire 1045 lignes de YAML répartis dans 20 fichiers, sans compter les rôles pour elasticsearch qui existent déjà, et qu'on n'a pas à faire soit-même. En tout, on a ça :

etienne@computer:~/code/ansible_for_ELK$ find roles/ group_vars/ *.yml -type f | grep -v '.git' | wc -l
186
etienne@computer:~/code/ansible_for_ELK$ wc -l $(find roles/ group_vars/ *.yml -type f | grep -v '.git')
[...]
8579 total

ARE YOU FUCKING SERIOUS ?

8579 lignes dans 186 fichiers ? Sérieusement, WHAT THE FUCK ?

Tout ça pour quelques apt-get install et quelques sed dans des fichiers de conf ?

Je veux bien que ça soit verbeux, mais quand même, c'est pire que du Java !

Et je ne parle pas d'ansible en lui-même (et du python qu'il faut installer sur la machine, plus le virtual env, etc...), sinon ça ne serait pas équitable.

Ha, oui, et ça ne contient pas non plus la configuration du logstash indexer (nous y reviendrons plus tard).

Comparons avec nos anciens scripts, à base de bash et de Dockerfile :

etienne@computer:~/code$ find elk_installation/ -type f | grep -v '.git' | wc -l
56
etienne@computer:~/code$ wc -l$(find elk_installation/ -type f | grep -v '.git')
1437 total

Donc, en l'état actuel, on a 1437 lignes de code/config/commentaires/whatever dans 56 fichiers.

Et dedans, il est aussi compté les 104 lignes dans 4 fichiers README et surtout TOUTE la configuration du logstash indexer (300 lignes dans 21 fichiers).

Petite apparté : nous imposons un formalisme particulier pour notre configuration de logstash indexer pour éviter que toutes les équipes qui l'utilise ne se marchent dessus lors de l'ajout de leurs règles de parsage et qu'elles ne pète pas la conf d'une autre par inadvertance. Cela explique les 21 fichiers de conf pour le logstash indexer

Dis, Ansible, tu m'expliques un peu ?

Exemple

Prenons un exemple judicieusement choisi qui m'a fait m'arracher les cheveux cette semaine. Voici ce que je cherche à faire :

  • git cloner la configuration du logstash indexer sur le serveur
  • changer une chaine de caractère dans les fichiers de conf (il s'agit d'un changement de path entre l'ancienne infra et la nouvelle. Le truc, c'est qu'il faut maintenir les 2 en même temps, le temps de finir de migrer)
  • faire un lien symbolique entre le dépôt fraichement git cloné et le répertoire de conf normal

Voici en bash ce qui était fait :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Remove old conf, for clean setup/update
rm -rf /etc/logstash/conf.d /srv/indexer_conf
# Get the actual conf
git clone ssh://git@git.entreprise.net/server_install/logstash-indexer.git /srv/indexer_conf
# Install the conf in the right place
ln -s /srv/indexer_conf/config /etc/logstash/conf.d
# Replace the patterns_dir path to the right one
sed -i 's#[\t ]+patterns_dir\s+=>\s+\[?".*"\]?#patterns_dir => "/etc/logstash/conf.d/patterns"' /etc/logstash/conf.d/*.conf
# Restart logstash
service logstash restart

Et en Ansible :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
---

- name: Remove old logstash conf
  file:
    state: absent
    path: "/etc/logstash/conf.d"

- name: git clone/pull lastest logstash-indexer conf
  git:
    repo: "{{ indexer_git_repo }}"
    dest: "/srv/indexer_conf"
    force: yes

- name: Link indexer conf to logstash
  file:
    state: link
    src: "/srv/indexer_conf/config"
    dest: "/etc/logstash/conf.d"

- name: find the file to edit
  find:
    paths: "/etc/logstash/conf.d"
    patterns: "*.conf"
  register: tmp_glob

- name: Replace pattern path references
  replace:
    path: "{{ item.path }}"
    regexp: '[\t ]+patterns_dir\s+=>\s+\[?".*"\]?'
    replace: 'patterns_dir => "/etc/logstash/conf.d/patterns"'
  with_items: "{{ tmp_glob.files }}"


- name: Restart logstash
  service:
    name: logstash
    state: restarted

...

Verbeux, n'est-ce pas ?

Mais ce n'est pas le point le plus intéressant. Non, le point intéressant, c'est que Ansible ne sait pas faire le sed -i 'blabla' *.conf d'un seul coup.

Naïvement, on peut penser que le sed pourrait se faire ainsi :

- name: Replace pattern path references
  replace:
    path: "{{ item.path }}"
    regexp: '[\t ]+patterns_dir\s+=>\s+\[?".*"\]?'
    replace: 'patterns_dir => "/etc/logstash/conf.d/patterns"'
  with_fileglob: "/etc/logstash/conf.d/*.conf"

Mais ça ne marche pas. Et vous savez pourquoi ?

Parce que, c'est con, hein ?, mais cette instruction-là est exécutée en local et non en remote.

Vous m'avez bien lu. Toutes les instructions au-dessus (file, git, ...) et en-dessous (service) sont exécutées sur le serveur distant, mais pas le replace. A cause du with_fileglob.

Mais ça, rien ni personne ne nous le dit ! pas même la doc officielle. Et c'est pas l'erreur file not found renvoyée qui va vous aider à le découvrir.

Sérieusement, quel est le crétin qui s'est dit que cette instruction-là précisément, contrairement aux autres, serait exécutée localement ?

Et comme ça n'a pas été prévu de faire un sed * en remote, il faut utiliser le subterfuge des ligne 20-24 : faire un ls * remote, stocker la réponse dans une variable et itérer sur cette dernière dans la commande replace.

J'ai envie de dire "Bien joué" !

Récapitulons

  • Ansible est beaucoup plus verbeux que du bash
  • Ansible exécute parfois des trucs en local, et parfois en remote, mais ne te le dit jamais
  • Ansible a besoin de python + les bonnes libs qui vont bien. Et encore, en fonction de ce que l'on veut faire, il faut en rajouter. Soit en remote, soit en local. C'est toujours la même histoire
  • Les API d'Ansible ne sont pas stables. Sérieusement, il y a des trucs qui sont ajoutés et surtout supprimés (ou des mot-clés qui changent, ce qui est pire) d'une version à une autre
  • Impossible d'exécuter uniquement une partie d'un script/role/playbook/whatever, c'est tout ou rien
  • Ansible a 5 ans, et avant, le truc à la mode, c'était Puppet (tout au moins, dans mon monde). Par quoi devrons-nous le remplacer dans 5 ans ?
  • Quel sera le coût de sorti d'Ansible ? Ansible est compatible uniquement avec Ansible. Le jour où on ne veut plus d'Ansible, comment fait-on ?

(true story pour Puppet. A la mode pendant quelques années, avec formations, conférences, "workshops", etc... on passe tout à Puppet. Et depuis 1 an, Puppet s'est fait détrôné par Ansible)

De l'autre côté :

  • Bash est plus concis, même avec des commentaires pour expliquer des trucs pas évidents (genre des regexp)
  • Bash est exécuté en remote ou en local, mais uniquement l'un ou l'autre, et c'est le user qui maitrise ça
  • Bash est installé en standard sur toutes les distrib, tous les serveurs du monde
  • Bash est stable. Et il n'y a pas d'API, ni de DSL à la con (vivent les buzzword)
  • Tu peux copier-coller le contenu d'un fichier .sh dans un terminal, ça juste marche. Même un petit morceau
  • Bash existe depuis 1989, et est fourni en standard partout. Il est juste pas à la mode des informaticiens bobo-POO-TDD-WTF

Quitte à avoir du python, pourquoi se palucher un DSL en YAML ? On en perd toute la puissance de python...

Pour moi, c'est clair, il n'est pas encore arrivé celui qui détrônera bash.

<3 Bash