Transcript de ma conférence « Une Application Résiliente, dans un Monde Partiellement Dégradé »
11 décembre 2023 —En 2019, j’ai commencé à travailler sur un gros système distribué, avec plusieurs dizaines de services qui interagissaient pour répondre aux attentes de nos clients. Et j’ai retrouvé, en amplifié, des problématiques que j’avais déjà rencontrées de manière plus subtile, lorsque je travaillais sur des systèmes consitués d’une demi-douzaine de composants.
C’est à ce moment là que j’ai commencé à travailler sur un talk intitulé « Une Application Résiliente, dans un Monde Partiellement Dégradé », que j’ai depuis donné plusieurs fois en conférences entre 2019 et 2023.
Je suis ainsi intervenu lors de AWS re:invent 2020 (30 minutes) ou pendant le Forum PHP 2019 (40 minutes) :
Cela dit, puisque tout le monde n’accroche pas avec le format vidéo et que beaucoup préfèrent du texte, voici une tentative de transcript de cette conférence. Ou, plutôt, voici un déroulé de quasiment tous les morceaux que j’utilise pour donner ce talk, en les sélectionnant à chaque fois en fonction du public cible et de la durée dont je dispose.
Vous trouverez en-dessous de chaque slide le texte qui lui correspond, parfois enrichi d’informations que je n’ai pas données lors de mes présentations ou qui ne sont pas visibles dans les slides – parce qu’elles sont passées par ma gestuelles ou par des animations / transitions invisibles avec ces images figées. Le style est volontairement assez proche de l’oral.
Bonjour à tous, bonjour à toutes.
Une petite histoire avant de commencer, ça vous dit ?
Il y a de nombreuses années de ça, alors que je travaillais encore en SSII, un beau matin, mon chef et moi partons pour une aventure. Nous prenons la voiture, l’autoroute, nous quittons Lyon, alors qu’il ne faisait pas encore jour.
Nous partions… chez un client.
Nous sommes français. Donc, bien sûr, pendant le trajet, nous pensons au bon repas que nous allons faire à midi, avec notre client, dans un bon restaurant, après une dure demi-journée de travail.
Et, une fois arrivés, nous travaillons. Dur. En effet.
Après tout, nous avions fait le déplacement, ce n’était pas pour rien : de nombreux sujets demandaient à la fois notre attention et celle de notre client.
Mais, à un moment, notre interlocuteur client nous demande de lui garantir, oui, vraiment, de lui garantir, que l’application fonctionnera toujours.
On se regarde mon chef et moi, et nous répondons que nous ne pouvons pas garantir que l’application, complexe, fonctionnera toujours. Et là, il nous retourne « vous ne pouvez pas dire ça à un client ». Sauf que « et bien, si, nous venons de le faire ».
Vous vous en doutez, la suite de la demi-journée a été un peu tendue…
Et nous n’avons pas du tout mangé dans un bon restaurant avec notre client.
À la place, mon chef et moi avons mangé dans un fast-food, sur une aire d’autoroute, sur le chemin du retour.
Je m’appelle Pascal MARTIN.
Aujourd’hui, je suis Principal Engineer chez Bedrock. Je suis également AWS Hero.
Et, avec les années, j’ai réalisé que, moi aussi, je voulais que mes applications fonctionnent tout le temps. Ou plutôt, qu’elles fontionnent autant que possible. Et donc, comment faire ?
Déjà, parlons de « neufs ». De « nines » en anglais.
On parle de « neufs » quand on parle de disponibilité.
Avec « deux neufs », ou 99% de disponibilité, une application peut être indisponible plus de trois jours par an.
Par contre, si on monte à « quatre neufs », ou 99.99% de disponibilité, notre application n’a plus le droit d’être indisponible qu’une petite heure par an.
Ou même, puisqu’on parle plus souvent de disponibilité par mois, notre application à quatre-neufs de disponibilité n’a le droit d’être en panne que 4 minutes et demie par mois ! J’aime autant dire qu’il ne faut pas coder des bugs trop souvent, et qu’il faut les détecter et les corriger vite !
Cela dit, si vous êtes malin, vous aurez noté dans vos contrats que les maintenances prévues ne comptent pas dans le temps d’indisponibilité.
Après, vous pouvez tenter, tous les jours à 18h, de déclarer une maintenance pour les 24 heures suivantes… Mais ça risque de vite se voir.
Plus sérieusement, vous garder la possibilité de programmer des maintenances, quitte à ce que ce soit pendant des horaires non critiques pour l’application et ses utilisateurs, est une bonne idée.
Quand on dit « disponibilité », ça veut dire quoi ?
Et bien, typiquement :
- qu’un service est disponible un certain pourcentage du temps.
- Ou, qu’un certain pourcentage de requêtes réussissent.
- Ou même, un peu plus élaboré, qu’un certain pourcentage de requêtes réussissent, en moins d’un certain temps — un traitement lent est alors considéré comme ayant échoué.
Dans le cadre d’une plateforme de streaming, comme celle sur laquelle je travaille, on peut compter le pourcentage de vidéos qui se lancent avec succès.
Cela dit, même si vous atteignez vos objectif de disponibilité, si votre moment d’indisponibilité tombe mal…
Prenez le site de déclaration des impôts. Je suis sûr qu’il marche très bien toute l’année et qu’il n’est pas loin des 99.99% de disponibilité. Mais si ses 45 minutes annuelles de pannes arrivent juste le dernier jour de saisie des déclarations… Et bien, vous en entendrez parler dans la presse !
Au-delà de la disponibilité, on peut aussi parler de « nines » pour d’autres idées, comme la durabilité.
Par exemple, pour indiquer le pourcentages de chances qu’un fichier existe encore après un an.
Et cette notion de durabilité a son importance : que se passe-t’il pour votre application et votre entreprise si vous perdez des données ?
Car oui, cela arrive.
Cela dit, disponibilité, durabilité, ce sont des notions techniques. Et les utilisateurs de nos applications n’en ont pas grand chose à faire. Ce qui compte, c’est leur vision des choses.
Typiquement, sur un site e-commerce, un indicteur à suivre peut être le pourcentage de tentatives d’ajouts au panier qui réussissent. Sur un site de presse, vous voudrez que vos pages article s’affichent en moins d’une seconde et demi (oui, c’est déjà long !).
Ou, quand je vais au restaurant le midi, je m’attends à ce que le paiement avec ma CB réussisse !
En fait, comme le dit si bien Charity Majors, les neufs n’ont pas tant d’importance. Ce qui compte, c’est que les utilisateurs et utilisatrices de nos applications soient contentes. Autrement dit, que nos applications répondent à leurs attentes et à leurs besoins.
Car, oui, ce que nous faisons derrière nos ordinateurs, ça a un impact sur la vraie vie de vrais gens.
Prenez l’exemple que je montre ici : un panne dans un datacentre américain, pendant laquelle des verrous connectés ont cessé de fonctionner.
Non mais, sérieusement ?
Vous imaginez, après une journée de boulot, essayer de rentrer chez vous et ne pas pouvoir… À cause d’une panne informatique dans un datacentre à des centaines ou milliers de kilomètres ?
Et bien, voilà. Peu importe le nombre de neufs que vous annoncez, ce que vous faites, ce que nous faisons, a un impact sur la vraie vie de vrais gens.
Maintenant, essayons d’appliquer ça à nos architectures.
Et, aujourd’hui, nous travaillons souvent avec des systèmes distribués. Peut-être, des plateformes composées d’un ensemble de microservices ou de ce que nous prétendons être des microservices…
Commençons avec cet exemple assez simple : une application qui dépend d’un service, nommé A
. Ce service a une disponibilité de 99.99%.
Quelle est la disponibilité théorique de notre application ?
Et bien, pour ce cas simple d’une application qui ne dépend que de ce seul service, facile : notre application a elle aussi une disponibilité théorique de quatre-neufs.
Maintenant, prenons un exemple un peu plus complexe : une application qui dépend de deux services A
et B
. Nos développeurs sont très forts et ces deux services A
et B
ont tous les deux une disponibilité de 99.99%.
Quelle est la disponibilité théorique de notre application ?
Vous seriez tenté de dire « 99.99% » ?
Et bien, non !
Puisque l’application dépend des deux services A
et B
, nous devons multiplier leurs deux taux de disponibilité… Et nous obtenons ainsi une disponibilité théorique de seulement 99.98% pour notre application.
Notre application, qui dépend de deux services annonçant quatre-neufs de disponibilité, ne peut pas elle-même prétendre annoncer quatre-neufs de disponibilité.
Passons maintenant à un cas certainement plus proche des vraies applications sur lesquelles vous intervenez tous les jours…
L’application interroge un service A
, qui dépend d’un service B
. Elle interroge aussi un service C
, qui dépend du premier service A
. Oh, et elle a aussi besoin du service D
. Oui, un petit sac de noeuds. Où chaque service affiche son propre taux de disponibilité.
Quelle est la disponibilité théorique de votre application ?
Oh, attendez !
Une partie des services que vous avez développé dépendent de systèmes de stockages : une base de données MySQL, un serveur Redis, un cluster Elasticsearch, un autre cluster Redis… Tous ces systèmes, eux-mêmes, ont leur propre taux de disponibilité.
Donc, quelle est la disponibilité théorique de votre application ?
Et quand je dis que chacun de ces services annonce un taux de disponibilité, je ne plaisante pas. Et vous ne contrôlez pas toujours ces taux de disponibilité annoncés.
Par exemple, RDS, le service de base de données relationnelle d’AWS, annonce 99.95% de disponibilité. Si vous prenez l’offre avec un serveur primaire et un serveur failover ! Si votre application dépend directement de RDS, elle ne peut donc pas annoncer une disponibilité de 99.99%, c’est juste impossible.
Notez que depuis que j’ai créé ce slide il y a quelques années, le service RDS a évolué et son SLA aussi.
Après cette parenthèse, je reprends mon schéma…
Et il manque quelque chose : vous ne codez pas des adresses IP partout dans vos applications ni dans vos fichiers de configuration.
À la place, vous utilisez des noms de machines, des noms de domaines…
Chaque requête vers chacun de vos services ou chacun des services dont ceux-ci dépendent est donc précédée d’une requête vers un service DNS !
Et ce service DNS, il est parfois aussi en panne !
Là, je vous laisse réfléchir un instant, à cette question que je vous ai déjà posée :
Quelle est la disponibilité théorique de votre application ?
Bon courage…
En fait, si vos nombreux microservices communiquent les uns avec les autres en temps réel, vous avez juste créé un monolithe distribué.
Et les systèmes distribués, c’est simple… N’est pas ? (sur un ton ironique)
Ou alors, vous aussi, vous avez remplacé votre bon vieux monolithe par des microservices, pour que chaque panne se transforme en un vrai murder mystery ? (sur un ton ironique)
Mais bon, quand même, essayons d’être sérieux.
Et, dans le vrai monde des vrais gens, comment ça se passe ? Il y a peut-être des choses qu’on peut apprendre de comment font les autres…
Il y a plusieurs années maintenant, un collègue et moi sommes parti à Londres pour une conférence nommée QCon, où nous avons vu des intervenants et intervenantes de pas mal d’entreprises connues – comme la BBC ou AirBnB.
Et là, pendant une des premières conférences, nous avons entendu « les choses vont échouer, c’est un fait ».
Alors bon, on s’est dit que ah oué, c’était courageux de monter sur scène et d’affirmer devant une salle pleine que ça va casser.
Mais ça ne s’est pas arrêté là !
Une autre conférence, une autre personne sur scène, et nous entendons « tout échoue, si on y laisse le temps ».
Et un peu plus tard, encore une autre conférence, où on nous dit « à l’échelle, les choses échouent beaucoup » !
Donc, plusieurs intervenants et intervenantes, de grosses entreprises connues et à succès, qui montent sur scène et affirment que juste ça va casser et c’est normal. OK, c’est intéressant ! Et en fait, ça rejoint un peu ce qu’on a nous aussi constaté. Ca a un côté rassurant, peut-être ^^
Et, finalement, j’arrive à cette citation, que j’aime beaucoup et qui a inspiré le titre de mon talk :
« Les systèmes distribués ne sont jamais ‘opérationnels’.
Ils existent dans un état permanent de service partiellement dégradé. »
Dans une plateforme complexe constituée de nombreux composants, il y a toujours quelque chose qui ne marche pas. C’est comme ça. Et plus tôt on l’accepte, plus tôt on commence à se concentrer sur ce qui importe : faire en sorte que la plateforme dans son ensemble continue à répondre, au mieux, aux attentes de ses utilisateurs.
Maintenant, un cas inspiré de faits réels, un projet sur lequel j’ai bossé il y a quelques années… Ou, sans doute, la plupart des projets sur lesquels vous intervenez tous les jours…
Prenons un utilisateur qui essaye d’accéder à une page Web.
Cette page web, notre application, dépend d’un service A
, qui dépend d’un service B
, qui dépend d’une base de données.
La page Web dépend aussi d’un service C
, qui dépend lui-aussi du service A
.
Quand tout va bien, les utilisateurs sont contents : le site Web fonctionne.
Mais, un jour, l’équipe qui développe le service A
déploie une nouvelle version de celui-ci. Et il y a un bug dans le code. Peut-être une condition ratée dans une boucle, peut-être une boucle infinie, qui fait que A
interroge beaucoup plus B
que d’habitude…
En conséquence, B
interroge beaucoup plus sa base de données que d’habitude…
Et cette base de données se retrouve rapidement surchargée.
Elle devient incapable de répondre aux requêtes.
En conséquence, le service B
s’écroule : sans sa base de données, il ne sait plus non plus répondre aux requêtes…
Et donc, A
, qui dépendait de B
, tombe à son tour.
Et comme C
dépend de A
et bien, vous l’avez deviné, C
s’écroule lui aussi.
Et donc, votre page Web ne peut plus être générée, votre site ne s’affiche plus…
Et les utilisateurs ne sont pas contents.
Tout ça parce qu’on a déployée un petit bug sur un seul des composants de notre plateforme…
Mais ! Au fait !
Il y a quelques années, vous aviez développé un webservice pour un partenaire. Webservice – ou API, comme on dirait maintenant – qui charge ses données depuis la base de données…
Et ce webservice est utilisé par le site web de votre partenaire. Site web qui a lui-même des utilisateurs…
Si la base de données est dans les choux, votre webservice ne fonctionne plus…
Et donc, le site web de votre partenaire est lui aussi cassé. Et des utilisateurs ne sont plus satisfaits.
Et bien, en déployant un petit bug sur un des composants de votre plateforme, vous avez cassé votre plateforme entière et, en bonus, la plateforme de votre partenaire.
En général, c’est le moment où le CEO chez le partenaire prend son téléphone, appelle le CEO chez vous, qui déboule dans l’open-space en s’exprimant bien fort, et où tout le monde commence à courir en panique dans les couloirs !
Pour moi, c’est l’occasion parfaite de vous parler de la notion de « Blast Radius ».
Le Blast Radius, ou rayon d’explosion, ça représente l’impact d’une panne d’une application.
Plus le blast radius d’une application est réduit, moins une panne impactera les autres applications.
Ici, le Blast Radius du service A
, c’était l’ensemble de votre plateforme, et celle de votre partenaire :-/
Et, un jour, quelque chose va casser, c’est comme ça. Si nous voulons répondre aux attentes de nos utilisateurs, le blast radius de chacun des composants de notre plateforme ne peut pas être « la plateforme entière » !
Heureusement, tout n’est pas perdu, nous disposons d’outils qui peuvent nous aider.
Et comprendre certaines notions est également un plus.
Avant tout, nous avons besoin de savoir ce que fait notre application. Sans ça, nous ne saurons pas si elle fonctionne bien, si elle commence à montrer des signes de fatigue, ou si elle est en train de s’écrouler.
On parle souvent d’« observabilité » et de ses trois piliers. Ce sont, pour moi, des pré-requis — au moins pour les logs et les métriques ; et je n’en parlerai pas plus aujourd’hui.
Maintenant, un peu de vocabulaire.
Je vais aborder trois termes, trois notions.
Tout d’abord, parlons de « SLI ». I
pour Indicator.
Un SLI, c’est une mesure, qualitative, d’un aspect, du niveau de service.
Et les quatre points de la définition sont tous importants !
Bien sûr, collecter ces mesures n’est pas toujours évident et nous effectuons souvent des compromis.
Par exemple, nous travaillons avec des taux, des moyennes, des percentiles.
Et, pour vous aider à mieux saisir cette notion de SLI, rien de mieux, sans doute, que des exemples !
- La latence est un SLI : notre application répond en X millisecondes à un type de requêtes HTTP.
- Le taux d’erreurs est un autre SLI : X% des requêtes sur notre application réussissent – ou échouent.
- Le débit, aussi : notre application répond à X requêtes par seconde.
- Ou on peut reparler de disponibilité, un autre indicateur : notre application est disponible X% du temps.
- Et puis, même, quand on parle de durabilité : nous avons X% de chances d’un fichier existe encore après un an.
Tout ceci, ce sont des indicateurs.
Et notez que je n’ai pas indiqué de valeur sur ce slide, uniquement des X
.
Une fois qu’on a des Indicateurs, on peut se fixer des Objectifs : des SLO.
Un SLO, c’est une valeur cible, pour un niveau de service, mesuré par un SLI.
Et fixer des objectifs est difficile. En effet, en face de ces objectifs, vouz devrez fournir des efforts pour les atteindre !
Fixer des objectifs faciles à atteindre n’a pas toujours vraiment de sens. Et fixer des objectifs inatteignables – comme 100% de disponibilité – n’en a pas non plus.
Voici des SLOs (objectifs) correspondant aux SLIs (indicateurs) vus juste avant.
- Pour la latence, un objectif peut être de mettre moins de 25 millisecondes pour répondre à une type de requêtes HTTP.
- Pour le taux d’erreurs, nous pouvons vouloir que notre application réponde avec succès dans plus de 99.99% des cas – ou, dans l’autre sens, nous pouvons viser avoir moins de 0.01% de requêtes qui échouent.
- En parlant de débit, nous viserons que notre application sache répondre à au moins 250 requêtes par seconde.
- Disponibilité ? Nous souhaitons que notre service soit disponible au moins quatre-neufs pourcents du temps.
- Et pour la durabilité, enfin, nous pouvons viser à ce qu’un fichier ait 14-neufs pourcents de chance d’encore exister au bout d’un an – je crois que c’est l’objectif de durabilité annoncé par Amazon S3 ;-)
Vous aurez remarqué que les indicateurs sont les mêmes que ceux qu’on a défini juste avant, et nous avons uniquement défini, ici, des valeurs cibles.
Ca parait peut-être un peu effrayant mais, en fait, avoir défini des objectifs est extrêmement puissant ! C’est un excellent outil pour vous et pour vos équipes !
En effet, si votre niveau de service est supérieur à votre objectif, si vous faites mieux que ce que vous êtes censés faire…
Et bien, c’est génial ! Vous avez le droit de casser des choses ! C’est le moment de développer des nouvelles fonctionnalités, c’est le moment où vous êtes libres d’expérimenter ! Et vous avez le droit de le faire, puisque vous dépassez les objectifs !
À l’inverse, si votre niveau de service est inférieur à votre objectif…
Et bien, cela signifie que vous n’avez plus le droit de casser des choses. Au contraire. Il est donc temps d’arrêter de travailler sur des nouveautés dans tous les sens. Il est temps de concentrer vos efforts sur la stabilisation de votre service.
Et vous avez une très bonne justification pour investir du temps sur des tâches de stabilisation : votre service, en l’état, n’atteint plus ses objectifs.
Et c’est seulement maintenant, une fois que vous avez défini des indicateurs et fixé des objectifs, que les SLA entrent en jeu. Et ce même si « SLA » est souvent la première chose à laquelle on pense : quand on dit « SLA » pour SLI+SLO, c’est un abus de langage.
Un SLA, un agreement, c’est un contrat que vous passez avec un utilisateur, avec un client. Et ce contrat spécifie des conséquences si votre application n’atteint pas ses objectifs. Ce SLA est donc généralement lié à un aspect commercial ou buesiness.
Anecdote : Google Search n’a pas de SLA. Google veut que le service soit fluide et disponible, mais n’a pas signé un contrat avec le monde entier. Donc, il n’y a pas de conséquence (les utilisateurs ne reçoivent pas de compensation financière si Google Search est down), même si ça mal financièrement parlant (pas de revenus publicitaires si pas de service) et en termes d’image.
Si on essaye de remettre tout ça ensemble…
On commence par définir un indicateur qui a du sens pour notre application.
Par exemple, un SLI pourrait être « notre API répond avec succès à X% des requêtes ».
Une fois cet indicateur défini, on se fixe un objectif.
Par exemple, 99%. On arrive donc à « notre API répond avec succès à 99% des requêtes ».
Et, seulement maintenant, nous pouvons passer à la conséquence si nous n’atteigons pas cet objectif.
Par exemple, nous pouvons offrir une réduction sur le prochain mois, ou un truc du genre…
Bien sûr, avant d’inscrire quelque chose comme ça dans vos contrats, parlez-en avec vos collègues, dont les personnes du service légal ;-)
N’allez pas dire « j’ai vu Pascal Martin en conférence, il a dit de faire ça » 🙏
À partir de ces informations, notamment à partir des métriques et/ou des logs, on peut mettre en place des alertes.
Leur rôle est de nous prévenir lorsque notre application commence à aller mal, pour que nous puissions éviter une panne… Ou lorsque l’application est écroulée, pour que nous la réparions.
Bien sûr, si Slack ressemble à ça tous les matins…
Enfin, face à ça, la première personne qui arrive au bureau et regarde le salon d’alertes… Bah elle va prendre un café. Vu la quantité d’alertes, il doit bien y avoir encore quelques trucs qui marchent, de toute façon ^^
Et certains matins, il y a du monde au café !
J’ai aussi vu, des fois, des messages d’alertes tellement peu informatifs que la personne d’astreinte ne sait absolument pas quoi en faire…
Imaginez être réveillé au milieu de la nuit par une alerte « critique », dont le message ne donne aucune information quant à son urgence réelle !
En fait, une alerte ne devrait sonner que quand elle nécessite une intervention.
Et l’alerte doit être actionnable : quelque chose doit pouvoir être fait pour corriger le problème. Et si l’alerte inclut un lien vers la documentation qui décrit qui faire, c’est encore mieux.
Rappelez-vous : ces alertes, elles vont sonner au milieu de la nuit, elles vont réveiller quelqu’un… Et à 3h du matin, on n’est pas toujours en grande forme !
Un peu pour le fun, je vais prendre deux exemples.
Admettons : vous avez ces deux métriques pour deux applications.
La première consomme 30% de CPU, la seconde en consomme 90%.
Et maintenant, je vous pose deux questions :
- Quelle application est la meilleure ?
- Et faut-il réveiller quelqu’un pour intervenir ?
Et bien… là comme ça, je n’en sais rien ! Je ne peux pas savoir !
Peut-être que l’application qui consomme 30% de CPU est horriblement lente… Peut-être que l’application qui consomme 90% de CPU est rapide. Ou l’inverse. Je ne sais pas.
Ces indicateurs ne me sont pas utiles et ne devraient pas servir de base au déclenchement d’une alerte.
La vraie question importante, plus que la consommation CPU, ça serait le ressenti utilisateur !
Croyez-moi, l’application qui consomme 30% de CPU peut faire souffrir vos utilisateurs nettement plus que celle qui en consomme 90% !
Et aussi : tenez compte des SLO. Si on fait mieux que l’objectif, pas besoin de réveiller quelqu’un, même s’il y a quelques erreurs !
Et puisqu’on parle de réveiller quelqu’un…
Si j’ai trois composants architecturés ainsi, avec A
qui appelle B
qui appelle C
.
En cas de panne de C
, qui doit être réveillé ?
Et bien, si le service C
est KO, c’est à l’équipe du logiciel C
d’être alertée !
Et pas à l’équipe du logiciel A
qui l’appelle 3 couches plus haut…
Et, malheureusement, bien trop souvent, c’est l’équipe de A
que l’on réveille, ou parfois celle de B
, parce que ce sont leurs logiciels qui voient la panne de C
.
Après tout ça…
Et si on envisageait quelques solutions pour améliorer les choses ?
Bon, déjà, améliorer la résilience, c’est avant tout… être prévoyant ^^
Ce que vous voyez, c’est une machine à café… Branchée sur un onduleur, en cas de coupure de courant ! Bon, hélas, c’était un vieil onduleur et une machine à café ça consomme pas mal, donc ça n’a pas tenu longtemps :-(
Plus sérieusement, être prévoyant, c’est ne pas improviser.
Avant de travailler en production, agissez en environnement de staging et documentez ce que vous avez fait.
Puis, en production, suivez cette documentation.
Ou, encore mieux, automatisez !
Par exemple, utilisez un outil comme Terraform pour décrire et gérer votre infrastructure, au lieu de faire des clics dans des interfaces web !
Ne pas improviser, c’est aussi savoir se poser les bonnes questions, en avance, pour que les réponses soient toutes prêtes… Ou pour corriger les problèmes avant qu’ils ne surviennent !
Parmi les questions que j’adore poser à mes collègues, vous m’entendrez parfois demander :
- « Qu’est-ce qui va casser ? » – tout, un jour ou l’autre ;-)
- « Comment est-ce que ça va casser ? » – plus intéressant, souvent, puisque cela permet de prévoir des solutions.
- Et surtout, « comment on le sait et comment on investigue ? »
Pour ce qui est des pistes techniques, on pourrait en parler pendant fort longtemps.
Tout d’abord, j’aimerais insister sur l’idée que le scaling, ça devrait se faire en fonction de la durée des requêtes, qui peut jouer comme proxy de l’expérience utilisateur, et pas en fonction de la charge CPU des machines.
Ensuite, utilisez des timeouts courts sur toutes les requêtes que vous émettez.
Et puis, pour que votre application soit résiliente, elle doit survivre en cas d’échec. Plus votre application continuera à à-peu-près-fonctionner quand une de ses dépendances est cassée, plus elle saura répondre aux attentes de ses utilisateurs.
Avoir un mécanisme de feature flipping, d’activation ou de désactivation de fonctionnalités, peut aussi vous aider. Typiquement, pour désactiver des fonctionnalités couteuses en cas de pic de charge ou de ralentissement. Encore mieux si c’est fait automatiquement !
Et comme les pannes sont souvent le résultat de déploiements, si vous disposez d’un mécanisme de déploiement progressif, que ce soit Canary ou Blue-Green, vous réduirez le risque d’impacter l’ensemble de vos utilisateurs.
Lancer des tests de charge sur votre plateforme est aussi une bonne façon d’améliorer sa résilience. Du moins, des tests de charge vont vous aider à identifier « ce qui casse en premier » et vous saurez donc sur quels composants vous concentrer en premier lieu.
Ces tests de charge peuvent être occasionnels ou réguliers, ils peuvent suivre des scénarios plus ou moins proches de la réalité… Et demander plus ou moins d’efforts pour leur réalisation.
Une autre piste technique, lorsqu’on parle de résilience d’une application à la perte d’une de ses dépendances, est de mettre en place un mécanisme de redondance.
Typiquement, si votre application dépend d’une base de données…
Et bien, ne mettez pas qu’un seul serveur de base de données.
Mettez-en deux, avec un mécanisme de réplication entre les deux.
Ainsi, si le premier serveur tombe en panne, votre application continuera à fonctionner, en utilisant le second.
Bien sûr, ça coute deux fois plus cher – sans compter l’effort de mise en place et la validation régulière du mécanisme de bascule. Mais si la base de données est indispensable au fonctionnement de l’application, ça en vaut peut-être le coup ?
Et puis, si on essaye de voir un peu plus large…
Aujourd’hui, vous déployez votre application sur des serveurs dans un datacentre, peut-être en France.
Mais si un séisme emporte Paris, votre datacentre ne sera plus là et votre application non plus.
Bon, j’admets, on aura aussi d’autres problèmes…
Pour réduire le risque, vous pourriez déployer votre application sur des serveurs dans d’autres pays, peut-être en Grande Bretagne ou en Allemagne.
Et puis même, si vous souhaitez que votre application continue à répondre dans le cas où une météorite emporte l’Europe – et, oui, on aura vraiment d’autres problèmes – vous la déploierez peut-être même sur d’autres continents.
Cette approche est rarement mise en place pour une seule question de résilience, parce qu’elle coute cher et demande pas mal de boulot. En général, on sauvegarde des backups sur un autre continent, mais on ne lance pas l’application entière plusieurs fois.
Cela dit, si vous avez des utilisateurs dans le monde entier, héberger sur plusieurs continents et répondre aux utilisateurs depuis le datacentre le plus proche d’eux, ça peut être très intéressant pour améliorer leur expérience, pour leur fournir des temps de réponse réduits.
Notez toutefois que copier des données entre plusieurs pays en Europe prend quelques millisecondes à quelques dizaines de millisecondes…
Mais copier des données jusqu’à l’autre bout du monde va prendre jusqu’à quelques centaines de millisecondes, avec les problématiques de synchronisation que cela implique.
Maintenant, revenons aux microservices…
Je vous avait dit tout à l’heure qu’on en reparlerait, je crois ?
Quand on parle microservices, on voit souvent une architecture qui ressemble à celle-ci, en haut de l’écran : une application dépend d’un service A
, qui appelle un service B
, qui dépend d’une base de données.
Sauf qu’avec une architecture comme celle-ci, si la base de données tombe en panne, l’application B
tombe en panne… Et comme l’application A
dépend de B
, elle s’écroule elle-aussi et notre plateforme entière est dans les choux et nos utilisateurs ne sont pas satisfaits.
Cette architecture, en haut de l’écran, ce n’est pas une architecture microservices.
Ce que vous avez construit là, c’est un monolithe distribué, avec des composants fortement couplés qui dépendent les uns des autres. À travers le réseau, en plus.
Une vraie architecture microservices, c’est celle-ci, en bas de l’écran :
- Le service
A
ne dépend plus du serviceB
, il ne l’appelle plus en synchrone. - À la place, lorsqu’une donnée est modifiée au niveau du service
B
, celui-ci pousse un message dans une file/queue. - Et
A
écoute les messages qui arrivent dans cette file, pour mettre à jour la donnée de son côté.
Oui, ça demande du boulot, pour mettre en place ce mécanisme de communication asynchrone. Et cela demande une base de données du côté de A
, pour que ce service puisse stocker sa représentation locale de la donnée.
Mais si B
s’écroule, A
, qui n’en dépend plus, continuera à fonctionner. Et nos utilisateurs resteront satisfaits.
Et bien ceci, l’architecture en bas de cet écran, c’est une vraie architecture microservices.
Comme le dit cette citation, des microservices qui ne communiquent pas en asynchrone mais en synchrone, c’est aussi mauvais qu’une grosse application monolitique.
Et j’irais jusqu’à dire que c’est pire, notamment en termes de résilience, puisqu’appeller une fonction à travers le réseau a bien plus de chances de mal se passer que juste appeler une fonction locale comme on sait faire depuis si longtemps !
Très souvent, quand je parle de résilience, on me dit « je vais ajouter du cache ».
Peut-être parce que j’ai la chance de bosser sur des applications qui reçoivent pas mal de trafic ?
Je n’ai pas le temps aujourd’hui d’expliquer pourquoi ajouter du cache n’améliore pas la résilience d’une application, mais j’ai le temps de parler d’une utilisation de cache qui vous pourrit peut-être la vie…
Bien souvent, on a une application (en bas), une base de données (en haut), et comme elle a du mal à répondre à la charge, on place un serveur de cache, peut-être Redis ou memcached, entre les deux.
Au démarrage de l’application ou du serveur de cache, celui-ci est vide et on bourrine la base de données – qui souffre donc quelque peu.
Mais ensuite, la plupart des données sont présentes dans le cache, sauf peut-être une de temps en temps, et tout va bien, la base de données n’est plus surchargée et l’application répond vite.
Hélas, au bout de 60 minutes, le cache pour plein d’entrées expire !
Et là, à nouveau, votre application émet plein de requêtes vers la base de données pour recharger les données et les restocker en cache.
Pendant ce temps, la base de données souffre, l’application ralenti – si vous avez de la chance.
On repart ensuite pour un moment où tout va bien… Jusqu’à 60 minutes après quand, à nouveau, les entrées en cache expirent. Et ainsi de suite.
Toutes les 60 minutes, la durée de vie de vos entrées en cache, la base de données souffre.
Et si on imaginais un moyen de revoir ça, d’améliorer un peu les choses ?
Bien sûr, au démarrage de l’application ou quand le cache est vide, on requête la base de données, elle a du mal, mais bon, c’est comme ça – et on pourrait envisager des approches qui aideraient, comme pré-chauffer le cache.
Mais cette fois, on change quelque chose : au lieu de systématiquement utiliser 60 minutes comme durée de cache, on utilise “60 minutes plus ou moins quelques minutes”. Certaines entrées vont expirer après 56 minutes, d’autres après 68 ou 62 minutes.
Comme toutes nos entrées de cache ne vont pas expirer en même temps, nous allons étaler dans le temps les requêtes à la base de données pour les recharger.
Et ici, on ne voit pas le pic de charge que nous avions précédemment sur la base de données toutes les 60 minutes.
Les expirations de cache sont étalées dans le temps, la charge sur la base de données est lissée.
Et l’expérience utilisateurs est améliorée.
Ce principe d’introduire un petit peu d’aléatoire, c’est ce qu’on appelle du « random jitter ». Et c’est souvent utilisé pour des durées de cache : un peu plus ou un peu moins que la durée voulue.
Mais ça s’applique dans plein d’autres cas, comme, super classique, les « crons de 4h du matin »…
Je me souviens d’un projet, il y a pas mal d’années (et encore, j’ai vu ça plusieurs fois dans ma carrière). Un jour, on avait un cron, un traitement régulier, à mettre en place. Un batch d’import ou d’export ou de recalcul, je ne sais plus. Rien d’urgent, donc on s’est dit qu’on allait le programmer pour l’heure où nos serveurs étaient le moins chargé : 4h du matin.
Forcément, plusieurs mois plus tard, nous avions toutes les nuits des alertes aux environs de 4h du matin : notre application était plus que lente, nos serveurs étaient complètement surchargés. Ah ben oui : au fur et à mesure que nous avions ajouté d’autres crons, nous les avions ajoutés, eux aussi, à 4h du matin. Un mélange de copier-coller dans la crontab et de « boah, si celui là – puis les autres – est à 4h, ça doit être la bonne heure… »
Et donc, 4h du matin, c’était l’heure où notre serveur était le plus chargé de toute la journée.
Tous les jours.
Bref. Pour vos crons, évitez de tous les entasser sur 4h du matin.
Etalez-les sur la journée, ou au moins sur la nuit entière.
Nous savons que des choses vont échouer, c’est comme ça.
Acceptons-le.
Peut-être, si une requête échoue, nous pouvons la rejouer ?
Peut-être qu’elle réussira la seconde fois ?
C’est une approche que marche bien pour des pannes temporaires.
En allant plus loin, même : si une requête dure trop longtemps, considérez qu’elle a échoué.
Annulez-là.
Puis rejouez là.
Là encore, sur des pannes temporaires ou sur des ralentissements de courte durée, c’est une approche que marche parfois très bien.
Cela dit, attention quand même.
Imaginez, vous avez des requêtes qui réussissent, toutes les minutes, en vert.
À la seconde minute, pas de chance, des requêtes échouent.
Et pareil à la troisième minute.
Mais, en plus, à la troisième minute, vous rejouez les requêtes qui avaient échoué à la minute précédente…
Au bout d’un moment, à force de rejouer encore et encore les requêtes en échec, ça commence à faire pas mal de requêtes en échec, non ?
Et puis, bon, soyons réaliste, votre serveur va finir par s’enflammer…
… Et votre application va finir par complétement s’écrouler.
Donc, ré-essayer en cas d’échec, oui.
Mais un nombre limité de fois (et zéro fois c’est bien aussi, dans certains cas).
Et en attendant de plus en plus longtemps entre chaque ré-essai.
D’ailleurs, attendre de plus en plus longtemps entre chaque tentative alors que les utilisateurs de votre application n’attendent jamais très longtemps, ça montre bien que vous n’avez pas besoin de ré-essayer trop de fois : les utilisateur seront déjà partis vers Twitter ou Facebook.
Concevoir une appplication résiliente, c’est admettre que quelque chose va casser et faire en sorte que l’application continue tout de même à fonctionner du mieux possible.
Ca passe donc par la mise en place d’un mode dégradé.
Je prends l’exemple de la page d’un article sur un site de presse. J’ai bossé dans la presse il y a pas mal d’années, donc je connais bien cet exemple.
Avec mes talents de dessinateur, ça ressemble un peu à ça.
En haut, vous avez un bandeau avec le nom du site, des menus, peut-être une pastille « Bonjour Pascal ».
Après, vous avez un ou deux gros titres, un diaporama de grandes photos, puis le contenu textuel de l’article que vous veniez lire.
En partie droite de l’écran, deux colonnes de widgets divers : les dernières news, un flux de réseau social, la météo de votre région, un extrait d’un autre article, une publicité dont vous n’avez rien à faire, une autre publicité sur laquelle vous ne cliquerez jamais sauf par erreur…
Si votre serveur devient un peu chargé, ou si vous ne parvenez plus à joindre suffisamment rapidement le service de votre annonceur, que faites-vous ?
Et bien, peut-être, vous pourriez juste ne plus afficher cette dernière publicité ?
Et si votre serveur est encore trop chargé et si cette charge influe négativement sur l’expérience des utilisateurs de votre site, vous pourriez peut-être juste arrêter d’afficher la liste des dernières news ?
Et si l’API de votre fournisseur météo est cassée, que faites-vous ?
Et bien, arrêtez juste d’afficher le bloc d’informations météo, non ?
Et on peut continuer comme ça encore un moment, en arrêtant d’afficher le diaporama de photos…
Et même, en continuant jusqu’à l’extrême, en n’affichant plus que le contenu textuel de l’article.
Après tout, c’est ce que l’utilisateur venait lire, non ?
Oui, c’est un peu moche…
Mais c’est bien mieux, pour l’utilisateur, qu’une grosse page d’erreur qui lui dit « coucou, on est désolé, notre application consomme trop de ressources et on a préféré t’afficher un message d’erreur plutôt que le contenu que tu venais consulter ».
Ou, encore pire, la page d’erreur que le navigateur affiche quand votre serveur est tellement tout feu tout flamme qu’il ne répond plus du tout.
Quelques autres exemples de modes dégradés ?
- Sur un site e-commerce, quand j’essaye d’afficher ma liste de souhaits qui contient 10 articles, si vous ne parvenez pas à charger les informations d’un des produits, affichez quand même les 9 autres ! C’est mieux que rien, vous avez 90% de chances d’afficher l’article que je voudrais acheter aujourd’hui, et mon expérience sera bien meilleure que face à une page d’erreur.
- Toujours sur un site e-commerce, dans le tunnel de commande, si vous rencontrez un problème technique sur un des produits… Et bien, permettez-moi quand même d’acheter les autres ! Et, ensuite, proposez moi un bon de réduction de 10% pour vous faire pardonner si je reviens demain. Il y a de bonnes chances que je revienne demain et que vous conserviez un client. Alors que si votre tunnel de commande juste plante… Vous savez comment s’appelle votre concurrent ? Bah moi aussi, maintenant.
- Et puis, grand classique : si vos serveurs sont un peu trop chargés, au lieu de ramer et de dégrader mon expérience… Avez-vous vraiment besoin de m’afficher des données personnalisées ? Est-ce que des données par défaut, ça ne serait pas mieux que rien ?
Des fois, un mode dégradé, ça peut même aller aussi loin que de véritables fallbacks.
Par exemple, si votre site dynamique a du mal, vous voudrez peut-être afficher une version statique, re-générée une fois par jour ? Je ne dis pas que c’est parfait, mais c’est peut-être mieux que rien ?
Ou, sur une application mobile notamment, si vous ne parvenez pas à charger une donnée – parce que votre serveur rame ou parce que je suis sur un réseau de la campagne – peut-être qu’afficher une valeur par défaut déjà stockée côté client serait mieux que rien ?
Et lorsque vous avez mis en place un mécanisme de cache, quand une donnée est expirée, si vous ne parvenez pas à la recharger depuis son origine (votre base de données, par exemple)… Et bien, exploiter la donnée expirée, c’est peut-être une approche viable et préférable à juste planter ?
Tout ça, je ne dis pas que c’est toujours possible… Mais, souvent, ce type d’approche, c’est bien mieux pour vos utilisateurs qu’une page d’erreur !
Bien sûr, vous voudrez mesurer l’impact que les modes dégradés ont sur la satisfaction des utilisateurs…
Et sur les performances business de votre produit.
Cela signifie que vous devez savoir comment mesurer ces impacts…
Et, donc, qu’il est peut-être temps de définir des SLI et SLO orientés business, si vous ne les avez pas encore.
Passons à une autre technique qui peut vous sauver dans certaines situations, notamment en cas de surcharge dûe à une affluence plus élevée que la normale.
Note: ici, je devrais plus écrire « load-shedding » que « circuit breaker » – on va dire que je vais utiliser le second pour faire le premier…
Je repars sur un schéma qui ressemble aux architectures que nous avons déjà vues : un site web, qui dépend d’un service A
, qui dépend d’un service B
, qui dépend lui-même d’une base de données.
Maintenant, imaginons que cette base de données ralentisse. Ca peut être parce qu’un gros traitement batch est en cours, parce que vous avez déployé une évolution qui la charge plus que d’habitude, ou en raison d’une forte affluence sur l’application. Peu importe.
Et si, au lieu de laisser la base de données ralentir jusqu’à s’écrouler, entrainant dans sa chute le service B
puis le service A
puis votre plateforme toute entière, vous détectiez ce ralentissement ?
Et si vous décidiez de trancher, brutalement, le trafic entrant sur votre plateforme depuis 10% des utilisateurs, le plus haut possible, en amont de tous les services ?
Cela permettrait peut-être à la base de données de retrouver la forme ?
Et, ainsi, vos utilisateurs seront satisfaits.
OK, je l’admets, en tranchant le trafic venant de 10% des utilisateurs, vous aurez 10% d’utilisateurs insatisfaits.
Mais il vaut sans doute mieux 90% d’utilisateurs satisfaits qu’une plateforme plantée et 0% d’utilisateurs satisfaits, non ?
Enfin, difficile de faire une conférence sur la résilience sans parler de Chaos Engineering.
Qu’est-ce que c’est, le Chaos Engineering ?
Et bien, avant tout, c’est admettre et prévoir que tout peut, ou que tout va, casser.
C’est reconnaitre que plus les pannes sont fréquentes, mieux elles seront tolérées.
Et donc, provoquons des pannes ! Faisons ralentir des services. Stoppons des serveurs. Oui, en production !
(enfin, vous le ferez souvent en staging dans un premier temps, avant de passer à la production)
C’est en provoquant régulièrement des pannées vous-mêmes, pendant les heures de bureau, que vous saurez comment votre application se comporte et que vous saurez où des évolutions sont nécessaires.
Et c’est grâce à ces évolutions que votre application restera opérationnelle, même en cas de panne d’un de ses composants.
L’outil le plus connu dans ce domaine est sans doute le fameux Chaos Monkey de Netflix, qui termine des machines virtuelles ou des conteneurs. Il en existe plein d’autres, vraiment plein.
Et puis, parfois, Le Cloud peut être une approche technique qui aidera à améliorer la résilience d’une plateforme.
Notamment grâce aux possibilités d’élasticité, d’auto-réparation ou de haute-disponibilité qu’il offre.
Bien sûr, dans Le Cloud, tout se paye.
Et les fonctionnalités que je cite ici demandent d’y penser et de savoir les activer et configurer pour qu’elles soient efficaces. Pas de magie.
Et, avant de finir, quels sont vos objectifs, à vous ?
Perso, mon job n’est pas de coder une API.
Ni de configurer des serveurs.
Ni même de déployer une application.
Non, mon job est de fournir un service, de répondre aux attentes de mon entreprise et de ses clients.
Donc, développeurs, ops, et autres : tendons vers un seul métier, apprenons les uns des autres.
Et vous verrez qu’avec des compétences plus variées, on s’en sort bien mieux et on apporte bien plus de valeur.
Et, puisque nous sommes en conférence, puisqu’on entend parler de Kubernetes, de Serverless, et de 36 autres trucs tous plus sympa les uns que les autres, et qu’on voit bien trop souvent du CDD – du Conference Driven Development – après les événements comme celui-ci, je ne peux m’empêcher de vous demander :
s’il vous plait, choisissez des technologies ennuyeuses !
La dernière base de données NoSQL à la mode, OK, sur le papier, elle a l’air super sympa. Et elle marche peut-être super bien quand vous la mettez en place.
Mais quand elle va tomber en panne à 3h du matin, je vous garanti que votre collègue qui va être réveillé par les alarmes va bien galérer pour la remettre d’applomb, d’autant que pas grand monde n’aura partagé de questions/réponses à son sujet sur Stack Overflow…
Alors qu’un bon vieux MySQL, c’est peut-être bien moins attirant quand on se demande quelle techno prendre pour notre prochain projet, mais quand ça foire, il y a bien plus d’expertise et d’aide disponible !
Ca ne veut pas dire qu’il faut toujours n’utiliser que MySQL ou que X ou Y ne sont pas des choix valables.
Mais choisissez judicieusement où vous allez investir de l’énergie. Ca ne peut pas être partout.
On approche de la fin de mon talk…
Et, je suis désolé, mais, quoi que vous faissiez, un jour ou l’autre, ça arrivera.
Un jour ou l’autre, un incident surviendra, une partie de votre plateforme – ou même votre plateforme entière – s’écroulera.
Ce jour là, même dans le feu de l’action, communiquez.
Communiquez en interne, entre collègues qui interviennent pour réparer, mais aussi après de vos autres collègues, des autres services de votre entreprise.
Communiquez aussi en externe après de vos clients et utilisateurs – parfois, par le biais, justement, de collègues dans d’autres services.
Et communiquez régulièrement. Des points d’attention courts et clairs, ça aide vos collègues et vos clients et vos utilisateurs : ça les rassure. Et ils vous pardoneront mieux la panne qu’ils sont en train, tout comme vous, de subir.
Un incident, c’est un moment avec beaucoup de stress.
Et c’est un moment où il est bon de ne pas paniquer.
Parce que paniquer, c’est une très bonne façon de faire des bétises et d’empirer les choses.
Donc, pendant un incident, appliquez les solutions que vous aviez préparé en avance pour le jour où.
Vous vous êtes préparé, n’est-ce pas ?
C’est aussi le moment de suivre vos documentations et vos procédures.
Etapes par étapes.
Ce n’est pas le moment de vous dire « je sais très bien quoi faire, pas besoin de suivre la doc » et de louper une étape et d’empirer les choses.
Et enfin, un incident, plus que jamais, c’est un moment où vous devriez travailler en pair.
Deux cerveaux valent mieux qu’un. Encore plus en situation de stress.
Si vous devez jouer des requêtes SQL en base de données pour réparer, croyez-moi, il est bon d’avoir un ou une collègue à côté qui relise votre requête. Peut-être que cela aidera à voir que vous aviez oublié la clause where
!
Et même si réparer est urgent, prenez soin de vous.
On fait bien moins d’erreurs lorsqu’on est reposé, lorsqu’on a dormi, lorsqu’on n’est pas sur le feu depuis des heures.
Typiquement, gardez quelques collègues en dehors de la gestion de l’incident pour qu’ils et elles soient en forme, et tournez après quelques heures.
En cas d’incident qui arrive le soir, ne rappelez pas l’ensemble de vos équipes immédiatement : gardez des gens en réserve, pour les déclencher au bout de quelques heures si nécessaire.
Et une fois l’incident terminé, peut-être le lendemain, mais pas non plus trop longtemps après pour ne pas avoir oublié, vient le moment de réaliser un post-mortem.
Vous allez dresser la timeline du déroulé de l’incident et de sa gestion.
Vous allez identifié ce qui a marché et ce qui n’a pas marché – que ce soit sur des aspects techniques ou organisationnels.
Tout ceci vous aidera à identifier des axes d’amélioration.
À la fois pour éviter que cet incident précis se reproduise, mais aussi pour mieux gérer les futurs autres incidents…
Car, oui, d’autres incidents arriveront, c’est la vie.
Il est temps pour moi de conclure…
Et… Et bien, vous avez. Nous avons. Du boulot.
Créer, développer, maintenir une application résiliente, ce n’est pas facile.
Et c’est le moment où je réalise que je n’ai pas encore dit ce que c’est, la résilience…
Et bien, comme le dit John, la résilience, c’est l’histoire de la panne qui n’est pas arrivée.
Probablement, parce que votre application sait faire face à la chute d’un de ses composants.
Pour terminer, j’aimerais vous réafficher cette citation que j’aime beaucoup :
« Les systèmes distribués ne sont jamais ‘opérationnels’.
Ils existent dans un état permanent de service partiellement dégradé. »
Je m’appelle Pascal MARTIN.
Je suis Principal Engineer chez Bedrock à Lyon, et je suis aussi AWS Hero.
Vous trouverez toutes mes infos de contact sur pascal-martin.fr
Merci pour votre attention 👋
J’ai rédigé ce pseudo-transcript sans écouter d’enregistrements des fois où j’ai donné cette conférence entre 2019 et fin 2023. Je n’apprends jamais non plus mon discours par cœur et j’ai toujours quelques variations mineures entre deux déroulés. Aussi, ci-dessus, vous avez parcourru une version longue de ce transcript, où j’ai repris et remixé tous les morceaux que j’utilise ou non lorsque je donne ce talk en vrai, en fonction du public cible et de la longueur du créneau dont je dispose.
J’espère que ce transcript vous a été agréable et qu’il vous sera utile.
Et je vous souhaitez bon courage 💪
Et si vous organisez une conférence autour de sujets d’architecture, de Cloud (dont AWS et Kubernetes), de scalabilité, de résilience, de performance ou de couts d’hébergement… Pensez à moi 🎤 – pour ce sujet ou pour un autre ;-)