Aller au contenu

Health checks EC2 vs ELB — pourquoi des instances InService servent encore des 500

L'ASG dit que chaque instance est InService. Le target group ALB dit que la moitié est Unhealthy. Les deux ont raison, et le fossé tient à un flag de configuration. Voici comment les deux modèles de health check interagissent et le piège du grace-period qui casse les déploiements.

L’équipe a roulé une nouvelle version de l’application par rafraîchissement d’instances. La console ASG montre toutes les instances de la flotte en InService. La console du target group ALB montre la moitié en Unhealthy, retournant 5xx sur l’endpoint de santé. Le pager se met à sonner. Vingt minutes de débogage plus tard : l’application a démarré, l’instance EC2 est joignable, mais l’application plante à la première requête à cause d’une variable d’environnement manquante. Le health check EC2 voit une instance qui tourne et rapporte saine. Le health check ELB voit l’application retourner 503 et rapporte non saine. L’ASG, configuré avec le type EC2 par défaut, voit sa propre vérification passer et laisse l’instance en service indéfiniment. L’ALB arrête de lui envoyer du trafic. La flotte est à la moitié de sa vraie capacité. Les deux health checks font exactement ce qu’on leur a dit de faire. Personne n’a dit à l’ASG d’écouter l’ALB.

Ce qui se passe

Il y a deux modèles de health check en jeu sur un ASG derrière ALB, et ils répondent à des questions différentes.

Les health checks EC2 répondent à « l’instance tourne-t-elle ? » — l’ASG interroge l’API instance-status d’AWS. La vérification passe si le statut système est ok (l’hôte EC2 est joignable) et le statut d’instance est ok (l’OS est joignable et pas dans un état dégradé). La vérification échoue quand AWS détecte une défaillance niveau hôte, une panique noyau, ou l’instance qui passe à arrêtée/terminée. Les health checks EC2 sont le défaut ASG. Ils sont rapides, peu coûteux, utiles — mais ils ne répondent pas à la question qui compte : « l’application fonctionne-t-elle ? »

Les health checks ELB répondent à « l’application répond-elle ? » — le load balancer fait une requête HTTP, HTTPS, TCP ou gRPC vers un endpoint configuré sur chaque cible et juge selon la réponse. Pour un ALB, c’est typiquement GET /health retournant 200. Pour un NLB, ce peut être une sonde de connexion TCP ou un check HTTP/HTTPS. La vérification passe quand la cible atteint le seuil de bonne santé (réponses consécutives réussies) et échoue quand elle franchit le seuil de mauvaise santé.

L’ASG peut être configuré pour écouter l’un ou l’autre, ou les deux. Le champ HealthCheckType sur l’ASG est EC2 (défaut) ou ELB. Le mettre à ELB ne désactive pas les health checks EC2 — il ajoute le verdict de l’ELB à l’équation. L’ASG considère une instance non saine si l’un de ses health checks configurés échoue. Donc HealthCheckType=ELB veut dire « considérer l’instance non saine si EC2 ou ELB le dit » — qui est ce que la plupart des équipes veulent vraiment.

L’interaction :

  1. L’ASG lance une instance. Elle est Pending dans la machine à états de l’ASG.
  2. L’instance démarre. Les health checks EC2 commencent à passer une fois que le statut système est ok.
  3. L’ALB commence à vérifier la nouvelle cible. La cible est initial le temps que le premier check complète.
  4. L’ASG marque l’instance InService quand les health checks EC2 passent.
  5. L’ALB marque la cible healthy quand les health checks ELB passent.
  6. L’ALB envoie du trafic à la cible seulement quand elle est healthy.

Deux problèmes de timing résultent de cette danse :

  • L’instance est InService (ASG) avant que l’application soit prête (ALB). C’est normal — le rôle de l’ASG est de suivre si l’instance existe ; le rôle de l’ALB est de suivre si elle répond. Les deux convergent une fois que l’application démarre.
  • L’instance reste InService (ASG) pendant que l’ALB l’a marquée Unhealthy. C’est l’état cassé. L’ASG voit une machine qui tourne, l’ALB voit une application cassée, et sans HealthCheckType=ELB l’ASG ne remplace jamais la machine.

Le piège du grace-period des health checks s’applique à l’étape 4 spécifiquement. Le grace-period (HealthCheckGracePeriod sur l’ASG) est le nombre de secondes que l’ASG attend après le lancement avant d’évaluer les health checks. Le défaut est 300 secondes, bien pour la plupart des applications. L’erreur est de le laisser à 300 quand l’application prend 8 minutes à démarrer — l’ASG arrive à 5 minutes, l’ELB rapporte toujours non saine parce que l’app démarre encore, l’ASG voit « ELB non saine après le grace-period » et termine l’instance. Prochaine instance lancée, même chose. L’ASG brûle des instances en boucle. La flotte ne grandit jamais.

L’erreur inverse est de régler le grace-period trop haut. Avec un grace-period de 30 minutes, une instance vraiment cassée — une qui plante immédiatement au démarrage — reste InService 30 minutes avant que l’ASG le remarque. La flotte tourne à capacité réduite toute cette fenêtre.

Comment vérifier

Inspecter la configuration de health check sur un ASG :

aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-app-prod-asg \
  --query 'AutoScalingGroups[0].[HealthCheckType,HealthCheckGracePeriod]' \
  --output table

EC2 avec un grace-period de 300 secondes est le défaut. Chercher deux drapeaux de mauvaise configuration :

  • HealthCheckType=EC2 sur un ASG derrière ALB. Presque toujours faux. L’équipe voulait ELB et a oublié.
  • HealthCheckGracePeriod=300 sur une application qui prend plus de 300 secondes à démarrer. Commun après migration d’une AMI mince à une plus grosse en oubliant de monter le grace-period.

Vérifier la vue du target group ALB sur la même flotte :

aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-app/abc \
  --query 'TargetHealthDescriptions[].[Target.Id,TargetHealth.State,TargetHealth.Reason]' \
  --output table

Les états que vous pourriez voir, et ce que chacun veut dire :

  • healthy — la cible passe les health checks et reçoit du trafic.
  • unhealthy — la cible échoue les health checks. Le champ raison clarifie : Target.FailedHealthChecks, Target.Timeout, Target.ResponseCodeMismatch, etc.
  • initial — la cible vient d’être enregistrée et l’ALB n’a pas terminé le premier tour de health checks.
  • draining — la cible a été désenregistrée et termine les requêtes en vol.
  • unused — la cible n’est dans aucune zone que l’ALB couvre. Rare ; habituellement un mauvais sous-réseau.

La combinaison qui déclenche l’intro — ASG dit InService, ALB dit unhealthy — est le pistolet fumant pour HealthCheckType=EC2 sur un ASG derrière ALB.

Pour chronométrer le temps réel que l’application met à être prête :

# Lancer une instance fraîche dans l'ASG et regarder
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name my-app-prod-asg \
  --desired-capacity $(($(aws autoscaling describe-auto-scaling-groups \
    --auto-scaling-group-names my-app-prod-asg \
    --query 'AutoScalingGroups[0].DesiredCapacity' \
    --output text) + 1))

# Puis sonder la santé des cibles jusqu'à ce que la nouvelle instance passe healthy

Le delta entre « l’instance apparaît dans le target group comme initial » et « l’instance apparaît comme healthy » est le vrai temps de disponibilité de l’application. Le HealthCheckGracePeriod de l’ASG devrait être ce nombre plus une marge (typiquement 30 à 60 secondes), pas le défaut de 300.

La correction

La configuration qui marche vraiment sur un ASG derrière ALB, par importance décroissante :

  1. Régler HealthCheckType=ELB. C’est le drapeau le plus important. L’ASG écoute les deux checks, remplace les instances que l’ALB dit cassées, et reste utile à travers les cas limites liés au grace-period :

    aws autoscaling update-auto-scaling-group \
      --auto-scaling-group-name my-app-prod-asg \
      --health-check-type ELB \
      --health-check-grace-period 360
  2. Régler HealthCheckGracePeriod au vrai temps de démarrage de l’application plus 30 à 60 secondes de marge. Le mesurer. Ne pas deviner. Une application qui prend 4 minutes à démarrer a besoin de 270 à 300 secondes de grace ; une application qui prend 90 secondes a besoin de 120 à 150.

  3. Configurer le health check du target group pour correspondre au vrai signal de disponibilité de l’application. Le check par défaut (GET / retournant 200) est rarement le bon. Un endpoint de santé correct :

    • Retourne 200 seulement quand l’application est vraiment capable de servir du trafic réel — incluant la connectivité à la base, la joignabilité des services en aval, et toute initialisation en processus.
    • Retourne 503 (pas 500, pas 404) quand l’application démarre, draine, ou est autrement non prête. 503 a la sémantique explicite « service indisponible, réessayer » que les load balancers comprennent.
    • Est peu coûteux. Les health checks se déclenchent toutes les 30 secondes environ, par cible, par instance de LB. Un endpoint de santé qui fait une vraie requête de base à chaque check est un DDoS contre la base.

    Régler la config de health check du target group :

    aws elbv2 modify-target-group \
      --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-app/abc \
      --health-check-path /health \
      --health-check-interval-seconds 30 \
      --healthy-threshold-count 2 \
      --unhealthy-threshold-count 3 \
      --health-check-timeout-seconds 5

    Les défauts conviennent dans l’ensemble — interval=30, healthy_threshold=5, unhealthy_threshold=2, timeout=5. Le réglage ci-dessus baisse healthy_threshold à 2 (récupération plus rapide) et monte unhealthy_threshold à 3 (tolère un seul flap pendant la charge de pointe).

  4. Aligner le grace-period au motif de déploiement. Pendant un rafraîchissement d’instances, l’ASG lance de nouvelles instances et attend le grace-period avant d’évaluer leur santé. Si votre grace-period est 600 secondes et vous avez 20 instances à rafraîchir une à la fois, le refresh prend au moins 200 minutes juste sur les grace-periods. Démarrages plus rapides = grace-periods plus courts = déploiements plus rapides.

  5. Utiliser des surcharges de HealthCheckGracePeriod pour les cas spéciaux. Les lifecycle hooks pendant la phase de lancement peuvent étendre le temps de disponibilité effectif sans monter le grace-period pour tout le monde. Le champ default_instance_warmup sur l’ASG est la manière moderne de contrôler ça pour les politiques de scaling spécifiquement — indépendamment du grace de health check.

Le piège de déploiement : quand vous poussez une nouvelle AMI et démarrez un rafraîchissement, les nouvelles instances démarrent depuis une nouvelle AMI mais l’ASG garde le même HealthCheckGracePeriod. Si la nouvelle AMI prend plus long à démarrer que l’ancienne — commun pendant une transition d’artefacts cuits en AMI vers tirés via user-data — le grace-period qui suffisait pour l’ancienne est trop court pour la nouvelle. Le refresh « réussit » pour les premières instances (elles démarrent dans le grace-period, ont la chance d’être saines, tout va bien), puis échoue pour les instances qui ont une contention I/O et démarrent lentement. La correction est de monter le grace-period dans le cadre du déploiement de la nouvelle AMI, pas comme un changement séparé.

Trois règles à chaque ASG derrière ALB :

HealthCheckType=ELB est le défaut que vous voulez. Tout autre veut dire que l’ASG ne peut pas remplacer les instances cassées qui ont l’air vivantes du point de vue EC2. L’économie de tourner en EC2 seul n’est pas réelle — l’ALB fait ses propres checks ; la question est si l’ASG entend la réponse.

Les endpoints de santé sont du code applicatif, pas de la config infrastructure. Un endpoint de santé qui retourne 200 quand la base est tombée est pire qu’aucun endpoint. Faire en sorte que l’endpoint vérifie vraiment les choses qui peuvent échouer.

Mesurer le temps de démarrage. Régler le grace-period depuis la mesure. « 300 secondes parce que c’est le défaut » est un rapport d’incident futur qui s’écrit.

Le modèle est simple une fois vu : health checks EC2 pour « la machine est vivante », health checks ELB pour « l’application marche ». L’ASG écoute ce que vous configurez. La plupart des équipes veulent les deux, et la plupart le découvrent à la dure pendant un déploiement raté. Voir l’article sur les lifecycle hooks lié pour le motif de hook de lancement qui protège contre le problème inverse — des instances qui passent InService avant d’être vraiment prêtes.


Stack Harbor opère les paliers applicatifs sur ASG et les politiques d’auto-scaling à coût plafonné dans le cadre de Gestion AWS et de la couche Auto-scaling. Si vos événements de scaling sont encore des surprises, commencez par une consultation d’infrastructure.