Health Check, ou comment monitorer ses APIs

Laurent Carnaroli
YounitedTech
Published in
5 min readJun 25, 2019

Le besoin de monitorer son système d’information

Chez Younited on a plein d’API. Ok on n’est pas Netflix mais on commence à en avoir tellement qu’il devient difficile de suivre leur état en temps réel.

En plus, lorsque l’on voit le type de requêtes et lecture de traces que sont obligés de faire nos copains du DevOps on s’est dit qu’il était temps de se faciliter la vie et s’offrir un moyen facile de surveiller toutes nos APIs !

Le HealthCheck bien sûr !

L’idée était donc d’offrir des points d’entrés et une méthodologie standard pour s’assurer de deux choses principalement:
— La présence / le bon déploiement de la ressource
— Un rapport détaillé donnant l’état du service et ses dépendances (opérationnel, dégradé ou carrément mort).

Ce que nous recherchons c’est d’avancer vers un système à High Availability où les services sont découplés et ou le chemin critique de l’application est clairement identifié.
L’approche n’est pas nouvelle et elle fait même l’objet de développements et articles autour de cela mais nous avons opté pour une implémentation maison qui nous semble plus simple et nous offrant plus de liberté.

L’implémentation se découpe donc en 3 parties que nous allons détailler.

Le NuGet : l’interface côté API

Pour récolter les informations de chaque API nous avons donc défini un NuGet contenant une interface IHealthChecker à implémenter et offrant des mécaniques pour effectuer ses tests et retourner les résultats.

Comme nos APIs sont en .Net et hébergées sur Azure il nous est donc facile de publier ce NuGet sur notre feed privé.

Le cœur du health check se trouve ici dans l’implémentation de la méthode HealthCheckAsync().
Jetons tout d’abord un œil à ce que nous devons renvoyer:

- Le couple Domain / ComponentName permet d’identifier l’application au sein du système d’information.
- OverallHealthCheckStatus donne le status global de l’application, 3 valeurs sont possibles: Healthly, Sick ou Dead.
- Une collection de TestResult permettant de diagnostiquer précisément l’application.

C’est lorsque l’on se demande ce que l’on va renvoyer dans ces résultats que le travail commence:

Nous devons donc renvoyer tout un ensemble de résultats d’auto-diagnostic. Le runner fourni avec le NuGet va être responsable de calculer le statut global du test.

Voici un exemple d’implémentation:

La classe HealthCheckerRunner est importante ici car elle permet :

  • d’ajouter différents types de tests à exécuter (AddHealthCheckTest, AddDependencyTest, AddSqlServerCheck etc)
  • de calculer le résultat global du health check en fonction de leur criticité

L’objet TestParameter permet de passer des paramètres à un test, définir sa criticité et éventuellement surcharger le timeout du test.

Le point important ici est de bien comprendre qu’il faut absolument que le code présenté ici soit bullet proof dans le sens ou il doit toujours renvoyer un résultat sous peine de voir notre composant toujours en erreur.

Enfin, il reste maintenant à jeter un œil à l’implémentation d’un test concret :

Encore une fois, la seule règle ici est qu’il faut absolument que ce code se termine proprement, d’où le try / catch sur tout le code.

Le WebJob, qui collecte les résultats

Bon, maintenant qu’on a donné les moyens à nos APIs de s’auto-diagnostiquer, il nous faut quelque chose permettant de collecter régulièrement les résultats de health check afin de les restituer.
Pour ce faire, nous avons mis en place un WebJob schedulé (ou planifié, en bon français) qui appelle chaque route Health Check de chacune de nos APIs et en publie le résultat.

On note ici que ce code permet de publier les résultats au fil de l’eau grâce au chaînage de la tache de health check avec la publication de son résultat
tasks.Add(getApplicationStatusAsyncTask.ContinueWith(PublishResult))

Quant au code de GetApplicationStatusAsync il est se contente d’appeler le Health Check sur chacune des applications:

Sur le papier c’est assez simple mais il ne s’agit pas simplement d’appeler l’url de health check et récupérer le résultat mais il faut être capable de prendre en compte divers scénarii tels que:
— L’absence de réponse (timeout)
— Ressource introuvable (mauvaise url configurée pour l’application en question)
— Erreur technique type exception lors de l’exécution des tests

Ce travail de gestion des cas d’erreur est déléguée au runner lui-même lors du dernier appel de la méthode HealthCheckAsync dans l’API :

Cette méthode est celle qui lance chaque test défini dans la méthode HealthCheckAsync. En coulisse, l’appel à chacune des méthodes permettant d’ajouter un test (AddHealthCheckTest, AddDependencyTest, AddSqlServerCheck etc) ajoute une nouvelle tâche à exécuter par le runner.

Il faut donc que le runner lance chacune des tâches de manière indépendante et résiliente afin, une fois encore, d’être assuré d’obtenir un résultat fiable et dans le temps imparti.

Ce qui est intéressant ici c’est l’appel à la méthode LongRunningOperationWithCancellationTokenAsync que l’on va regarder en détail:

La première ligne permet tout d’abord de définir un Cancellation Token afin de pouvoir mettre individuellement le test en timeout mais continuer d’exécuter les autres tests.
Cela nous permet de créer la tâche de timeout qui sera mise en concurrence avec le test en lui même lors de l’appel à Task.WhenAny(healthCheckTestTask, taskCompletionSource.Task). Ainsi, la première des deux tâches se terminant sera le résultat du test.
Si le test se termine dans le temps imparti, le résultat du test sera retourné, sinon, un résultat Timeout sera renvoyé.

Et voilà, il ne reste plus qu’à publier les résultats!

L’API de publication et le front

Enfin, la dernière étape dans ce projet était de publier les résultats et surtout de les afficher de manière utile pour tous.

Nous avons donc décidé de faire une application Angular et de publier les résultats de notre Webjob vers notre API qui par la suite broadcast les résultats via un hub Signal R. Cela nous permet ainsi de voir nos petits bouger en temps réel!

Maintenant que nous savons monitorer nos APIs il faut continuer l’effort sur les autres briques Azure que nous utilisons!

Je pense en particulier aux Webjobs ainsi qu’aux Azure Functions ou la problématique est un peu différente. On pourrait penser à un classique système de heartbeat message mais nous souhaitons que cela ait le moins d’impact possible sur notre composant. Affaire à suivre donc…

--

--