Home .NET Les health checks avec ASP.NET Core

Les health checks avec ASP.NET Core

  Gilles, Architecte technique .NET 20 min 20 mars 2020

Les « Health checks », ou vérifications de santé, permettent de connaître l’état de santé d’une application et de ses dépendances. Le suivi de la santé d’une application est primordial pour une application basée sur les microservices ou encore lors de l’utilisation d’orchestrateurs.

Cette fonctionnalité est intégrée nativement dans ASP.NET Core depuis la version 2.2, mettant à disposition du développeur à la fois des services ainsi qu’un middleware.

Elle rend notamment accessible une méthode HTTP GET affichant le statut global de la santé de l’application sous forme de texte, ou permet encore de retourner au format JSON un état plus détaillé incorporant par exemple l’état de chaque dépendance.

Il y a trois types de statut :

  • Healthy : L’état de l’application est sain, tous les services sont fonctionnels.
  • Degraded : L’application fonctionne partiellement. Certaines fonctionnalités seront par exemple limitées, indisponibles ou en erreur.
  • Unhealthy : L’application ne fonctionne pas du tout ou n’est pas dans un état permettant une utilisation correcte.

Création du projet démo

Le projet démo sera très minimaliste car les fonctionnalités de base sont déjà nativement supportées par ASP.NET Core. Nous allons utiliser le SDK 3.1.101 avec votre système d’exploitation et IDE préférés pour mettre à disposition l’état de santé de notre application. Avec Visual Studio vous pouvez créer le projet via un template Web, avec VSCode il vous faudra exécuter la ligne de commande suivante :

dotnet new mvc

Maintenant que notre projet Web est créé, nous allons à présent modifier le fichier Startup.cs pour configurer nos services et middleware.

ConfigureServices() permet de définir notre service de santé dans l’application :

public void ConfigureServices(IServiceCollection services)
{
        services.AddControllersWithViews();
        services.AddHealthChecksUI();
}

Configure() permet d’ajouter le middleware à la pipeline :

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    //...
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");

        endpoints.MapHealthChecks("/health");
    });
}

Lancez maintenant l’application puis naviguez à l’URL https://localhost:5001/health

L’application retourne le texte « Healthy », ce qui est logique puisque nous n’avons pour le moment ajouté aucune règle et que l’application fonctionne correctement.

 

Créons maintenant le check EvenSecondHealthCheck.cs qui retournera simplement « Unhealthy » si la seconde de l’heure actuelle est impaire (ne pas oublier le using Microsoft.Extensions.Diagnostics.HealthChecks) :

public class EvenSecondHealthCheck : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default(CancellationToken))
    {
        var isOdd = DateTime.Now.Second % 2 == 0;
        var result = isOdd
            ? HealthCheckResult.Healthy("Current second was even")
            : HealthCheckResult.Unhealthy("Current second was odd");
        return Task.FromResult(result);
    }
}

Ajoutons-le ensuite à notre service du Startup.cs :

services.AddHealthChecks()
    .AddCheck<EvenSecondHealthCheck>("even_second");

En allant à l’URL /health et en rafraichissant plusieurs fois la page on remarque que le retour varie entre « Healthy » et « Unhealthy », notre nouvelle règles est donc bien prise en compte.

 

En regardant dans les logs de l’application on peut remarquer que l’état « Unhealthy » engendre un log FAIL, ce qui est plutôt pratique et peut aider à diagnostiquer des bugs liés à ce soucis :

Health check even_second completed after 0.0462ms with status Unhealthy and ‘Current second was odd’

Nous pouvons de cette façon ajouter autant de checks que nous le souhaitons. ASP.NET Core offre donc un mécanisme simple pour effectuer des vérifications sur l’application, mais l’écriture de chaque check peut vite devenir fastidieuse. Heureusement, comme nous allons le voir, des extensions existent.

Monitorer une URL

Xabaril a créé toute une série d’extensions qui permettent très aisément d’effectuer des checks aussi bien au niveau du système d’exploitation qu’au niveau SQL en passant par le réseau.

Microsoft recommande l’utilisation de Xabaril mais rappelle qu’il s’agit d’un package tiers et donc ni maintenu ni supporté par Microsoft.

Nous allons par exemple ajouter un check pour vérifier qu’une URL réponde, imaginons notre API REST. Il va tout d’abord falloir ajouter le package suivant via le gestionnaire NuGet de Visual Studio ou en ligne de commande :

dotnet add package AspNetCore.HealthChecks.Uris

Il suffit ensuite d’appeler la méthode AddUrlGroup() en renseignant l’URL :

services.AddHealthChecks()
    //.AddCheck<EvenSecondHealthCheck>("even_second")
    .AddUrlGroup(new Uri("https://webnet.fr/"), "Webnet", HealthStatus.Degraded);

En accédant à l’URL /health nous avons bien le statut « Healthy ».

Si l’URL était indisponible alors le statut aurait été « Degraded ». Nous pouvons le constater en modifiant l’URL Webnet par une erronée.

Jadis Xabaril fournissait le package BeatPulse pour combler le manque de support des health checks par Microsoft. Depuis que Microsoft supporte nativement les health checks avec ASP.NET Core 2.2+, Xabaril a porté et déprécié son code BeatPulse au profit de AspNetCore.Diagnostics.HealthChecks.

Monitorer une base de données

De la même manière nous pouvons facilement vérifier l’état d’une base de données, par exemple SQL Server. Installons le package NuGet associé :

dotnet add package AspNetCore.HealthChecks.SqlServer

Puis ajouter la méthode AddSqlServer() accompagnée de la chaîne de connexion :

services.AddHealthChecks()
    //.AddCheck<EvenSecondHealthCheck>("even_second")
    //.AddUrlGroup(new Uri("https://webnet.fr/"), "Webnet", HealthStatus.Degraded)
    .AddSqlServer("MyConnectionStrings");

Inutile de mentionner l’ensemble de la pléthore de checks fournis par Xabaril ni leur simplicité d’utilisation. Cependant cela nous permet seulement de connaître l’état global de l’application, il serait préférable de connaître le détail de l’ensemble de nos checks de façon individuelle.

Détailler l’état de santé

Pour détailler précisément l’état de chaque check, nous allons définir un nouveau middleware. Dans Startup.cs, ajoutons juste avant app.UseEndpoint() :

app.UseHealthChecks("/health", new HealthCheckOptions
{
    Predicate = _ => true,
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

Naviguons vers l’URL /health pour voir le résultat. Nous obtenons maintenant un rapport détaillé incluant chaque check. Le résultat est au format JSON donc pas forcément lisible, nous allons maintenant le rendre davantage user-friendly.

Ajouter une interface graphique

Xabaril met également à disposition un package permettant d’afficher le statut de l’application et l’ensemble de nos checks accompagnés de leurs historiques d’état. Ajoutons le package NuGet suivant :

dotnet add package AspNetCore.HealthChecks.UI

Il faut ensuite ajouter le service associé :

services.AddHealthChecksUI();

Puis un nouvel endpoint :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}"
    );
                
    endpoints.MapHealthChecks("/health");
    endpoints.MapHealthChecksUI();
});

Afin de mapper les URLs de statut de check au format JSON avec notre future interface graphique et ses règles associées, ajoutons la configuration suivante dans appsettings.json :

"HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "All",
        "Uri": "https://localhost:5001/health"
      }
    ],
    "EvaluationTimeOnSeconds": "10",
    "MinimumSecondsBetweenFailureNotifications": "60"
  },

Détails des paramètres :

  • HealthChecks : Différents groupes de checks à aggréger
  • EvaluationTimeOnSeconds : Nombre de secondes entre deux vérifications.
  • MinimumSecondsBetweenFailureNotifications : Nombre minimum de secondes entre les notifications d’échec pour éviter de se faire inonder d’erreurs identiques

Naviguons vers l’URL /healthchecks-ui pour obtenir un dashboard de l’ensemble de nos checks :

Il y a également la possibilité de voir l’historique de la santé d’un check en cliquant sur l’icône de détails :

L’interface graphique peut si besoin être brandée à votre entreprise via l’ajout d’un fichier CSS.

Ajouter des tags

Les tags sont très pratiques si vous souhaitez créer des groupes de checks, en réintégrant un même check dans plusieurs groupes.

Créons par exemple un groupe qui recense toutes nos URLs. Dans Startup.cs il va falloir ajouter un nouveau middleware qui référencera tous les checks ayant au moins un tag nommé « url » :

app.UseHealthChecks("/health-url", new HealthCheckOptions
{
    Predicate = (check) => check.Tags.Contains("url"),
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});

Pour l’interface graphique nous devons ajouter l’URL dans notre appsettings.json :

  "HealthChecksUI": {
    "HealthChecks": [
      {
        "Name": "All",
        "Uri": "https://localhost:5001/health"
      },
      {
        "Name": "URL",
        "Uri": "https://localhost:5001/health-url"
      }
    ],
    "EvaluationTimeOnSeconds": "10",
    "MinimumSecondsBetweenFailureNotifications": "60"
  },

Ajoutons ensuite le tag à notre groupe :

services.AddHealthChecks()
    .AddCheck<EvenSecondHealthCheck>("even_second")
    .AddUrlGroup(new Uri("https://webnet.fr/"), "Webnet", HealthStatus.Degraded, new[] { "url" });

Voici le résultat obtenu :

Il y a un groupe « All » qui contient l’ensemble des checks de l’application, et un groupe « URL » qui ne contiendra que les checks d’URL.

Un check acceptant un paramètre tags de type string[], vous pouvez donc spécifier autant de tags que nécessaire.

Ajouter des checks avancés

La plupart des checks proposés par Microsoft et Xabaril peuvent suffire à la plupart des applications, mais il est possible que vous ayez besoin d’en créer de nouveaux.

Voici les types que j’ai dû créer pour le besoin d’une application :

Sécuriser les données

Les health checks ne sont pas censés afficher de données sensibles, mais leur listing peut s’avérer être une aubaine pour un pirate puisqu’il a à sa disposition le listing de l’ensemble des dépendances et services de notre application, ainsi qu’éventuellement des informations système.

Si pour votre application l’accès à ces données doit être public alors attention à ne pas divulguer d’informations comme par exemple une connexion à la base de données générant une exception et affichant un message de type « Impossible de se connecter à la base de données XXX ».

Si l’accès doit être restreint il y a différents scénarios. Le plus simple est par exemple d’ajouter dans Startup.cs une règle stipulant que l’utilisateur doit être connecté ou qu’il fasse partie d’un rôle de type « Admin » ou mieux « HealthCheck », l’inconvénient étant qu’il faut s’authentifier pour y accéder et cela complexifie son utilisation via des services tiers comme des orchestrateurs. Une autre solution aurait pu être de définir des restrictions sur l’accès à cette URL, mais malheureusement ce n’est pas possible avec IIS, la règle devant s’appliquer à toute l’application et donc inutile pour notre scénario. Enfin, l’idéal serait de définir que seules des plages d’adresses IP whitelistées peuvent y accéder, ce qui a été réalisé pour notre démo. Vous pouvez retrouver l’implémentation dans HealthCheckRestrictMiddleware.cs :

            //Check if allowed (IP-restricted)
            app.UseHealthCheckRestrictMiddleware();

Dans le précédent exemple nous autorisons seulement localhost (::1) ainsi que deux plages réseau définies dans appsettings.json à accéder aux checks de notre application.

Conclusion

Nous avons vu à travers cet article que la mise en place des vérifications de santé d’une application ASP.NET Core 2.2+ est très simple, et créer de nouveaux checks l’est tout autant.

Au sein de Webnet j’ai eu récemment l’occasion de le mettre en place sur un projet ASP.NET Core 2.2 où tous nos serveurs étaient hébergés en interne et reliés à notre client final via un tunnel IPsec. Notre client était en pleine migration de toute son infrastructure réseau et des bugs étaient fréquemment remontés car l’application ne fonctionnait plus, principalement à cause de problèmes de flux. Il y avait donc une perte de temps de saisie de divers tickets d’assistance, d’accès aux logs de la machine pour essayer d’identifier le problème et différents tests de ping ou encore telnet.

Avec l’intégration des health checks il suffit dorénavant de se rendre sur la page de statut sécurisée pour avoir une vue globale de la santé de l’application et de ses dépendances, ainsi que l’historique des problèmes survenus.

Si vous utilisez des services comme Microsoft Application Insights vous pouvez en une ligne de code y intégrer vos nouveaux checks dedans, puis créer des dashboards avancés ou alertes customisées avec Azure Monitor.

 

Vous pouvez retrouver tout le code source de la démo sur GitHub, chaque section de cet article apparaissant dans un commit séparé.

Lire les articles similaires

Laisser un commentaire

Social Share Buttons and Icons powered by Ultimatelysocial