gwift-book/source/part-1-workspace/maintainable-applications/12-factors.adoc

14 KiB
Raw Blame History

12 facteurs

Pour la méthode de travail et de développement, nous allons nous baser sur les The Twelve-factor App - ou plus simplement les 12 facteurs.

Lidée derrière cette méthode, et indépendamment des langages de développement utilisés, consiste à suivre un ensemble de douze concepts, afin de:

  1. Faciliter la mise en place de phases dautomatisation; plus concrètement, de faciliter les mises à jour applicatives, simplifier la gestion de lhôte, diminuer la divergence entre les différents environnements dexécution et offrir la possibilité dintégrer le projet dans un processus dintégration continue ou déploiement continu

  2. Faciliter la mise à pied de nouveaux développeurs ou de personnes souhaitant rejoindre le projet, dans la mesure où la mise à disposition dun environnement sera grandement facilitée.

  3. Minimiser les divergences entre les différents environnemens sur lesquels un projet pourrait être déployé

  4. Augmenter lagilité générale du projet, en permettant une meilleure évolutivité architecturale et une meilleure mise à léchelle - Vous avez 5000 utilisateurs en plus? Ajoutez un serveur et on nen parle plus ;-).

En pratique, les points ci-dessus permettront de monter facilement un nouvel environnement - quil soit sur la machine du petit nouveau dans léquipe, sur un serveur Azure/Heroku/Digital Ocean ou votre nouveau Raspberry Pi Zéro caché à la cave - et vous feront gagner un temps précieux.

Pour reprendre de manière très brute les différentes idées derrière cette méthode, nous avons:

#1 - Une base de code unique, suivie par un système de contrôle de versions.

Chaque déploiement de lapplication se basera sur cette source, afin de minimiser les différences que lon pourrait trouver entre deux environnements dun même projet. On utilisera un dépôt Git - Github, Gitlab, Gitea, …​ Au choix.

12 factors 1

Comme lexplique Eran Messeri [1], ingénieur dans le groupe Google Developer Infrastructure, un des avantages dutiliser un dépôt unique de sources, est quil permet un accès facile et rapide à la forme la plus à jour du code, sans aucun besoin de coordination. Ce dépôt ne sert pas seulement au code source, mais également à dautres artefacts et formes de connaissance:

  • Standards de configuration (Chef recipes, Puppet manifests, …​)

  • Outils de déploiement

  • Standards de tests, y compris tout ce qui touche à la sécurité

  • Outils de déploiement de pipeline

  • Outils danalyse et de monitoring

  • Tutoriaux

#2 - Déclarez explicitement les dépendances nécessaires au projet, et les isoler du reste du système lors de leur installation

Chaque installation ou configuration doit toujours être faite de la même manière, et doit pouvoir être répétée quel que soit lenvironnement cible.

Cela permet déviter que lapplication nutilise une dépendance qui soit déjà installée sur un des sytèmes de développement, et quelle soit difficile, voire impossible, à répercuter sur un autre environnement. Dans notre cas, cela pourra être fait au travers de PIP - Package Installer for Python ou Poetry.

Mais dans tous les cas, chaque application doit disposer dun environnement sain, qui lui est assigné, et vu le peu de ressources que cela coûte, il ne faut pas sen priver.

Chaque dépendance pouvant être déclarée et épinglée dans un fichier, il suffira de créer un nouvel environment vierge, puis dutiliser ce fichier comme paramètre pour installer les prérequis au bon fonctionnement de notre application et vérifier que cet environnement est bien reproductible.

Warning
Il est important de bien "épingler" les versions liées aux dépendances de lapplication. Cela peut éviter des effets de bord comme une nouvelle version dune librairie dans laquelle un bug aurait pu avoir été introduit.[2]

#3 - Sauver la configuration directement au niveau de lenvironnement

Nous voulons éviter davoir à recompiler/redéployer lapplication parce que:

  1. ladresse du serveur de messagerie a été modifiée,

  2. un protocole a changé en cours de route

  3. la base de données a été déplacée

  4. …​

En pratique, toute information susceptible de modifier un lien applicatif doit se trouver dans un fichier ou dans une variable denvironnement, et doit être facilement modifiable. En allant un pas plus loin, cela permettra de paramétrer facilement un container, en modifiant une variable de configuration qui spécifierait la base de données sur laquelle lapplication devra se connecter.

Toute clé de configuration (nom du serveur de base de données, adresse dun service Web externe, clé dAPI pour linterrogation dune ressource, …​) sera définie directement au niveau de lhôte - à aucun moment, nous ne devons trouver un mot de passe en clair dans le dépôt source ou une valeur susceptible dévoluer, écrite en dur dans le code.

#4 - Traiter les ressources externes comme des ressources attachées

Nous parlons de bases de données, de services de mise en cache, dAPI externes, …​ Lapplication doit être capable deffectuer des changements au niveau de ces ressources sans que son code ne soit modifié. Nous parlons alors de ressources attachées, dont la présence est nécessaire au bon fonctionnement de lapplication, mais pour lesquelles le type nest pas obligatoirement défini.

Nous voulons par exemple "une base de données" et "une mémoire cache", et pas "une base MariaDB et une instance Memcached". De cette manière, les ressources peuvent être attachées et détachées dun déploiement à la volée.

Si une base de données ne fonctionne pas correctement (problème matériel?), ladministrateur pourrait simplement restaurer un nouveau serveur à partir dune précédente sauvegarde, et lattacher à lapplication sans que le code source ne soit modifié. une solution consiste à passer toutes ces informations (nom du serveur et type de base de données, clé dauthentification, …​) directement via des variables denvironnement.

#5 - Séparer proprement les phases de construction, de mise à disposition et dexécution

  1. La construction (build) convertit un code source en un ensemble de fichiers exécutables, associé à une version et à une transaction dans le système de gestion de sources.

  2. La mise à disposition (release) associe cet ensemble à une configuration prête à être exécutée,

  3. tandis que la phase d'exécution (run) démarre les processus nécessaires au bon fonctionnement de lapplication.

Parmi les solutions possibles, nous pourrions nous pourrions nous baser sur les releases de Gitea, sur un serveur dartefacts ou sur Capistrano.

#6 - Les processus dexécution ne doivent rien connaître ou conserver de létat de lapplication

Toute information stockée en mémoire ou sur disque ne doit pas altérer le comportement futur de lapplication, par exemple après un redémarrage non souhaité.

Pratiquement, si lapplication devait rencontrer un problème, nous pourrions la redémarrer sur un autre serveur. Toute information qui aurait été stockée durant lexécution de lapplication sur le premier hôte serait donc perdue. Si une réinitialisation devait être nécessaire, lapplication ne devra pas compter sur la présence dune information au niveau du nouveau système.

Il serait également difficile dappliquer une mise à léchelle de lapplication si une donnée indispensable à son fonctionnement devait se trouver sur une seule machine où elle est exécutée.

#7 - Autoriser la liaison dun port de lapplication à un port du système hôte

Les applications 12-factors sont auto-contenues et peuvent fonctionner en autonomie totale. Lidée est quelles puissent être joignables grâce à un mécanisme de ponts, où lhôte effectue la redirection vers lun des ports ouverts par lapplication, typiquement, en HTTP ou via un autre protocole.

12 factors 7

#8 - Faites confiance aux processus systèmes pour lexécution de lapplication

Comme décrit plus haut, lapplication doit utiliser des processus stateless (sans état). Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge, ou dédier des processus particuliers pour certaines tâches: requêtes HTTP via des processus Web; long-running jobs pour des processus asynchrones, …​ Si cela existe au niveau du système, ne vous fatiguez pas: utilisez le.

#9 - Améliorer la robustesse de lapplication grâce à des arrêts élégants et à des démarrages rapides

Par "arrêt élégant", nous voulons surtout éviter le kill -9 <pid> ou tout autre arrêt brutal dun processus qui nécessiterait une intervention urgente du superviseur. De cette manière, les requêtes en cours pourront se terminer au mieux, tandis que le démarrage rapide de nouveaux processus améliorera la balance dun processus en cours dextinction vers des processus tout frais.

Lintégration de ces mécanismes dès les premières étapes de développement limitera les perturbations et facilitera la prise en compte darrêts inopinés (problème matériel, redémarrage du système hôte, etc.).

#10 - Conserver les différents environnements aussi similaires que possible, et limiter les divergences entre un environnement de développement et de production

Lexemple donné est un développeur qui utilise macOS, NGinx et SQLite, tandis que lenvironnement de production tourne sur une CentOS avec Apache2 et PostgreSQL. Lidée derrière ce concept limite les divergences entre environnements, facilite les déploiements et limite la casse et la découverte de modules non compatibles dès les premières phases de développement.

Pour vous donner un exemple tout bête, SQLite utilise un mécanisme de stockage dynamique, associée à la valeur plutôt quau schéma, via un système daffinités. Un autre moteur de base de données définira un schéma statique et rigide, où la valeur sera déterminée par son contenant. Un champ URLField proposé par Django a une longeur maximale par défaut de 200 caractères. Si vous faites vos développements sous SQLite et que vous rencontrez une URL de plus de 200 caractères, votre développement sera passera parfaitement bien, mais plantera en production (ou en staging, si vous faites les choses un peu mieux) parce que les données seront tronquées…

Conserver des environements similaires limite ce genre de désagréments.

#11 - Gérer les journeaux dévènements comme des flux

Une application ne doit jamais se soucier de lendroit où ses évènements seront écrits, mais simplement de les envoyer sur la sortie stdout. De cette manière, que nous soyons en développement sur le poste dun développeur avec une sortie console ou sur une machine de production avec un envoi vers une instance Greylog ou Sentry, le routage des journaux sera réalisé en fonction de sa nécessité et de sa criticité, et non pas parce que le développeur la spécifié en dur dans son code.

#12 - Isoler les tâches administratives du reste de lapplication

Evitez quune migration ne puisse être démarrée depuis une URL de lapplication, ou quun envoi massif de notifications ne soit accessible pour nimporte quel utilisateur: les tâches administratives ne doivent être accessibles quà un administrateur. Les applications 12facteurs favorisent les langages qui mettent un environnement REPL (pour Read, Eval, Print et Loop) à disposition (au hasard: Python ou Kotlin), ce qui facilite les étapes de maintenance.

Design for operations through codified non-functional requirements

Pour paraphraser une section du DevOps Handbook (Part V, Chapitre 20, Convert Local Discoveries into Global Improvements (page 293-294), une application devient nettement plus maintenable dès lors que léquipe de développement suit de près les différentes étapes de sa conception, de la demande jusquà son aboutissement en production. Au fur et à mesure que le code est délibérément construit pour être maintenable, nous gagnons en rapidité, en qualité et en fiabilité de déploiement et les tâches liées aux opérations en sont facilitées. Ces prérequis sont les suivants:

  • Activation dune télémétrie suffisante dans les applications et les environnements.

  • Conservation précise des dépendances nécessaires

  • Résilience des services et plantage élégant (i.e. sans finir sur un SEGFAULT avec lOS dans les choux et un écran bleu)

  • Compatibilité entre les différentes versions (n+1, …​)

  • Gestion de lespace de stockage associé à un environnement (pour éviter davoir un environnement de production qui fait 157 Tera-octets)

  • Activation de la recherche dans les logs

  • Traces des requêtes provenant des utilisateurs, indépendamment des services utilisés

  • Centralisation de la configuration (via ZooKeeper, par exemple)


1. The DevOps Handbook, Part V, Chapitre 20, Convert Local Discoveries into Global Improvements
2. Au conditionnel du futur plus-que-parfait antérieur. Mais ça arrive. Et tout le temps au mauvais moment.