Bien sûr c'est tout de suite après m'être enthousiasmé pour Ansible
que je suis tombé sur un os. À l'origine ça n'avait l'air de pas
grand-chose : récupérer des infos sur l'état des partitions et
appliquer une paire de formules pour effectuer le redimensionnement
adéquat. Le truc s'est transformée en une horrible bataille que j'ai
fini par gagner au prix d'un empilement d'astuces d'une lisibilité
douteuse.
J'avais déjà épuisé ma bonne volonté avec la lecture de fichiers YAML
décrivant quelques structures incluant le calcul de valeurs par
défaut. La solution consiste, jusqu'à preuve du contraire, à lire le
fichier, lui faire alimenter un template `Jinja 2` (la solution de
génération de texte retenue par Ansible), calculer certaines valeurs
dedans, écrire le résultat dans un fichier, puis recharger le fichier.
Pour éviter les problèmes de concurrence d'accès il faut le faire pour
chaque machine, ce qui finit par plomber sensiblement les temps
d'exécution.
`Jinja 2` fait par défaut ses calculs avec des entiers sur 32 bits, ce
qui est trop peu et génère des erreurs. Il faut ajouter un filtre de
conversion en entier 64 bits qu'on doit appliquer à chacun membre de
la formule, sachant qu'un tel filtre n'existe pas en standard. Même
s'il est facile à écrire -- c'est juste un bout de Python dans un
répertoire -- la guerre n'est pas gagnée pour autant puisque ce
répertoire est relatif au fichier Playbook lancé initialement, donc si
on veut partager des inclusions Ansible il faut que tous les fichiers
Playbook qui les appellent se trouvent dans le même répertoire, ce qui
implique de casser d'autres trucs qui avaient fini par marcher.
Bref j'avais mis le pied dans une profonde zone d'inconfort, et
commençais à me poser des questions.
=== Chef
Quand on parle d'Ansible, c'est rare qu'il ne soit pas fait mention de
Chef. Chef est peut-être la solution pour certains trucs, mais je n'en
veux pas. Déployer un serveur Chef //et// un SGBDR juste pour déployer
trois serveurs, c'est mal commencer la journée. Le jour ou j'aurai
plus de cent serveurs on en reparlera. Peut-être. Et puis Ruby ne
s'écarte pas suffisamment de Python (données muables par défaut,
typage dynamique) pour que j'en espère quoique ce soit.
=== SaltStack
SaltStack part d'une solution centralisée avec des connexions
persistantes, mais on peut dégrader ce modèle avec Salt-SSH, qui
effecture des connections SSH juste le temps d'exécuter quelques
commandes. SaltStack a l'air mieux fini qu'Ansible, grâce à une vraie
communauté (pas un mec tout seul qui répond "pas le temps" dans les
issues github) mais il en reprend une des tares fondamentales avec les
templates `Jinja 2`.
Arrivé là il est clair que Python est un outil de premier plan pour :
- Trouver des solutions originales à de vrais problèmes.
- Transformer les solutions en problèmes humiliants.
=== PalletOps
En gouglant un peu je suis tombé sur "PalletOps"
http://palletops.com
qui est plus ou moins l'équivalent de SaltStack mais en Clojure. Même
si le développement de PalletOps ralentit fortement vers 2014, on y
trouve toujours de belles idées. La présentation "PalletOps:
Functional Infrastructures"
https://www.youtube.com/watch?v=2U0QdyWQAGM
explique bien les vertus d'une infrastructure programmatique décrite
sous forme d'une suite de fonctions, sans dépendance vis-à-vis
d'autres états (comme Chef et sa base de données). Les fonctions sont
réutilisables sous forme de bibliothèques. Certains comportements sont
adaptables en fonction des plateformes-cible grâce aux multiméthodes
de Clojure. Surtout, PalletOps diffère un maximum l'exécution des
commandes, résolvant par avance tout ce qui est possible, et montrant
les détails dans son plan d'exécution. PalletOps offre la vision la
plus propre et la plus complète de ce qu'on peut imaginer comme
"infrastructure programmatique".
Mais il exhibe les principales caractéristiques des outils développés
en Clojure :
- Des idées géniales.
- Une typographie repoussante.
- Des niveaux d'abstraction parfois durs à suivre.
C'est terrible, les outils bâtis sur Clojure devraient défoncer ceux
en Python à plate couture. Clojure est plus rapide, mieux typé, il
compense l'absence de typage statique par un encadrement très strict
des mutations des données. On peut définir des structures complexes de
façon plus compacte qu'en JSON ou en pur Python. Ça devrait être la
fête mais la sauce ne prend jamais.
=== Retour la case départ : Ansible
Le détour par quelques autres outils a eu au moins la vertu de montrer
qu'Ansible a bon sur les points suivants :
- L'infrastructure en tant que code (idem PalletOps). Pas d'état
caché, pas de SGBDR.
- Un serveur universel : SSH (pas de connections persistantes).
- Un modèle de connexion très réussi (parallélisation, possibilité de
passer des commandes en local).
- Le principe d'indempotence qui autorise à répéter les mêmes
commandes sans risque.
- Des commandes standard qui résolvent les bons problèmes.
- Une approche déclarative.
La "discussion précédente"
https://groups.google.com/d/msg/techos/7QN3haH5--U/5k_5Mu6SEAAJ
s'est arrêtée sur l'affrontement éternel entre l'impératif et le
déclaratif. J'ai beau regarder, dans mes 2000 lignes de code Ansible
je ne vois rien qui sorte beaucoup d'une logique déclarative. Comme
l'approche retenue consiste à repartir d'un serveur tout nu ça n'a
rien d'étonnant, tout ce qui sort d'une approche purement linéaire ce
sont des alternatives du type "Si on veut du NFS alors inclure
l'installation du NFS."
Les ennuis arrivent avec le YAML et `Jinja 2` qui donnent des trucs
assez peu prévisibles, c'est à dire que la remontée d'erreur arrive
beaucoup trop tard. Et si le problème, c'était plutôt l'absence de
typage statique ?
=== Le malentendu
Il y a une sorte de règle empirique stipulant que la configuration du
système d'exploitation doit se faire avec un langage interprété. Par
exemple, toutes les versions de Linux arrivent avec des montagnes de
code Bash pour diverses tâches administratives. Les avantages sont
évidents :
- Pas d'état caché, ce qui s'exécute c'est ce qu'on voit.
- On peut modifier directement sans devoir reconstruire.
- Ça permet d'itérer plus vite.
- On n'a pas d'environnement de compilation à configurer.
Il faut bien reconnaître que comme environnement de développement pour
un langage interprété, Vim offre un rapport puissance-poids
phénoménal. Après, Python a fait son trou notamment parce qu'il
remédiait à quelques atrocités de Perl, qui lui-même remédiait à
quelques atrocités de Bash, et que les adopteurs étaient plus des
administrateurs système que des développeurs d'applications. Disons
que les seconds sont moins durs au mal.
Maintenant, dans le cadre d'une infrastructure programmatique, quel
est le sens de tout cela ? Si les déploiements sont centralisés, plus
besoin de transformer chaque serveur en mini-plateforme de
développement. On peut se permettre les joies d'un environnement
dédié, avec une belle interface graphique, un compilateur incrémental,
un gestionnaire de versions, un débogueur... et un vrai langage, dont
le typage statique trouve les erreurs avant qu'elles n'aient lieu.
Évidemment le choix du langage sera crucial. Pour ceux qui n'auraient
pas d'idée, il suffit d'attendre le prochain épisode.