Aujourd’hui, dans ce nouvel article, nous allons aborder les Tests de Charge ou Load Testing en anglais. Dans une seconde partie on verra deux outils de test de charges, leurs avantages ainsi que leurs inconvénients.
À quoi ça sert les Tests de Charge ?
Les tests de charges sont une catégorie de tests de système informatiques (applications, sites Web, API…), au même titre que les tests unitaires, les tests End-to-End (que j’ai notamment évoqués, dans un précédent article), tests fonctionnels, etc.
Ils permettent de tester l’infrastructure en simulant une charge de travail et ainsi de mesurer les capacités, les limites et les réactions du système et de l’infrastructure face à une grande quantité de trafic.
La plupart du temps, c’est l’équipe DevOps qui est chargé d’effectuer ces tests, afin de mieux ajuster les ressources nécessaires pour gérer d’éventuels pics d’affluence. Ces pics pourraient ralentir l’application, la faire planter ou voire même entraîner des erreurs inattendues.
On distingue trois seuils d’affluence :
- Faible affluence : très peu de trafic, trafic minimal. Exemple : en soirée ou pendant les jours fériés.
- Moyenne affluence : activité normale, conditions nominales. Exemple : en journée.
- Forte affluence : forte demande, souvent inattendue ou spontanée. Exemple : lors du lancement d’un nouveau produit ou d’événements suscitant l’intérêt du public.
Il est donc important d’avoir une infrastructure capable de répondre au mieux à ces différents niveaux de trafic, afin de prévenir les temps d’arrêt, ou downtime
.
Cette charge de travail peut prendre la forme d’un VU (Virtual User, utilisateur virtuel), qui reproduit le comportement d’un utilisateur réel sur l’application, par exemple en allant sur la page d’accueil, en se connectant ou en consultant des produits … Ce processus inclut tous les appels API aux différents services, qu’ils soient directs (requêtes HTTP) ou indirects (tests End-to-End).
Ce comportement est programmé à l’avance dans ce qu’on appelle un scénario. Ces scénarios décrivent les actions à exécuter, le nombre d’utilisateurs virtuels à simuler, la durée du tests ou encore la courbe d’évolution du trafic (linaire, logarithme, aléatoire).
Adaptez vos scénarios en fonction du type d’application, de sa fréquentation habituelle, etc. Pour chaque application, organisation et projet, les tests de charge diffèrent.
Types de tests de charge
Il existe plusieurs types de tests de charge pour mesurer les performances du système en fonction de l’affluence.
1. Smoke test 🔥
test de fumée
Et non, ce test ne consiste pas à pousser les limites matérielles des machines jusqu’à mettre le feu au datacenter, mais plutôt l’inverse. Il vise à tester le système avec une charge très faible d’utilisateurs sur un court laps de temps. Ce qui permet d’établir les performances minimales de base du système.
2. Average-load test 📊
test à charge moyenne
Les tests à charge moyenne permettent de tester le système sous une affluence moyenne, en maintenant cette charge durant un période prolongée (plusieurs heures, par exemple).
3. Stress test 🥵
test de résistance
Également nommé test aux heures de pointe ou tests de surtension, ce test permet de découvrir comment le système se comporte face à une forte affluence.
4. Spike test ⛰
test de pic
Ce test permet de vérifier si le système peut correctement encaisser un pic d’utilisation soudain et massif de connexions, ainsi que sa capacité de « déstockage » face à une baisse très rapide du traffic.
Cela permet de s’assurer du bon fonctionnement (ou non) de l’infrastructure durant des événements exceptionnels, tels que le lancement d’un nouveau produit, les soldes, la diffusion streaming d’évènements sportifs majeurs.
Les scénarios pour ce test doivent être adaptés en supprimant ou en ajoutant des cas par rapport aux autres tests, car ces événements exceptionnels ne représentent pas une journée standard.
5. Breakpoint test 🔴
test point de rupture
Ce test permet de découvrir le point de rupture (breakpoint), c’est-à-dire, à partir de quel moment et de quelle manière le système tombe en panne.
Le test monte à des valeurs irréalistes (plusieurs millions d’utilisateurs par seconde, par exemple) et permet donc d’ajuster au mieux l’infrastructure pour des utilisations plus normales et réalistes.
Celui-ci doit généralement être arrêté manuellement ou automatiquement lorsque les seuils d’erreur ou de temps de réponse commencent à échouer / augmenter. Lorsque ces problèmes apparaissent en nombre, cela indique que le système a atteint ses limites.
6. Soak test 💦
test d’endurance
Similaire au test à moyenne charge, le test d’endurance évalue le système sur une plus grosse période, allant de plusieurs heures à plusieurs jours non-stop. Le nombre de VU ainsi que les périodes de montée et de descente de charge restent les mêmes que le tests à charge moyenne.
Ce test se concentre sur l’analyse de la dégradation des performances ainsi que sur la consommation des ressources, tout en évaluant également la stabilité du système sur des périodes prolongées.
Locust vs K6
Comme vous l’avez compris, les tests de charge doivent être exécutés à l’aide de solutions capables de créer des scénarios, balancer la « sauce » et surtout en sortir des data (des métriques) qui permettent de voir comment s’est déroulé le test et comment le système a réagi.
Dans cette partie, nous allons nous intéresser à deux solutions de load testing, très connues : K6 et Locust.
Note : Plus on voudra simuler des utilisateurs (VU) et des requêtes par seconde, plus il est nécessaire d’avoir une machine puissante avec beaucoup de RAM et de CPU, sans oublier une bande passante adéquate. Cependant, même avec un ordinateur quantique de la NASA, on est vite limités par des contraintes techniques : le nombre maximum de sockets HTTP ouverts, le nombre de ports ou par la bande passante. Pour remédier à cela, on peut utiliser des solutions de load testing de façon distribuées, à l’image d’un Botnet. Après tout, un DDoS (Distributed Denial of Service) n’est ni plus ni moins qu’un test de charge un peu moins poussé et moins conventionnel. Plus on multiplie horizontalement les machines de test, plus on aura de « patates » (unité informel de puissance physique d’une machine de load testing) pour charger.
Locust 🦗
Locust est un logiciel open source de tests de charge, créé en 2011 et développé en Python. Le projet est toujours maintenu, sa dernière version est la 2.32.6 (sortie il y a 2 jours au moment où j’écris cet article). Disponible sous forme de package Python ou d’image Docker, il peut facilement être déployé.
Les scenarios de test doivent être écrits en Python, en utilisant des abstractions de certaines fonctions, comme le client HTTP qui est géré par Locust lui-même.
De plus, l’utilisation du multithreading et d’une architecture distribuée permet à Locust d’être un outil puissant est scalable sur plusieurs machines, en mode Master-Workers.
Il supporte également plusieurs protocoles de communication tels que le HTTP, gRPC, Kafka et même MQTT, idéal pour tester des systèmes IoT.
Utilisation
Locust peut être utilisé en ligne de commande et peut même être intégré en tant que librairie dans votre projet Python.
Pour lancer les tests, il suffit de préciser le fichier locustfile.py
contenant le scénario de tests puis, via la CLI (ou la web UI), de précisé l’URL de la cible à attaque, le nombre d’utilisateur (nombre de VU) et le taux de d’apparition des utilisateurs (exemple : 10 par secondes).
Locust dispose d’une simple interface Web (Web UI) afin de suivre en direct les résultats et les erreurs des tests, avec des jolis graphiques.
Lorsqu’un test est lancé on retrouve trois gros boutons (génial ! 🤩) « Edit », « Stop » et « Reset », qui permettent respectivement de modifier les options du test (nombre de VU, le spawn rate et l’URL), d’arrêter le test et de réinitialiser les données.
Voici un exemple de scénario :
from locust import HttpUser, task
class Scenario(HttpUser):
def __init__(self, http):
super().__init__(http)
self.API_VERSION = "v2"
self.headers = { 'Content-Type': 'application/json', "Authorization": `Bearer <Token>` }
@task
def create_user(self):
self.client.request("post", f"https://gorest.com.in/public/{self.API_VERSION}/users" name="create_user", headers=self.headers)
@task
def get_posts(self):
response = self.client.request("get", f"https://gorest.com.in/public/{self.API_VERSION}/posts", name="get_posts", headers=self.headers)
if len(response) == 0:
raise Exception("no post returned")
Métriques et rapport
Sur l’interface graphique, on retrouve les métriques du test sous forme graphique. On y retrouve les métriques suivantes :
- RPS (Requests Per Second) : le nombre de requêtes par seconde.
- Failures/s : le nombre d’erreurs/échecs par seconde.
- Response Time : Temps de réponse moyenne des requêtes en millisecondes (ms) et découpé en centiles (50ième percentile, 95ième percentile).
- Number of Users : Nombre d’utilisateurs.
Il est possible d’exporter ces données au format CSV et également d’exporter ces résultats et graphiques sous forme de page HTML, que l’on peut ensuite intégrer dans des présentations. 😉
En résumé, Locust est un outil assez simple à utiliser. Les scénarios se programment en Python, qui selon moi, permet d’écrire et de comprendre facilement les scripts, étant donné que le Python est un langage verbeux et assez naturel. Ce qui est un bon argument. Quelques métriques sont disponibles pour suivre le comportement du système et le déroulement du test. L’exportation en HTML ou en PNG des graphiques permet de les intégrer dans des présentations PowerPoint ou dans des cahiers de recettes.
K6 🐊
K6 est une solution développée en 2017, puis reprise par Grafana Labs en 2020, qui est derrière pas mal de solutions de monitoring très utilisées dans le monde du cloud native (Prometheus, Grafana, Loki, Tempo, etc.). Écrit en Go, il est Open Source et disponible en exécutable ainsi que sous forme d’image Docker.
Reprenant le design et la simplicité des outils de Grafana, K6 s’intègre parfaitement à cet écosystème. Pour les utilisateurs de Grafana Cloud, l’intégration est complète avec un onglet dédié, depuis lequel vous pouvez lancer directement vos scénarios, utilisant l’infra de Grafana.
Quant aux scénarios, ils doivent être programmés en JavaScript. Avec la possibilité d’importer des enregistrements de session pour des tests End-to-End, notamment grâce à K6 Studio.
Il est également possible de tester sur plusieurs protocoles, tels que GraphQL, gRPC, Websocket, soit nativement sur K6, soit via des extensions.
Utilisation
De par sa conception, K6 est très orienté CLI ; la Web UI est presque secondaire. Tout peut se faire en ligne de commande : lancer des tests, obtenir les métriques et exporter les rapports. De ce fait, il peut parfaitement être intégré dans des pipelines. C’est notamment le cas avec une pleine intégration sur GitLab, qui permet d’afficher le rapport des tests directement dans le fil de la MR.
De nombreux arguments en ligne de commande permettent de paramétrer : le nombre d’utilisateur, la durée du tests, l’hôte à attaquer, etc.
Sa Web UI optionnelle permet de suivre en direct l’avancée du test de charge ainsi que l’évolution des métriques.
Voici un exemple de scénario :
import http from 'k6/http';
import { check } from 'k6';
import { Counter } from 'k6/metrics';
export const requests = new Counter('http_reqs');
export const options = {
scenarios: {
sampleTest: {
executor: 'constant-arrival-rate',
exec: 'sampleTest',
duration: '10m',
rate: 10,
timeUnit: `1s`,
preAllocatedVUs: 1,
maxVUs: 1,
}
}
};
export function get_pizza() {
let res = http.get("https://quickpizza.grafana.com");
check(res, {
'status is 200': (r) => r.status === 200,
});
}
Métriques et rapport
K6 fournit pas mal de métriques telles que :
- Le nombre de requêtes par seconde
- La durée moyenne des requêtes
- Le nombre de requêtes échouées par seconde
- Le débit sortant
- Le débit entrant
- Le nombre de VUs par seconde
Bref… Tout un tas de données qui permettent de mieux observer l’infrastructure ciblée.
Une fois le test terminé, un gros bouton (ouais !) permet d’exporter les graphiques et les données sous forme de page HTML, qui peut ensuite être intégrée dans un rapport de test ou une présentation.
Il est possible d’utiliser un exporter Prometheus, ce qui permet d’avoir un tableau de bord Grafana pour suivre les tests de charge. Cependant, il n’est pas possible d’exporter les données au format CSV (désolé les data analystes).
Pour résumer, K6 est un outil moderne, cloud native, qui s’intègre parfaitement à la suite Grafana, offrant une grande quantité de métriques. Il est généralement utilisé de manière automatisée dans des pipelines, par exemple. Les scénarios sont en JavaScript et grâce au framework qui permet de créer des scénarios poussés.
Conclusion
Les tests de charge sont importants dans la mise en place d’une infrastructure. Plusieurs types de tests de charges permettent de s’assurer d’un fonctionnement optimal de la plateforme, que ce soit à forte, moyenne ou faible affluence. Des scénarios sont joués afin de simuler l’activité des utilisateurs en reproduisant les flux d’appels associés.
Des outils de test de charge sont disponibles en version open-source sous forme d’image Docker ou bien de libraire et également, en version SaaS (Software as a Service), comme le cas de Locust et K6, deux outils très populaires. Chacun a ses avantages et inconvénients ; comme par exemple, une intégration parfaite à l’écosystème de Grafana pour K6, ou encore un langage verbeux et simple pour éditer les scénarios, comme Locust.
Personnellement, j’ai une petite préférence pour Locust, car pour l’avoir utilisé dans un projet, je trouve qu’il est plus « user friendly » dans sa façon d’exécuter les tests et dans l’utilisation du Python pour l’écriture des scénarios. Bien qu’il manque, selon moi, quelques métriques sympa et la possibilité de définir entièrement les scénarios avec le nombre d’utilisateurs à générer, la cible ou encore le type de test, comme ce que propose K6, ça reste un très bon logiciel.
Mon avis sur les tests de charge est qu’ils ne devraient ni être long ni complexes, mais plutôt faciles à réaliser. C’est pour cette raison que j’apprécie particulièrement les interfaces conviviales, avec de gros boutons, permettant même à une personne non technique de les exécuter et d’interpréter les résultats. Les tests de charge ne doivent pas représenter une contrainte supplémentaire, surtout qu’ils interviennent généralement vers la fin d’un projet, en phase de recette.
Idéalement, il est préférable de réaliser les tests de charge le plus tôt possible dans le projet. Cela permet d’identifier des problèmes, tels que des goulots d’étranglement, avant qu’il ne soit « trop tard ».
De plus, j’apprécie particulièrement de mettre en relation les données issues des tests de charge avec les métriques de la plateforme, telles que la consommation de CPU, de RAM, le nombre de réplicas ou encore, le nombre d’erreurs, afin d’obtenir une vision globale et voir concrètement ce qui s’est passé.
Je travaille actuellement sur un SaaS qui utilise Locust et de l’IaC (Infrastructure as Code) pour déployer facilement et rapidement un botnet destiné à de gros volumes de tests de charge. Pour l’instant, cet outil est destiné à une utilisation interne, mais pourquoi le mettre en open-source à l’avenir (à déterminer).
J’espère que cet article vous a intéressé et qu’il vous a donné la « Load Testing Mania », l’envie de tester les performances de toutes vos applications. Si c’est le cas, n’hésitez pas à me partager vos volumes de test pour voir qui aura la plus grosse … infra bien sûr 😉