Twelve-Factor App : la méthode Heroku qui a survécu aux conteneurs
En janvier 2018, je tombe sur la méthode Twelve-Factor (et je la range tout de suite parmi les pratiques que je faisais déjà sans le nommer): trois lectures qui se complètent (le site de référence d’Adam Wiggins , l’adaptation aux microservices de Ben Horowitz sur le blog de nginx et le rapport que Kevin Hoffman en tire chez O’Reilly ). Soudain un mot sur ce que je faisais depuis des années. Avant, je connaissais déjà (et j’appliquais plus ou moins) KISS, YAGNI et DRY, puis SOLID; le cycle en V et AGILE faisaient partie du paysage, chacun avec ses défauts que je pouvais énumérer sans avoir besoin qu’on me les explique. Twelve-Factor, lui, ne s’adresse pas au grand public: c’est une checklist d’ingénieur, écrite pour Heroku, qui dit noir sur blanc ce qu’une application doit garantir pour survivre à la production et à ses propres redéploiements.
D’où ça vient
La méthode est écrite par Adam Wiggins, cofondateur d’Heroku, autour de 2011, pour la plateforme qu’il construit (le document synthétise l’observation de centaines d’applications déployées, dans un format explicitement inspiré des livres de Martin Fowler). Il vise le logiciel livré en service, ce qu’on appelle alors un SaaS. Son site n’a guère bougé, sa dernière mise à jour affichée est 2017, ce qui tient autant de la maturité que d’un début de vieillissement dont je reparle plus bas. Le Reactive Manifesto de 2013 dit à peu près la même chose sur un autre axe: un système réactif est résilient, élastique et piloté par les messages, ce qui rejoint les facteurs de concurrence et de disposabilité. 12factor.net
Le socle : code, dépendances, config
Les quatre premiers facteurs tiennent l’application par son socle: un codebase unique suivi en gestion de version, déployé plusieurs fois, un dépôt par application et non un dépôt par environnement; les dépendances déclarées explicitement et isolées, jamais supposées présentes sur la machine cible; la configuration stockée dans l’environnement, séparée du code (c’est le facteur qui vieillit le plus mal, j’y reviens plus bas); et les services externes, base de données, file de messages ou cache, traités comme des ressources attachées que l’on peut remplacer sans toucher au code.
Le cycle : build, release, run
Le cinquième facteur impose une séparation stricte entre construire, publier et exécuter, avec un artefact immuable entre chaque étape. Les processus sont sans état et ne conservent rien entre deux requêtes, ce qui autorise la montée en charge horizontale du huitième facteur. L’application expose son service en écoutant sur un port, elle ne s’appuie pas sur un serveur web externe pour exister. Et la disposabilité ferme la boucle: démarrage rapide et arrêt propre, pour qu’un ordonnanceur puisse tuer une instance sans cérémonie.
La production : parité, logs, admin
Les trois derniers facteurs concernent la vie en production. La parité dev/prod veut que l’écart entre développement, recette et production soit minimal, même langage, mêmes services de support, déploiement continu plutôt que gros lot. Les logs sont un flux d’événements que l’application écrit sur sa sortie standard, sans se soucier de leur destination, que l’environnement d’exécution route ensuite. Et les tâches d’administration, migrations ou scripts ponctuels, s’exécutent comme des processus uniques dans le même environnement que l’application.
Ce qui coince
Le facteur config prend un coup: écrire les secrets dans l’environnement du processus les expose à toute inspection du processus, aux dumps mémoire et aux logs mal filtrés, ce que les gestionnaires de secrets comme Vault corrigent en gardant le secret hors de l’environnement et en l’injectant à la demande. Le facteur logs a lui aussi évolué: écrire sur la sortie standard reste la bonne règle, mais la destination n’est plus un fichier sur disque, c’est un collecteur, Fluentd ou journald, qui l’achemine. Le dogme du processus sans état, enfin, se heurte au réel des bases de données et des files, où l’état est précisément ce qu’on veut conserver (la méthode ne l’ignorait pas, elle rangeait ces états dans des ressources attachées, ce que tout le monde finit par comprendre).
Traduit en conteneurs et en Kubernetes
La bonne surprise est que chaque facteur se traduit presque terme à terme: l’image matérialise le build et le release immuables, le conteneur qui tourne, le run; l’environnement du processus devient une variable d’environnement injectée depuis une ConfigMap pour le non-sensible et un Secret pour le reste; le port binding devient le port exposé par le conteneur derrière un Service; la disposabilité devient une sonde de vivacité et de disponibilité (c’est ce même mécanisme qui rend possible le scale-to-zero, on ne peut endormir une instance que si on sait la reconnaître prête); la concurrence devient le nombre de répliques et l’autoscaling; les logs comme flux finissent sur la sortie standard du conteneur, que le runtime collecte; les tâches d’admin deviennent des Job ou des CronJob; et la parité dev/prod, c’est l’argument même des conteneurs et de Kubernetes, le même environnement partout, du poste de développement à la production. C’est aussi le positif net de la méthode R.A.C.H.E : là où la satire nomme les anti-pratiques, ces douze facteurs nomment les contrats. Je les relis une fois par an. Je ne trouve pas souvent de raison de m’en écarter.