Chaos engineering : les étapes pour y arriver sur votre application

GraphQL Go Upload

Dans les dernières années, les méthodes d'hébergement et de développement des applications (micro-services) nous ont amenées à repenser la façon dont nos applications communiquent entre elles ainsi que la façon dont nous servons notre service au client. La multiplication des services permet d'avoir des applications mieux maîtrisées en terme de développement et de métier mais amènent également leur lot de problématiques : les sources d'erreur sont en effet multipliées.

Le Chaos testing, ou Chaos engineering est une philosophie qui demande aux développeurs de prendre en compte les éventuelles pannes pouvant survenir sur une application et ainsi se préparer à affronter une situation chaotique, à savoir :

  • Des erreurs applicatives,
  • Des erreurs d'infrastructure,
  • Des erreurs réseau,
  • De manière gélérale, toute erreur imprévue.

Initié en 2011 par Netflix, les équipes ont ainsi décrits quelques principes devant être respectés, nommés les [http://principlesofchaos.org/](Principes du Chaos). Je vous invite à les lire avant d'entâmer cet article. Nous verrons ici les étapes pour arriver à faire du Chaos engineering dans vos équipes et ainsi avoir une application résiliente à la panne.

Toutes les pannes citées ci-dessus peuvent (et doivent) être anticipées par les développeurs lors du développement de nouvelles applications mais les applications existantes aujourd'hui en production sont parfois loin d'être résilientes à toutes ces sources d'erreur. C'est pourquoi il m'a semblé intéressant de partager avec vous les étapes qui vont permettront d'arriver à obtenir une application, je l'espère, plus résiliente.

Le premier conseil que j'aimerais donner pour commencer est de ne pas casser vos services sur votre environnement de production dès maintenant. Vous avez des environnements de pre-production ou recette, servez-vous en en premier ! Inutile de mettre à mal votre production alors que vous avez la possibilité d'anticiper ces pannes, vos utilisateurs ou clients n'ont pas à subir cela.

Ensuite, comme spécifié ci-dessus, les erreurs peuvent être à la fois infrastructure, réseau mais également applicatives ! C'est à vous, développeurs, d'implémenter les services qui permettront de palier à ces pannes, même parfois aux problèmes d'infrastructure ou réseau. Enfin, procédez étape par étape. Nous allons d'ailleurs voir dès maintenant les différentes étapes que vous pouvez aborder, pas nécessairement dans l'ordre cité.

Quelques informations avant de commencer

Je ne citerais volontairement pas d'outils avant de commencer car chacun est libre d'implémenter ses règles de chaos. L'important est d'observer et de réagir à une panne que vous aurez provoqué; ainsi, quelques lignes de commandes simples peuvent être utilisées afin de commencer à jouer sur vos environnements.

Pour couper un processus, un simple kill peut être utilisé :

$ kill -9 <pid>

Également, dans le cas ou vos applications sont déployées sur un cluster Kubernetes, pour supprimer un pod aléatoirement :

$ kubectl delete pod/`kubectl get pods | cut -d' ' -f1 | sed 1d | shuf -n1`

Vous le voyez, vous pouvez scriper vos règles de chaos assez simplement. Bien, voyons maintenant les différents cas que vous pouvez commencer à observer.

Ajoutez de la latence

Pour commencer de façon pas trop violente, vous pouvez commencer par simplement ajouter de la latence sur vos serveurs, ainsi, vous devriez commencer à voir apparaître divers problèmes de timeout sur les services internes et externes qui peuvent endommager votre application. Ces erreurs sont très fréquentes et arrivent par forte charge ou par soucis de connectivité par votre provider. Vous devez les prendre en compte. Pour ajouter de la latence réseau, vous pouvez vous connecter sur une machine et simplement jouer avec la commande "tc" (pour TrafficControl) :

# Ajouter 500ms de latence
$ tc qdisc add dev eth0 root netem delay 500ms

# Vérifier que la règle a été appliquée
$ tc -s qdisc
qdisc netem 8002: dev eth0 root refcnt 2 limit 1000 delay 500.0ms

# Supprimer la règle
$ tc qdisc del dev eth0 root netem

Simple et efficace pour commencer à tester et observer le comportement de votre application sous latence !

Coupez vos tâches programmées

Sans casser directement votre application, vous pouvez commencer par réfléchir à ce qu'il se passerait si vos jobs asynchrones (envoi de mails, synchronisation de données, ...) cessaient de fonctionner. Ceux-si ne sont en effet pas directement visibles par vos utilisateurs et ce n'est parfois pas grave s'ils sont déclenchés avec un peu de retard, pourvu qu'ils soient déclenchés.

Prennons un exemple de dénormalisation de données afin de rendre ces données sur un front : lorsque vous indexez de nouvelles données, pensez à suffixer votre indexe d'un timestamp afin que celles-ci n'impactent pas vos données courantes. Aussi, vos données courantes doivent être stockées dans un index (ou mieux : un alias qui pointe sur un index) dédié, afin de pouvoir effectuer le switch à la fin du job de dénormalisation et ainsi s'assurer qu'en cas d'erreur du job, l'indexe ne soit pas touché et que les "anciennes" données soient toujours bien affichées sur votre front.

Bien évidemment, s'il s'agit de jobs devant absolument être déclenchés à l'heure (ouverture de droits suite à une commande faite en amont par exemple), assurez-vous que vos jobs sont correctement exécutés et ayez de l'alerting et du retry sur ceux-ci.

Coupez votre serveur de pub/sub

Lorsque vos applications échangent des données avec un serveur de pub/sub (publisher / subscriber), vous devez également vous attendre à ce que celui-ci puisse être rendu indisponible. Même lorsque votre serveur est en mode cluster, vous n'êtes malheureusement pas à l'abris d'un crash.

Vous devez donc vous assurer que toutes les notifications d'événement ayant échouées à être envoyées au serveur de pub/sub soient stockées afin d'être renvoyées dès que celui-ci sera de nouveau disponible. Il est en effet beaucoup meiux de pouvoir rattraper le temps plutôt que de perdre des données importantes pour votre métier.

Coupez votre base de données

Nous arrivons ici à un point critique : en général, lorsqu'une base de données est rendue indisponible, cela pose soucis à beaucoup d'applications car elles ne peuvent plus accéder à leurs données, ni lire, ni écrire. En plus du fait de vous conseillez de monter un cluster de vos bases de données, je suggèrerais également de vous attendre à ce que celles-ci puissent devenir indisponibles : données corrompues, liaison réseau qui échoue, ...

L'important est surtout ici d'essayer de rassurer vos utilisateurs et de leur afficher quelque-chose de sympa. Si vous avez quelques informations en cache dans le local storage de l'utiliateur, profitez-en et affichez-lui ces informations, à défaut de mieux.

Supprimez vos données

Votre base de données pourrait d'ailleurs rester disponible mais le problème pourrait très bien provenir de vos données qui deviendraient corrompues ou qui seraient effacées à la suite d'une faille dans votre application. Dans ce cas, vous devez vous assurer que vous pouvez être capable de détecter cela et de re-importer rapidement un backup stable et récent.

De la même façon, il est très important que vous testiez vos backups ! À tout moment, ceux-ci peuvent être défaillants et il serait dommage de ne pas pouvoir restaurer un backup récent en cas de perte de données car vous ne vous êtes pas assurés que ceux-ci étaient fonctionnels.

Coupez vos micro-services

Vos micro-services sont très certainement contactés par une des deux manières suivantes : par une API Gateway (GraphQL ?) en amont et des liaisons HTTP ou gRPC ou bien s'agit-il peut-être uniquement de micro-services répondant à des événements (consumers / producers). Dans tous les cas, vous devez vous attendre à des coupures sur ces applicatifs et vous assurer, a minima, qu'ils ne mettent pas en péril toute votre application. Ainsi, la partie concernant le micro-service en question pourrait être rendue indisponible (gestion de mise en favoris, par exemple) mais les autres fonctionnalités continueraient de fonctionner. Mieux encore, dans ce cas, vous pouvez prévenir vos utilisateurs que vous avez un soucis sur le fait de récupérer leurs favoris mais en profiter pour leur pousser les derniers contenus disponibles, à défaut de rien.

Si votre infrastructure le permet, une solution envisageable serait également de servir un cache de fallback aux utilisateurs en utilisant une stratégie de cache de type LRU (Last Recently Used) ou LFU (Last Frequently Used) en fonction des cas. Ainsi, les données ne seraient pas forcément complètement à jour, mais l'utilisateur aurait a minima quelques contenus disponibles et dans la majorité des cas n'y verait que du feu. Bien sûr, le cache de fallback peut représenter une grosse volumétrie, c'est pourquoi il est important de calculer les données qui seraient potentiellement à stocker dedans et ainsi maîtriser les données que vous cachez.

Augmentez la complexité du chaos

Lorsque vous arrivez à maîtriser la plupart des pannes pouvant arriver à votre infrastructure, il est alors temps d'augmenter l'échelle et de vous préparer à maîtriser de multiples pannes en parallèle. C'est le chaos. En effet, plusieurs micro-services peuvent être rendus indisponibles en simultané : si vous aviez donc prévu un cas de fallback de votre recommendation de produit, par exemple, sur un autre micro-service permettant de retourner les derniers produits disponibles dans votre catalogue, il vous faut être en mesure de trouver une autre solution.

Dans le cas ou votre application serait disponible dans plusieurs zones géographiques, rendez alors une zone complètement indisponible afin de vous assurer que vos clients seront bien redirigés vers la seconde zone. Vous commencerez alors à jouer avec des outils tel que [https://netflix.github.io/chaosmonkey/](Chaos Monkey) développé initiallement pour les besoins de Netflix sur ces sujets.

Vous êtes prêts à jouer le chaos sur votre production

Souvenez-vous, jusque là nous êtions sur des environnements hors-production. Il y aura un moment ou il vous faudra tester ces différents cas sur votre environnement de production. Une bonne pratique lorsque vous en arrivez là est de mettre en place des "game-day" afin de consacrer une journée complète à mettre le chaos sur votre infrastructure et de mobiliser vos équipes afin qu'elles soient prêtes à intervenir pour tester leur solution de fallback et/ou rétablir la panne en cas d'échec. Même en cas de panne, cela ne sera que bénéfique pour votre projet car il vous permettra d'améliorer la résilience de votre application au fur et à mesure, n'ayez donc pas peur d'en arriver là.

Conclusion

La chaos engineering s'appréhende étape par étape car dans le cycle de vie d'un projet, il est en général une des dernières étapes une fois l'application stable et en production. Il vous permet à la fois de rendre votre application résiliente aux pannes mais aussi de préparer vos équipes à intervenir sur ces sujets qui peuvent être réellement frustrants lorsqu'ils arrivent pour à la fois être capable de les régler rapidement mais aussi d'essayer de minimiser les chances qu'ils arrivent.