Un ping sur la page d'accueil vous dit que le serveur répond. Il ne vous dit pas si la base de données répond, si la file se remplit ou si le prestataire de paiement renvoie des erreurs depuis dix minutes. C'est le rôle des endpoints de santé : une URL où le service vérifie lui-même ses dépendances et rend compte de chacune. Le problème, c'est qu'il n'existe pas de format unique. Une équipe Java utilise Spring Boot Actuator, une équipe .NET les Health Checks d'ASP.NET Core, une autre suit le brouillon health+json de l'IETF, et la plateforme tourne sur Kubernetes, qui a son propre style. Dans cet article, nous montrons à quoi ressemble chacun, où la configuration coince le plus souvent et comment AAA Sentinel les lit tous sans vous demander de rien changer.
Quatre dialectes pour la même question
Tous répondent à « tout va bien ? », mais avec des mots différents :
- health+json : un brouillon de l'IETF (draft-inadarei-api-health-check) avec le type de contenu application/health+json et trois états : pass, warn et fail.
- Spring Boot Actuator : l'endpoint /actuator/health, avec les états UP, DOWN, OUT_OF_SERVICE et UNKNOWN.
- ASP.NET Core Health Checks : ce que publie MapHealthChecks, avec les états Healthy, Degraded et Unhealthy.
- Kubernetes : /livez et /readyz de l'API server, qui répondent ok ou, avec ?verbose, une ligne par vérification.
health+json : le brouillon de l'IETF
C'est le format le plus neutre : il ne dépend d'aucun framework. Le document porte un status global et, en option, un objet checks. Chaque clé de checks nomme un composant et ce qui est mesuré, sous la forme composant:mesure, et sa valeur est une liste, car le même composant peut être mesuré sur plusieurs nœuds. Chaque entrée a son propre status et, si vous le souhaitez, observedValue, observedUnit, componentType, componentId, time et output.
HTTP/1.1 200 OK
Content-Type: application/health+json
Cache-Control: no-store
{
"status": "warn",
"version": "1.4.2",
"checks": {
"postgres:responseTime": [
{
"componentType": "datastore",
"observedValue": 12,
"observedUnit": "ms",
"status": "pass",
"time": "2026-10-18T09:00:00Z"
}
],
"orders-queue:depth": [
{
"componentType": "component",
"observedValue": 1480,
"status": "warn",
"time": "2026-10-18T09:00:00Z"
}
]
}
}- pass : sain. warn : fonctionne, avec une réserve. fail : ne fonctionne pas.
- Le brouillon accepte des synonymes : ok et up valent pass ; error et down valent fail.
- Le code HTTP accompagne l'état : 2xx pour pass et warn, 4xx ou 5xx pour fail (503 en général).
- Champs facultatifs au niveau du service : version, releaseId, serviceId, description, notes et output.
Spring Boot Actuator : /actuator/health
Avec spring-boot-starter-actuator dans le projet, /actuator/health est exposé en HTTP dès le premier démarrage. Ce qui manque, c'est le détail : par défaut, show-details vaut never et la réponse s'en tient à l'état agrégé.
GET /actuator/health
HTTP/1.1 200 OK
{ "status": "UP" }GET /actuator/health
HTTP/1.1 503 Service Unavailable
{
"status": "DOWN",
"components": {
"db": {
"status": "DOWN",
"details": { "error": "org.springframework.jdbc.CannotGetJdbcConnectionException" }
},
"diskSpace": {
"status": "UP",
"details": { "total": 53687091200, "free": 31138512896, "threshold": 10485760, "exists": true }
},
"ping": { "status": "UP" }
}
}Depuis Spring Boot 2.2, les indicateurs sont sous components ; avant, ils étaient sous details. Par défaut, DOWN et OUT_OF_SERVICE répondent 503, UP et UNKNOWN répondent 200. Cette correspondance se modifie avec management.endpoint.health.status.http-mapping.
Afficher les composants dans Spring
Il y a deux propriétés distinctes, mieux vaut ne pas les confondre. show-components décide si la liste des composants et de leurs états apparaît ; show-details décide si le détail de chacun apparaît aussi (version de la base, espace disque, messages d'erreur). Si vous ne fixez pas show-components, elle prend la valeur de show-details. Un moniteur externe n'a besoin que des états, d'où cette configuration raisonnable :
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-components: always
show-details: when-authorized
probes:
enabled: true- show-components: always montre l'état de db, diskSpace, redis et des autres à tout le monde, sans données internes.
- show-details: when-authorized réserve le détail aux utilisateurs authentifiés (et, si vous fixez management.endpoint.health.roles, à ces rôles).
- probes.enabled publie aussi /actuator/health/liveness et /actuator/health/readiness ; sur Kubernetes, ils s'activent d'eux-mêmes.
- Si vous préférez ne rien exposer sur le port public, management.server.port déplace Actuator sur un autre port.
ASP.NET Core Health Checks
En .NET, vous enregistrez les vérifications avec AddHealthChecks et publiez l'endpoint avec MapHealthChecks. Chaque vérification renvoie un HealthCheckResult, et le rapport agrégé prend le pire état.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("database")
.AddCheck<QueueHealthCheck>("queue");
var app = builder.Build();
app.MapHealthChecks("/health");
app.Run();GET /health
HTTP/1.1 200 OK
Content-Type: text/plain
DegradedL'écrivain par défaut renvoie un seul mot en texte brut : Healthy, Degraded ou Unhealthy. Aucun composant. Et par défaut Degraded répond 200, comme Healthy ; seul Unhealthy répond 503. Un moniteur qui ne regarde que le code HTTP ne verra jamais un service dégradé. Les codes se modifient avec HealthCheckOptions.ResultStatusCodes.
Un écrivain JSON pour ASP.NET
Pour obtenir l'état par composant, il vous faut un ResponseWriter. L'option sans dépendance consiste à écrire le vôtre, dans l'esprit de l'exemple de la documentation Microsoft, avec une propriété results :
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = (context, report) => context.Response.WriteAsJsonAsync(new
{
status = report.Status.ToString(),
results = report.Entries.ToDictionary(
entry => entry.Key,
entry => new
{
status = entry.Value.Status.ToString(),
description = entry.Value.Description,
}),
}),
});{
"status": "Degraded",
"results": {
"database": { "status": "Healthy", "description": null },
"queue": { "status": "Degraded", "description": "1480 messages waiting" }
}
}- Sérialisez l'état avec ToString(). Si vous sérialisez l'enum HealthStatus tel quel, sans JsonStringEnumConverter, il sort sous forme de nombre : 0 pour Unhealthy, 1 pour Degraded et 2 pour Healthy.
- Ne mettez ni exceptions ni chaînes de connexion dans la réponse : les endpoints de santé sont souvent publics.
L'alternative communautaire : HealthChecks.UI.Client
Beaucoup de projets utilisent AspNetCore.HealthChecks.UI.Client, issu du projet communautaire AspNetCore.Diagnostics.HealthChecks (ce n'est pas un package Microsoft). Son écrivain renvoie les vérifications sous une propriété entries, avec durées et étiquettes :
// dotnet add package AspNetCore.HealthChecks.UI.Client
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});{
"status": "Degraded",
"totalDuration": "00:00:00.0153210",
"entries": {
"database": { "data": {}, "duration": "00:00:00.0081120", "status": "Healthy", "tags": [] },
"queue": { "data": {}, "description": "1480 messages waiting", "duration": "00:00:00.0040310", "status": "Degraded", "tags": [] }
}
}Kubernetes : /livez et /readyz
L'API server de Kubernetes expose /livez et /readyz (ainsi que l'ancien /healthz). Sans paramètre, ils répondent ok avec un 200. Avec ?verbose, ils renvoient une ligne par vérification : [+]nom ok si elle passe et [-]nom failed sinon, avec un 500 dès que l'une échoue. Certaines applications reprennent ce format pour leurs propres endpoints.
GET /readyz?verbose
HTTP/1.1 500 Internal Server Error
[+]ping ok
[+]log ok
[-]etcd failed: reason withheld
[+]informer-sync ok
readyz check failedLa correspondance des états
Les quatre formats ont un état sain et un état en panne. La nuance est au milieu : warn, Degraded et UNKNOWN ne sont pas une panne. Voici comment AAA Sentinel les lit :
Contrat de santé · de chaque état à sain, dégradé ou en panne
- health+json
- pass, ok, up → sain · warn → dégradé · fail, error, down → en panne
- Spring Boot Actuator
- UP → sain · UNKNOWN → dégradé · DOWN, OUT_OF_SERVICE → en panne
- ASP.NET Core
- Healthy (ou 2) → sain · Degraded (ou 1) → dégradé · Unhealthy (ou 0) → en panne
- Kubernetes
- ok ou [+]nom → sain · [-]nom → en panne
- Code HTTP hors 2xx
- En panne, quoi que dise le corps
- Corps qui ne suit aucun contrat
- Le code HTTP décide, comme un ping ; si le corps est arrivé tronqué, dégradé
Les pièges habituels
La plupart des faux « tout va bien » viennent de là :
- Actuator sans show-components ni show-details : il n'y a que {"status":"UP"}, et un composant en panne n'apparaît que comme un DOWN global, sans savoir lequel.
- ASP.NET avec l'écrivain par défaut : un mot en texte brut et un 200 pour Degraded.
- Le code HTTP et le corps se contredisent : un 200 avec status fail (écrivains maison qui répondent toujours 200) ou un 503 avec status pass (un proxy en amont qui renvoie sa propre erreur).
- Dégradé n'est pas en panne : si un warn réveille quelqu'un à trois heures du matin, l'équipe finira par faire taire le moniteur.
- La casse compte : up relève de health+json et UP d'Actuator. Un Up à mi-chemin n'est ni l'un ni l'autre.
- Un endpoint de santé qui met 30 secondes parce qu'il attend une dépendance bloquée. Donnez un délai maximal à chaque vérification.
Comment le contrat de santé d'AAA Sentinel les lit
Le moniteur de type contrat de santé envoie un GET à votre URL HTTPS et lit la réponse telle qu'elle arrive. Vous ne choisissez pas le format à la création : il le reconnaît d'après le corps. Voici les règles :
- Si le corps est du JSON, il essaie dans cet ordre : health+json (status en minuscules, synonymes compris), Actuator (status en majuscules, composants sous components ou details) et ASP.NET (Healthy, Degraded ou Unhealthy, composants sous results ou entries).
- Un status numérique n'est lu comme l'enum .NET que s'il arrive avec ses entrées sous results ou entries ; un nombre isolé pourrait venir de n'importe où.
- Si ce n'est pas du JSON, il lit le texte brut de l'écrivain par défaut d'ASP.NET et, à défaut, la sortie Kubernetes : ok ou une ligne [+]/[-] par vérification.
- Il retient l'état de chaque composant. En health+json, quand une clé contient plusieurs entrées (plusieurs nœuds), c'est la pire qui compte.
- L'état du service est le pire entre celui qu'il déclare et celui de ses composants : un pass avec un composant en fail est en panne.
- Si le code HTTP n'est pas 2xx, c'est en panne même si le corps dit pass. S'il est 2xx et que le corps dit fail, c'est aussi en panne : exactement le cas qu'un ping ne voit pas.
- Si le corps ne correspond à aucun contrat, le code HTTP décide. S'il est arrivé tronqué à cause de sa taille, il n'est pas considéré comme sain : il reste dégradé.
- Vous pouvez fixer une latence maximale (jusqu'à 10 secondes) : une réponse plus lente compte comme une panne.
Dégradé et en panne ne sont pas traités pareil
Un service dégradé continue de servir ; il n'est donc pas traité comme une panne :
- Dégradé (un warn, un Degraded ou un UNKNOWN) : c'est enregistré et la page de statut l'affiche comme dégradé, mais aucun incident n'est ouvert et rien ne se déclenche comme pour une panne.
- En panne : la machine à états habituelle s'applique. Le moniteur passe d'abord en suspect, la vérification est répétée depuis une deuxième région AWS (eu-central-1) et, seulement si elle se confirme, un incident s'ouvre et une alerte part par e-mail, Slack, Microsoft Teams ou webhook.
- Quand le service redevient sain, l'incident se ferme tout seul et vous recevez le rétablissement.
- Pendant les 24 premières heures, le moniteur observe et propose des seuils sans alerter.
Si votre endpoint demande des identifiants, le moniteur accepte bearer, basic, clé d'API, AWS SigV4 et les jetons Azure AD ou Google Cloud avec l'identité du compte déjà connecté. Chaque sonde porte l'en-tête X-AAA-Signature, signé avec un secret propre à votre organisation, pour que vous puissiez la distinguer du reste du trafic.
health+json en Node avec @aaa-sentinel/health
Si votre service tourne sous Node et n'a pas encore d'endpoint de santé, @aaa-sentinel/health (sur npm, sans dépendance, Node 20 ou plus) vous le fournit en health+json. Vous lui passez un objet avec une fonction par composant et il renvoie la réponse HTTP complète. Dans une Lambda derrière API Gateway ou une function URL, vous la renvoyez telle quelle :
import { runHealthChecks } from '@aaa-sentinel/health'
export const handler = () =>
runHealthChecks(
{
database: async () => {
await db.query('select 1')
},
queue: async () => ((await queueDepth()) < 1000 ? 'pass' : 'warn'),
payments: async (signal) => {
const started = Date.now()
await fetch('https://payments.internal/ping', { signal })
return { observedValue: Date.now() - started, observedUnit: 'ms' }
},
},
{ version: process.env.APP_VERSION, timeoutMs: 2000 },
)import express from 'express'
import { runHealthChecks } from '@aaa-sentinel/health'
const app = express()
app.get('/health', async (_req, res) => {
const r = await runHealthChecks(checks, { version: process.env.APP_VERSION })
res.writeHead(r.statusCode, r.headers).end(r.body)
})HTTP/1.1 200 OK
Content-Type: application/health+json
Cache-Control: no-store
{
"status": "warn",
"version": "1.4.2",
"checks": {
"database": [{ "status": "pass", "time": "2026-10-18T09:00:00.120Z" }],
"queue": [{ "status": "warn", "time": "2026-10-18T09:00:00.118Z" }],
"payments": [{ "observedValue": 41, "observedUnit": "ms", "status": "pass", "time": "2026-10-18T09:00:00.161Z" }]
}
}- Une vérification qui ne renvoie rien ou renvoie true est un pass ; false est un fail ; elle peut aussi renvoyer pass, warn ou fail, ou un objet avec observedValue et observedUnit.
- Les vérifications s'exécutent en même temps, chacune avec son délai maximal (timeoutMs, 2 secondes par défaut et 60 au plus). Celle qui lève une erreur ou dépasse le délai est un fail pour ce composant, et l'endpoint répond quand même. Chacune reçoit un AbortSignal pour interrompre son appel.
- L'état global est la pire vérification. fail répond 503 ; pass et warn, 200. Les répartiteurs de charge le comprennent aussi.
- Les messages d'erreur restent hors de la réponse, sauf si vous passez exposeErrors: true, car ils contiennent souvent des hôtes ou des chaînes de connexion.
- minimumStatus: 'warn' maintient le service au moins en warn, pendant une maintenance par exemple.
- Si vous avez déjà les résultats, healthResponse construit la même réponse sans rien exécuter, et healthBody ne vous donne que le document.
Checklist
Avant de créer le moniteur, vérifiez que votre endpoint :
- Répond à un GET en HTTPS, sans rediriger vers une page de connexion.
- Renvoie l'état de chaque composant : show-components dans Spring, un ResponseWriter JSON dans ASP.NET, checks en health+json.
- N'expose pas de détails internes à qui ne doit pas les voir.
- A un délai maximal par vérification et répond en quelques secondes.
- Distingue dégradé et en panne : warn, Degraded ou UNKNOWN pour ce qui fonctionne à moitié.
- Renvoie un code HTTP cohérent avec le corps : 503 quand l'état est en panne.
- Sous Node, @aaa-sentinel/health vous donne le format, les délais maximaux et le code HTTP en quelques lignes.