Le monitoring de flux par l'exemple
Après avoir décrit le monitoring de flux et les bonnes pratiques pour le mettre en place, passons à un cas pratique. Nous allons détailler un exemple complet et documenté de monitoring d’un mini-système d’information combinant appels de services et envois de messages, avec des modèles de code.
Les composants applicatifs présentés dans cet article ont des noms génériques (frontend, middleend et backend) afin que chacun puisse facilement les adapter à son propre contexte. Nous aurions pu, par exemple, imaginer le frontend comme une application mobile ou une application web monopage javascript, le middle-end comme une couche de services REST (API), et enfin le backend comme un système de persistance ou encore d’analyse des transactions.
À propos du code
Le code proposé dans cet article a été écrit dans un but illustratif. Il est donc le plus simple possible et ne comprend par exemple ni gestion des erreurs ni optimisation. À vous de vous en inspirer et de l’adapter (il est sous licence open source Apache) mais surtout ne le réutilisez pas tel quel.
Les étapes permettant de tester le code vous-même sont décrites dans la documentation du projet.
Pour que l’article ne soit pas trop long, nous avons utilisé des liens plutôt que du code source inclus dans le texte.
Si des portions du code vous semblent peu claires ou que vous avez des suggestions, merci de créer un ticket ; si vous avez des idées d’améliorations les pull requests sont les bienvenues.
Le système
Le premier article expliquait que les services métier ont deux caractéristiques:
- ils peuvent combiner plusieurs appels de services et/ou d’envois de messages
- ils peuvent combiner des technologies hétérogènes.
Un système d’information qui combine ces deux aspects nécessite d’avoir des outils de monitoring flexibles et qui reposent sur des technologies interopérables.
L’architecture applicative présentée ici prend en compte ces éléments:
Côté métier :
- Un serveur frontend qui expose un service et un site web avec un formulaire qui l’utilise.
- Un serveur middle-end expose deux services pour le frontend.
- Un serveur backend traite de manière asynchrone des messages publiés par le middle-end sur un bus.
Côté monitoring :
- un module de monitoring dans chaque brique applicative;
- un bus dédié recevant des messages de chacun de ces modules;
- un serveur de CEP traitant ces messages pour lever des alertes et produire des KPIs;
- une base de donnée indexant les messages et les informations générées par le CEP;
- un outil de dashboarding pour présenter les informations de monitoring.
Format des messages de monitoring
Il est important de bien définir le format des messages de monitoring, même si le système de traitement doit être conçu pour pouvoir s’adapter facilement à différents cas. L’utilisation d’un gabarit commun permet de simplifier l’exploitation des messages dans le CEP et les dashboard.
Dans notre cas, nous avons choisi d’utiliser des messages au format JSON:
{
"correlation_id": "octo.local_MonitoringBase_24389_2015-01-30 11:05:29 UTC_36cddd01-7bcd-4ced-8024-919ff1dbe6ca", // l'id de correlation
"timestamp": "2015-01-30T12:05:29.230+01:00", // horodatage du message
"module_type": "FrontendApp", // nom du module qui envoie le message
"module_id": "FrontendApp_octo.local_001", // identifiant du module qui envoie le message
"endpoint": "GET /messages", // nom du service
"message_type": "Send message to backend", // type de message
"begin_timestamp": "2015-02-19T22:11:15.939+01:00", // optionnel: horodatage du début de l'action en cours
"end_timestamp": "2015-02-19T22:11:15.959+01:00", // optionnel: horodatage de la fin de l'action en cours
"elapsed_time": 0.020169, // optionnel: temps mis pour l'action en cours
"service_params": {
// optionnel: paramètres d'appel du service
},
"headers": {
// optionnel: en-têtes d'appel du service (en-têtes http par exemple)
}
"result": {
// optionnel: résultat de l'appel du service
}
}
Détail de chaque composant
À propos du monitoring
Pour fournir les données nécessaires, chaque brique embarque du code permettant de récupérer les informations utiles et de les poster dans un bus. Pour pénaliser au minimum les performances du code applicatif, l’envoi des messages s’effectue systématiquement dans un thread dédié, et une gestion d’exception isole le code de monitoring du code métier.
Conformément aux recommandations du premier article, c’est un bus ZeroMQ qui a été choisi pour ses très bonnes performances.
Frontend
Le serveur frontend est en Ruby et utilise le framework web Sinatra qui est parfait pour comme ici exposer facilement des services web.
- app_base paramètre le socle de l’application, et fournit une méthode pour appeler des services du serveur middle-end.
- le répertoire static contient le site web
- frontend_app expose le service métier qu’appelle le site web et appelle deux services du middle-end l’un après l’autre.
Monitoring
Le code de monitoring est situé dans la classe monitoring_base.rb
Le framework Sinatra fournit les points d’entrées nécessaires pour le monitoring sous forme de méthodes before
et after
où toutes les informations de la requête en cours sont accessibles. Pour pouvoir stocker des informations pendant l’exécution de la requête, comme l’heure du début de son exécution, un champ est ajouté à la classe Request.
La méthode permettant d’appeler des services est surchargée pour faire deux choses :
- envoyer des copies de l’appel au système de monitoring;
- ajouter des en-têtes http dans l’appel de service pour propager l’identifiant de corrélation ainsi que l’heure de l’appel
Les données sont postées dans une queue et consommées dans un thread séparé.
Middle-end
Le serveur middle-end utilisé Spring, Spring Boot permet de configurer facilement une application et Spring MVC d’exposer des services REST.
- MiddleEndController contient le controller qui expose les deux services exposés.
- RedisProvider fournit l’accès au bus pour envoyer des messages au backend.
Monitoring
Du fait du choix de la technologie Spring, la mise en place de monitoring demande quelques acrobaties :
- Un HandlerInterceptor fournit un point d’entrée au début et à la fin de l’exécution de chaque requête http qui permet de créer les messages qui seront envoyés au monitoring.
- Il est nécessaire de sous-classer le HttpServletRequest pour pouvoir stocker des informations pendant l’exécution de la requête, comme l’heure du début de son exécution.
- Finalement HttpServletRequest qui représente la requête et HttpServletResponse la réponse ne donnent pas d’accès au contenu de la requête ou de la réponse car leur envoi est streamé. Il est donc nécessaire de wrapper les deux classes pour enregistrer les contenus pendant la tranmission, et pouvoir ainsi les relire ensuite.
Le résultat se trouve réparti dans 5 classes :
- MonitoringServletRequest représente la requête, il fournit quelques méthodes utilitaires – dont la récupération de l’identifiant de correlation – et utilise un RecordingServletInputStream pour enregistrer le contenu.
- RecordingServletResponse représente la réponse et enregistre le contenu à l’aide d’un RecordingServletResponse.
- MonitoringInterceptor est l’intercepteur qui envoie les messages en récupérant les informations fournies par la requête et la réponse.
Le code en charge de l’envoi des messages est situé dans un projet partagé car il est utilisé par le middle-end et le backend. L’essentiel du code est situé dans le MonitoringMessageSender qui utilise un thread dédié à l’envoi des messages et alimenté par une queue.
RedisProvider
a été modifié pour transmettre l’identifiant de correlation dans les messages envoyés au backend.
Le bus applicatif
Il s’agit d’un serveur Redis : il est principalement utilisé comme un cache clé-valeur mais son API lui permet également de servir de bus de messages. Ses principaux avantages sont sa facilité de mise en œuvre et sa vitesse de traitement.
Le backend
Nous avons simulé une application de traitement de messages à l’aide d’un pool de threads :
- ApplicationBase fournit le socle applicatif qui consomme les messages depuis Redis et les fait traiter par un pool de thread Java.
- Backend traite les messages.
Monitoring
Comme le code de réception est spécifique à l’application, le monitoring est complètement intégré au socle applicatif. Pour l’envoi des messages, il s’appuie sur le même projet partagé que le middle-end.
Le Complex Event Processing
Principes
Le composant de Complex Event Processing dépile les messages en provenance des différents modules (frondend, middle-end, backend). Il réalise alors en parallèle l’insertion dans la base de données et la mise à jour d’un état en mémoire. L’évolution de cet état peut générer des alertes qui seront à leur tour persistées dans la base.
Il est implémenté en Java à l’aide du framework d’intégration Apache Camel et se présente comme une application autonome.
- Les messages sont dépilés depuis ZeroMQ à l’aide d’un Connecteur qu’il nous a été nécessaire de réécrire en utilisant la librairie jeroMQ. Le composant existant fonctionnait en effet avec des bindings scala non applicables.
- L’état en mémoire ainsi que le déclenchement d’alertes sont implémentés en utilisant le framework Esper. Camel fournit le connecteur qui permet de s’y interfacer. Les règles sont écrites avec le DSL interne nommé EPL (Event Processing Language) dans le fichier de configuration Camel.
- Les messages et alertes sont persistés dans un cluster Elasticsearch à l’aide d’un connecteur maison utilisant la bibliothèque Jest (il n’est donc pas conçu pour monter en charge). Le connecteur par défaut s’enregistre comme un nœud du cluster Elasticsearch et cela rendait les tests locaux plus compliqués.
Interprétation des évènements
Le flux d’évènements est interprété pour construire des indicateurs en temps réels grâce au language d’analyse Esper Process Language (EPL). Ces statuts sont requêtés à intervalles réguliers afin de détecter des comportements anormaux comme des dépassements de seuils.
Nous avons cherché ici à remonter trois types d’alerte :
- Un temps de traitement trop important pour l’un des composants à l’aide de l’attribut
elapsed_time
des messages. - Un temps de traitement trop important pour un flux sur l’ensemble de la chaîne, en utilisant l’identifiant de correlation.
- Throttling : Un nombre d’appels supérieur à un seuil fixé dans une unité de temps fixe (ici une moyenne de plus de 3 appels sur 10 secondes).
La base de donnée du monitoring
Il s’agit d’une base Elasticsearch qui indexe automatiquement les données à leur insertion. Pour que les données soient indexées au mieux, il suffit de créer un index à l’avance pour que les champs soient indexés de la bonne manière.
Le dashboard
Avec les données déjà structurées et stockées dans Elasticsearch, Kibana est la solution naturelle pour les dashboard : des assistants permettent de facilement créer les différents graphiques en fonctions des données présentes dans la base.
Voici par exemple un dashboard des percentiles des appels sur les différents serveurs (la configuration de ce dashboard est disponible dans les sources) :
Les composants difficiles à monitorer
Les cas présentés ici sont favorables car les composants ne sont pas trop compliqués à monitorer, même si le middle-end demande un peu de gymnastique. Malheureusement dans tout SI d’une certaine taille, il existe toujours au moins une brique "boite noire", type portail ou plateforme e-commerce, qu’il est difficile d’outiller convenablement. Pour ces composants deux choix sont généralement possibles :
Utiliser les points d’extensions fournis par l’outil
Cette solution est la plus conforme. Cependant ces API sont souvent d’une qualité inférieure au reste et avant de vous lancer il y a trois choses à vérifier :
- La documentation est-elle suffisamment détaillée, particulièrement quand des objets internes au composant sont exposés ?
- Les API sont elles stables ? Comme ces API sont assez proches du moteur des outils, elles sont plus susceptibles de ne pas être compatibles d’une version à l’autre.
- Toutes les informations dont vous avez besoin sont-elles exposées ?
En fonction des réponses à ces trois questions, la deuxième solution sera peut-être préférable.
Utiliser un proxy
Si les échanges avec le composant se font par http, l’autre solution est de mettre en place un proxy comme nginx pour générer les messages de monitoring. En configurant les logs vous devriez pouvoir obtenir les informations dont vous avez besoin, et un composant custom est nécessaire pour les pousser vers le serveur de CEP.
Cette solution a le désavantage d’ajouter une couche supplémentaire d’infrastructure, mais évite d’avoir à développer du code trop spécifique à un outil.