Chapitre 11
Les environnements
Avant d'arriver en production, une fonctionnalité passe par des étapes de développement et de tests.
Chacune de ces étapes a lieu sur un environnement dédié.
On appelle "environnement", une copie de l'infrastructure permettant d'exécuter le logiciel et les outils
dont il a besoin. Il s'agit donc de faire tourner le code mais aussi la base de données, le cache et toutes les autres
dépendances.
Les environnements ne sont pas une science exacte. Chaque entreprise adapte son cycle de développement
et ses environnements selon ses besoins.
Nous allons voir ici les plus communs.
Production
L’environnement de production, ou "prod" pour les intimes, est l’environnement final où le logiciel est
déployé pour être utilisé par les utilisateurs.
C’est l’objectif ultime du cycle de développement, où tout doit fonctionner de manière fiable, sécurisée et performante.
On y branche donc des outils de surveillance pour s'assurer en permanence du bon fonctionnement.
Le nombre de ventes vient de tomber à 0 depuis 5 minutes : on envoie directement un SMS à l'astreinte.
On appelle "mise en production" (MEP) l'action de déployer sur cet environnement du code ou une dépendance.
Pour faciliter leur cycle de travail, les développeurs, notamment lorsqu'ils travaillent en équipe,
préfèrent tout découper en petites tâches.
Dès que possible, ce sera un découpage fonctionnel : on itère sur une fonctionnalité
pour à chaque fois apporter plus de valeur au client.
Pour certains sujets, on préfèrera un
découpage purement technique : on sort les pièces du puzzle les unes après les autres pour tout emboîter à la fin.
Ça arrive notamment sur les projets difficilement itérables. Par exemple, on ne peut pas exposer au client la moitié d'un moyen
de paiement : avant l'ouverture, il faut avoir géré les remboursements, les rapports financiers, les outils pour le service client...
Dans l'idéal, ces petits morceaux sont déployés très rapidement en production.
Sortir au fur et à mesure rend la MEP moins dangereuse :
- Moins de choses à tester et surveiller.
- Les anomalies sont localisées rapidement.
- Le retour en arrière est plus facile. On appelle ça un "rollback".
Ces petits bouts, ce sont les "commits" dont on a parlé dans le chapitre précédent.
Chacun n'apportant pas forcément une amélioration directe pour le client, on a couramment un malentendu entre les développeurs
et les autres sur le terme "mettre en production".
Quand un développeur dit que son code est en production, ça signifie juste qu'il est physiquement présent dans
l'infrastructure de l'environnement de production. Il peut ne pas être ouvert aux clients.
Dans leur gymnastique de découpage des tâches sur de gros projets, les dev séparent fréquemment la mise en production et l'activation finale de la
fonctionnalité. Pour faire ça, les deux techniques les plus utilisées :
-
99% du code est en production mais non utilisé. On s'assure qu'il n'y a pas de régression.
La dernière MEP lance le point d'entrée vers la fonctionnalité.
On fait ça majoritairement quand il s'agit d'un pur découpage technique, quand on n'est pas trop confiant et qu'on veut avancer à tâtons.
Car malgré tous les tests, il nous arrive de serrer les fesses lors de la mise en prod de code impactant. Comportement utilisateur imprévu, mauvaise évaluation de la charge, une subtilité sur l'environnement de prod... On va voir par la suite que, pourtant, on fait tout pour éviter ça ! -
Utiliser un commutateur de fonctionnalité, en anglais feature toggle ou feature switch.
100% du code est en production mais on a placé dedans un interrupteur. L'activation de la fonctionnalité se base sur une configuration, bien souvent située en dehors du code. On peut donc manipuler l'interrupteur sans déclencher de mise en production.
Les commutateurs ne servent pas uniquement lors des déploiements en production. Ils sont aussi utiles pour débrancher les fonctionnalités non vitales et coûteuses en cas de surcharge du système. Par exemple, lorsque je travaillais pour Deezer, on débranchait le 31 décembre toutes les fonctionnalités qui ne sont pas directement liées à la musique. Sans ça, tu peux être sûr que le site aurait été dans les choux lors du passage à la nouvelle année.
Les systèmes de feature switch avancés offrent des options intéressantes :
-
Restriction aux internes : accès seulement via les adresses IP de l'entreprise, via l'adresse email pro...
Ça nous permet par exemple de faire ce qu'on appelle de la "prod cachée" : on manipule directement la nouvelle fonctionnalité en prod tandis que les clients utilisent toujours l'ancienne version. -
Déploiement progressif pour un petit pourcentage d'utilisateurs : si tout se passe bien pour eux, on continue,
sinon, on revient en arrière.
Cette stratégie se nomme un "canari", en référence aux canaries utilisés dans les mines pour détecter les niveaux de gaz. Les oiseaux étaient envoyés d'abord pour essuyer les plâtres. Les mineurs pouvaient ensuite descendre sans problème. - etc...
Préproduction
La préprod est l'environnement sur lequel ont lieu les derniers tests avant la mise en production.
En théorie, c'est un clone parfait de la production : toute l'architecture, les technologies et les configurations sont les mêmes.
Elle permet des tests en conditions réelles pour vérifier que toutes les pièces du puzzle s'assemblent correctement.
Chaque ligne de code passe par cette étape avant la MEP. Dès qu'une nouvelle fonctionnalité est mise en
préproduction (MEPP), la préprod est donc en avance sur la prod. Il faut garder ce décalage le plus bref possible
pour libérer la préproduction en cas de hotfix (correction de bug urgente).
Développement
La "dev" (oui, c'est au féminin), est l'environnement qui nous aide à développer le logiciel. Le plus souvent, c'est une copie de l'environnement
de production, adaptée pour qu'elle tourne sur nos ordinateurs.
Quand on code, on a directement à disposition en local une base de données, un système de cache etc...
C'est pour faire tourner tout ça que les développeurs ont besoin de machines puissantes avec pas mal de mémoire.
L'environnement de développement n'exécute pas exactement le même code que les environnements de production et de
préproduction :
- Les configurations ne sont pas les mêmes : temps de mise en cache, affichage explicite des erreurs etc...
- On intègre des outils de debug, de tests et tout un tas de trucs pour nous aider.
- L'infrastructure n'est pas rigoureusement la même. Par exemple, si la production tourne dans le cloud, on va avoir en développement une infrastructure qui l'imite mais qui n'est pas une copie fidèle.
- Il est branché sur les environnements de tests des partenaires, aussi appelés bacs à sable (sandbox). Ils peuvent avoir un comportement différent de la production.
Ces décalages expliquent en partie pourquoi parfois les développeurs se prennent la tête à comprendre un problème. Le fameux "mais ça marche chez moi".
Test local
L'environnement "test" est une variante de l'environnement de développement qui sert à faire tourner les tests
automatiques.
Distinguer les deux nous sert à mettre en place des outils différents et des doublures sur des parties de l'infrastructure.
C'est comme au cinéma, on met en place un faux pour tromper le code.
Ça permet des tests fiables et rapides.
Par exemple, en dev nous nous appuyons sur une vraie base de données alors qu'en test nous pouvons
vouloir brancher notre code sur une fausse. Ça permet de plus facilement tricher et de provoquer les différents
scénarios que l'on souhaite tester.
Deuxième exemple : lorsqu'on exécute les tests automatiques, ce qui arrive des dizaines de fois par jour,
nous ne souhaitons pas envoyer d'email. On va alors se brancher sur un faux service d'envoi d'email.
Sans entrer dans le détail, le concept de "doublure" se décompose en plusieurs notions. Entre autres : mock, stub, fake, spy, dummy...
Ce sont tous des outils qui permettent de retourner de fausses données, de simuler un système complet, d'analyser
les interactions avec les systèmes sous-jacents etc...
Par abus de langage, 99% des développeurs utilisent "mock" pour désigner tout type de doublure.
Le concept de doublure est crucial en développement.
Quand tu entends parler de vieux code "legacy", de code pas maintenable et pas testable, qui ne "respecte pas les bonnes pratiques",
c'est parce-que ce code n'a pas été pensé pour pouvoir être doublé ou accepter des doublures. Les doublures sont la principale raison qui nous pousse
à mettre en place des architectures solides.
Quand tu nous entends parler d'hexagone, d'injection de dépendances et de leurs amis, ce n'est pas seulement pour l'amour du joli code.
Ces pratiques permettent de rendre le code testable et fonctionnel sur l'environnement de test.
Sans ça, on n'a aucun moyen, en dehors de tests manuels, pour se protéger de l'introduction d'un bug ou d'une régression.
Les mises en production deviennent stressantes, on découvre des choses bizarres 6 mois plus tard, bref, pas un projet sur lequel on
prend du plaisir à travailler.
Dans la plupart des entreprises, on retrouve l'environnement de test à deux endroits :
- Sur l'ordinateur de chaque développeur. Selon leurs habitudes de travail, les dev peuvent exécuter les tests des dizaines de fois par jour. En particulier s'ils adoptent la conception pilotée par les tests (Test Driven Development / TDD).
- Lors de la phase d'intégration continue dont nous parlions dans le chapitre précédent. Le code se doit d'être testé par les tests automatiques avant de pouvoir être fusionné et déployé sur les environnements en aval de la chaine.
UAT / Staging
Certaines entreprises mettent en place d'autres environnements, majoritairement dédiés aux tests manuels.
Les autres se servent en général directement de la préproduction pour ça.
Alors que l'environnement "test" est dédié aux tests automatiques, les environnements de "User Acceptance Testing"
ou de "staging" servent aux tests manuels et à la consolidation du travail de plusieurs personnes.
Par exemple, nous sommes en train de développer un gros projet impliquant l'interconnexion avec un partenaire.
On l'a dit, le code lui-même va sûrement être déployé en production au fur et à mesure, tout en s'assurant qu'on n'introduit
pas de régression.
Par contre, avant l'ouverture aux clients : les demandeurs (chefs de produit, product owner...) veulent tester le rendu et l'expérience utilisateur,
les développeurs veulent vérifier que les différentes pièces du puzzle s'assemblent bien, le partenaire veut vérifier
qu'il arrive à envoyer une requête et qu'il reçoit bien les réponses qu'il espère.
Le temps que tout le monde teste et éventuellement corrige, ça peut prendre des semaines :
impossible de réserver la préproduction pendant aussi longtemps. On va donc créer un environnement d'UAT dédié
à ce projet et l'ouvrir au partenaire.
Pour être plus réactif sur d'éventuels retours, il est courant, dans le cycle de développement, de placer le déploiement
en UAT autour de la revue de code. Avant ou après selon que tu valorises plus le temps des relecteurs ou des demandeurs 😇,
mais avant la fusion sur la branche master (qui doit rester une copie parfaite de la préproduction, voir le chapitre précédent).
Versionnage
Tous les environnements ci-dessus ont pour objectif commun de pouvoir déployer sereinement une fonctionnalité en production.
Nous avons vu avec le déploiement progressif que malgré nos efforts, nous souhaitons parfois avoir des premiers retours
utilisateurs avant d'ouvrir le produit à tout le monde.
C'est aussi possible grâce au versionnage.
Lors d'un déploiement progressif, la fonctionnalité définitive est ouverte à de vrais clients. Alors qu'ils n'ont
rien demandé à personne, on leur force l'utilisation de la fonctionnalité et de ses éventuels bugs.
À l'inverse, créer une "version" revient à créer une variante complètement indépendante.
Chacun est libre de l'utiliser ou de rester sur sa version actuelle.
Bon, c'est pas complètement vrai, car maintenir plusieurs versions peut s'avérer chronophage, au bout d'un moment
on va stopper le support et forcer la main. Mais en général, l'utilisateur est tranquille pour quelques années.
Le numéro de version respecte conventionnellement le format SemVer : majeure.mineure.patch.
Par exemple 1.23.4 signifie qu'on en est à la première version majeure, qu'il y a eu 23 nouvelles fonctionnalités
depuis sa sortie, dont la dernière a subi 4 corrections.
À une version, peut être associée une étiquette de maturité, correspondant à une phase de développement de la fonctionnalité :
- Dev / Nightly build : les derniers développements au fur et à mesure.
- Alpha : première version fonctionnelle mais instable, destinée à des tests internes.
- Beta : plus avancée mais possibles bugs ou problèmes d’optimisation. Ouverte à quelques volontaires triés sur le volet.
- Release Candidate : version quasi-prête. Sauf si on découvre un truc grave, c'est le code qui sera ouvert à tous.
- Stable : la version est disponible pour tout le monde. Certaines versions stables sont désignées comme bénéficiant du support long terme (LTS). Les utilisateurs savent qu'ils peuvent se baser dessus pendant plusieurs années en toute sécurité.
Les utilisateurs peuvent ainsi volontairement utiliser des versions instables afin de bénéficier des dernières fonctionnalités.
En échange, ils sont invités à rapporter les problèmes.
Ce système est particulièrement utilisé pour les applications mobiles, les API, les bibliothèques...