Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Maîtriser santé, délais et flux longs avec Traefik

Par mis à jour le

Améliorer cette page

Logo Traefik Proxy

Pour qu'un Traefik 3.7.15 reste fiable face à un backend lent, mort ou bavard, bornez la connexion et la réponse du backend dans un transport de serveur, allongez le délai d'écriture du point d'entrée pour les flux, ajoutez une sonde de santé et ne laissez pas un retry rejouer une écriture. Une sonde de santé est une vérification périodique de chaque backend ; un retry est une nouvelle tentative automatique, qui peut compter deux fois une action quand elle est mal cadrée. Cette leçon s'adresse à qui publie déjà un service derrière Traefik et découvre qu'un réglage qui paraît raisonnable peut produire des pannes silencieuses : un flux d'événements coupé à heure fixe, un doublon que le client ne voit pas. Elle applique à Traefik les principes de la leçon sur les flux longs et repose sur des mesures rejouées sur la version 3.7.15.

La leçon va du placement des délais jusqu'à la preuve, réglage par réglage.

  • Placer chaque délai sur le tronçon qu'il surveille : client vers Traefik, ou Traefik vers backend.
  • Borner un backend muet sans casser une réponse lente mais légitime.
  • Expliquer pourquoi un flux SSE actif est coupé par le délai d'écriture.
  • Régler une sonde de santé et prévoir le temps qu'elle met à agir.
  • Empêcher qu'une réponse perdue fasse compter deux fois une action.
  • Prouver chaque réglage par une mesure faite depuis un client.

La leçon suppose que vous savez écrire une route Traefik et lire un code HTTP. Le lab fournit la machine, les quatre backends et les clients de mesure.

Traefik surveille deux tronçons avec deux familles de réglages : le tronçon client vers Traefik se règle sur le point d'entrée, le tronçon Traefik vers backend se règle dans un transport de serveur. Un backend est le serveur d'application derrière le proxy. Un délai (timeout) est une durée au-delà de laquelle Traefik abandonne et répond lui-même par une erreur, au lieu d'attendre sans fin.

Le transport de serveur (serversTransport) est un objet de configuration dynamique qu'un service cite par son nom. Il porte les forwardingTimeouts : dialTimeout pour établir la connexion réseau (TCP) vers le backend, et responseHeaderTimeout pour attendre le début de la réponse. Le point d'entrée (entrypoint), lui, est le port d'écoute de Traefik. Sa configuration est statique, lue au démarrage, et porte transport.respondingTimeouts avec notamment writeTimeout, dont la valeur d'origine est 0s (aucun délai) selon la documentation de Traefik 3.7. Les deux autres délais de ce bloc, readTimeout (lecture de la requête entière, 60s d'origine) et idleTimeout (connexion inactive entre deux requêtes, 180s d'origine), ne sont pas traités ici.

Cette différence de nature a une conséquence pratique. Le fichier dynamique est relu à chaud par Traefik. Le fichier statique ne l'est pas : un changement de writeTimeout demande de redémarrer Traefik, ce que le lab rappelle dans son README. La page Dépanner un point d'entrée Traefik cite déjà les valeurs d'origine de deux de ces délais : 30 secondes pour dialTimeout et aucun délai pour responseHeaderTimeout.

Les horloges d'un échange à travers Traefik. La requête est lue par le point d'entrée, puis Traefik se connecte au backend, borné par dialTimeout, attend les en-têtes de la réponse, bornés par responseHeaderTimeout, puis relaie le corps de la réponse, par exemple des événements SSE, jusqu'à la dernière écriture vers le client. Le writeTimeout du point d'entrée est une horloge unique qui court de la fin de la lecture des en-têtes de la requête à la dernière écriture.Requête luepar le point d'entréeConnexion au backenddialTimeoutAttente des en-têtesresponseHeaderTimeoutCorps de la réponseévénements SSEDernière écriturevers le clientwriteTimeoutune seule horloge, du début à la fin
Deux bornes de transport pour les étapes vers le backend, et une seule horloge pour tout l'échange côté point d'entrée. Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

Regardez le schéma de gauche à droite : les deux bornes du transport ne couvrent que leur étape, alors que writeTimeout couvre toute la ligne. Ce détail décide de tout ce qui suit, y compris du fait que les deux familles de réglages doivent se choisir ensemble.

Comment borner un backend qui ne répond jamais sans couper une lenteur légitime ?

Section intitulée « Comment borner un backend qui ne répond jamais sans couper une lenteur légitime ? »

Il faut fixer dialTimeout et responseHeaderTimeout dans le transport de serveur, au-dessus de la réponse légitime la plus lente et en dessous de ce que le client supporte. Sans responseHeaderTimeout, un backend qui accepte la connexion puis se tait laisse le client attendre indéfiniment : Traefik n'a aucune raison d'abandonner. Le lab part d'un transport nommé lent, réglé à 30 secondes sur les deux délais : un utilisateur devant un backend muet attend donc jusqu'à une demi-minute avant de voir une erreur.

# FRAGMENT : à placer dans le fichier dynamique, services et routers non montrés
http:
serversTransports:
borne:
forwardingTimeouts:
dialTimeout: 3s
responseHeaderTimeout: 5s
services:
api:
loadBalancer:
serversTransport: borne

Le bloc déclare un transport nommé borne, puis le service api le cite dans serversTransport. Les valeurs de 3 et 5 secondes sont celles de la mesure ci-dessous, pas une recommandation : choisissez-les en face de votre réponse légitime la plus lente, avec une marge. Un transport s'applique aux services qui le citent, et à eux seuls : dans le lab, le service qui sert le flux SSE n'en cite aucun et n'est donc pas touché.

Mesuré sur Traefik 3.7.15, avec ce transport : une réponse légitime de 1,5 seconde rend 200 en 1,50 s ; un backend qui dort rend 504 en 5,00 s, exactement la borne ; une réponse qui prend 7 secondes rend aussi 504 en 5,00 s. Le 504 (Gateway Timeout) est le code que Traefik produit lui-même quand le backend n'a pas répondu à temps.

Retenez la portée de responseHeaderTimeout : il mesure, une fois la requête entièrement envoyée au backend, le temps jusqu'au début de la réponse, c'est-à-dire ses en-têtes. Un backend qui envoie ses en-têtes tout de suite puis distille le corps pendant des minutes n'est donc pas visé par lui : ce n'est pas une borne d'inactivité sur le corps (un délai qui repart à zéro à chaque octet reçu). Le cas d'un flux servi derrière un responseHeaderTimeout court n'a pas été mesuré ici.

Pourquoi le délai d'écriture du point d'entrée coupe-t-il un flux SSE actif ?

Section intitulée « Pourquoi le délai d'écriture du point d'entrée coupe-t-il un flux SSE actif ? »

writeTimeout est une durée totale accordée à l'écriture d'une réponse, pas une borne d'inactivité : un flux SSE qui émet un événement par seconde est coupé quand même, à l'échéance. Un SSE (Server-Sent Events) est une réponse HTTP qui ne se termine pas et dans laquelle le serveur écrit des événements au fil du temps. L'échéance est le moment où la durée accordée est écoulée. La leçon sur les flux longs pose qu'une borne de lecture repart à chaque octet, et qu'un battement (un petit message envoyé à intervalle régulier pour montrer que la liaison vit) ne protège pas d'une borne de durée totale. La première règle est vraie des bornes d'inactivité, mais writeTimeout n'en est pas une, et c'est la différence la plus trompeuse de cette leçon : la mesure ci-dessous est celle que la leçon sur les flux longs cite.

Voici ce qui a été mesuré, avec un backend qui émet un événement par seconde, sans aucune inactivité.

writeTimeout du point d'entréeFenêtre d'observationÉvénements reçus
3s (réglage de départ du lab)8 s3
20s25 s20
0s (aucun délai)8 s8

Dans les deux premières lignes, le nombre d'événements est celui des secondes écoulées jusqu'à l'échéance : le flux est fermé à writeTimeout, alors qu'il n'a jamais été silencieux. Un battement placé dans le flux n'y change rien, car il ne remet pas l'horloge à zéro. Dans la mesure, seule la valeur 0s, qui supprime la protection, a laissé le flux vivre. Le scénario du lab parle d'une coupure « après une courte période d'inactivité » ; la mesure montre une coupure à durée fixe.

Cette horloge unique a une conséquence sur les bornes du transport. writeTimeout couvre aussi l'attente du backend, puisque l'horloge court depuis la fin de la lecture des en-têtes de la requête jusqu'à la fin de l'écriture de la réponse (c'est la définition de la documentation de Traefik). Avec un writeTimeout de 3 secondes et un transport qui abandonne à 5, un backend muet ne rend donc aucun 504 : le client voit la connexion se fermer sans réponse (curl affiche le code 000 au bout de 5,00 s). Avec ces mêmes réglages, une réponse légitime de 4 secondes subit le même sort, alors que celle de 2 secondes passe. Les bornes du transport ne produisent plus leur 504 dès que writeTimeout est plus court : il faut les régler ensemble, avec un writeTimeout supérieur à la plus longue attente que vous acceptez.

# FRAGMENT : fichier STATIQUE de Traefik, redémarrage nécessaire
entryPoints:
web:
address: ":80"
transport:
respondingTimeouts:
writeTimeout: 0s

Le bloc supprime la borne pour le point d'entrée web. C'est le réglage qui a laissé passer les huit événements sur huit dans la mesure, mais il retire aussi la borne qui protège, entre autres, contre un client qui lit trop lentement (cet effet n'a pas été mesuré). Fixer une valeur élevée plutôt que zéro garde une protection, au prix d'un flux qui se ferme à l'échéance : le client doit alors se reconnecter. Ce compromis est un jugement, pas une règle : il dépend de la durée de vie que vous attendez d'un flux. Comme writeTimeout se pose sur le point d'entrée, il vaut pour toutes les routes qui l'empruntent ; sortir les flux sur un point d'entrée dédié est une piste à envisager, que le lab ne met pas en œuvre.

Le WebSocket, une connexion HTTP qui bascule en canal où client et serveur s'écrivent à tout moment, se comporte autrement. Après la réponse 101 (celle qui accepte le changement de protocole), la connexion devient un tunnel : le serveur HTTP de Go, sur lequel repose Traefik, efface les échéances de la connexion au moment où Traefik la lui reprend (opération que Go nomme hijack). Mesuré avec un writeTimeout de 3 secondes, un client WebSocket qui attend 6 secondes avant d'envoyer son message reçoit bien son écho. Dans cette mesure, writeTimeout n'a pas coupé le tunnel. Ne généralisez pas à d'autres délais du point d'entrée, que cette leçon n'a pas mesurés.

Que mesure la sonde de santé, et à quelle vitesse retire-t-elle un backend ?

Section intitulée « Que mesure la sonde de santé, et à quelle vitesse retire-t-elle un backend ? »

La sonde de santé (healthCheck) interroge périodiquement une route de chaque backend et retire de la rotation (l'ensemble des backends entre lesquels Traefik répartit les requêtes) celui qui ne répond pas correctement, mais pas avant son prochain passage. Sans sonde, un backend malade continue d'en faire partie et reçoit sa part de trafic. Dans le lab, l'un des deux backends API devient malade quand on supprime un fichier : avec deux backends répartis à parts égales, 10 requêtes sur 20 échouent, alors que son pair est parfaitement sain.

# FRAGMENT : à placer sous loadBalancer, à côté de servers
healthCheck:
path: /health
interval: 1s
timeout: 1s

La sonde envoie une requête sur path, tous les interval, et attend la réponse pendant timeout au plus. Sans réglage de status, tout code de 200 à 399 vaut « sain » (la documentation dit « entre 2XX et 3XX ») ; le 503 du backend malade du lab est consigné par Traefik comme un échec (checking HTTP health: received error status code: 503). L'intervalle d'origine est de 30 secondes et le timeout d'origine de 5 secondes (documentation de Traefik 3.7) ; la mesure du lab concorde : deux échecs successifs apparaissent dans le journal à 30 secondes d'écart. Le réglage par défaut laisse donc un backend malade en rotation jusqu'à une demi-minute.

La fenêtre de détection se mesure en comptant les échecs juste après la panne : on envoie 20 requêtes courtes à un moment donné après la perte du backend, et on compte celles qui échouent.

Réglage de la sondeMoment de la mesureRequêtes en échec sur 20
aucune sonde2 s après la panne10
interval: 10s1 s après la panne10
interval: 10s12 s après la panne0
interval: 1s, timeout: 1s2 s après la panne0

Lisez la ligne de 10 secondes : tant que le prochain passage n'a pas eu lieu, le backend est encore dans la rotation. Raccourcir l'intervalle rétrécit la fenêtre, au prix de plus de requêtes de sonde vers chaque backend ; c'est un compromis de charge, pas une règle absolue. La leçon Traefik avec Docker explique la différence avec l'état unhealthy que Docker communique, et rappelle qu'une sonde seule ne supprime pas l'erreur pendant la fenêtre.

Deux limites à garder en tête. La sonde ne voit que la route de santé, jamais les routes métier : un backend dont /health répond 200 alors que ses autres routes échouent reste dans la rotation. Elle ne redémarre pas non plus le backend : elle ne modifie que le routage. Dans le lab, les routes de santé et métier dépendent du même fichier, ce qui rend la mesure lisible mais ne prouve rien sur un backend réel.

Comment empêcher qu'un retry compte une action deux fois ?

Section intitulée « Comment empêcher qu'un retry compte une action deux fois ? »

Un middleware de retry (retry) rejoue une requête quand le backend n'a pas répondu, et il ne doit pas rejouer une action qui écrit, à moins que cette action soit idempotente. Une opération idempotente donne le même état final qu'on l'exécute une fois ou dix. Une lecture l'est. Un POST qui incrémente un compteur ne l'est pas, et son rejeu produit un doublon. Le retry (nouvelle tentative) est utile contre une panne passagère, mais il ne sait pas si l'écriture a déjà eu lieu avant la coupure.

Le lab recrée le piège. Le backend d'action incrémente un compteur, puis ferme la connexion sans répondre : l'écriture est faite, la réponse est perdue. Avec le réglage de départ du lab, qui contient retryNonIdempotentMethod: true, Traefik prend le silence pour une panne et rejoue le POST. Mesuré : le compteur finit à 2 pour une seule action, et le client reçoit un 200. Le doublon est donc invisible pour lui : seul le compteur du backend le révèle.

# FRAGMENT : middleware de retry sans le drapeau qui rejoue les écritures
http:
middlewares:
retry-lecture:
retry:
attempts: 3

Avec ce middleware, qui ne contient plus retryNonIdempotentMethod, la même expérience rend un 502 au client et laisse le compteur à 1 : le POST n'est pas rejoué. Le client voit une erreur honnête, au lieu d'un succès qui cache un doublon. Une autre voie consiste à ne pas attacher le middleware au router d'action : il ne rejouera alors rien de cette route. Le choix dépend du fait que d'autres routes du même router méritent un retry, ce que le lab ne mesure pas.

Un 502 reste pourtant une demi-réponse : le client ne sait pas plus que Traefik si l'action a eu lieu. Pour qu'il puisse rejouer sans danger, c'est l'application qui doit reconnaître une requête déjà traitée, par exemple avec une clé d'idempotence fournie par le client. Cela relève du service, pas du proxy, tout comme le battement d'un flux, que la leçon sur les flux longs attribue au service.

Comment prouver chaque réglage depuis un client ?

Section intitulée « Comment prouver chaque réglage depuis un client ? »

On prouve un réglage de délai en chronométrant une requête qui doit échouer ou réussir, pas en relisant le fichier de configuration. Le code seul ne suffit pas : un 000 de curl signifie qu'aucune réponse HTTP n'a été reçue (connexion fermée, ou --max-time écoulé), et c'est un échec différent d'un 504. L'option -w de curl affiche à la fois le code et la durée.

Fenêtre de terminal
curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' --max-time 40 \
-H 'Host: api.flux.lab.test' http://127.0.0.1/bloque

La commande envoie la requête vers la route qui ne répond jamais, avec l'en-tête Host (le nom de site demandé, que le router utilise pour reconnaître la route). Avec le transport borné décrit plus haut et un writeTimeout assez large, la sortie doit montrer 504 et une durée proche de 5. Un 000 obtenu avant l'échéance de --max-time trahit au contraire, très probablement, une coupure par le point d'entrée : Traefik n'a plus pu écrire sa réponse (c'est une déduction de la sémantique de writeTimeout, seul le 000 est mesuré). Le journal d'accès, décrit dans Observer Traefik, donne le même renseignement dans son champ Duration.

Ce que vous voulez prouverMesureLecture
Une lenteur légitime passe/lent?ms=1500, --max-time 4200 en environ 1,5 s
Un backend muet échoue vite/bloque, --max-time 10504 (ou 502 si le backend coupe la connexion) avant 10 s
Un flux vit au-delà de l'échéancecurl -N --max-time 5 sur /events, comptez les lignes data:environ un événement par seconde
Un backend malade est retiré20 requêtes courtes après la panne0 échec
Un POST n'est pas rejouéGET /compteur après une action perdue1

Pour la ligne du flux, curl -N désactive la mise en tampon côté client : sans elle, c'est curl qui retarderait l'affichage et accuserait à tort Traefik. Une mesure qui passe une fois ne prouve pas la robustesse : pour juger un writeTimeout, laissez le flux ouvert plus longtemps que l'échéance et comptez.

Que valide le lab, et que laisse-t-il à votre jugement ?

Section intitulée « Que valide le lab, et que laisse-t-il à votre jugement ? »

Le lab valide des comportements mesurés depuis le client, pas des directives précises. Plusieurs vérifications sont lancées sur la machine du lab : une attente légitime de 1,5 seconde réussit en moins de 4 secondes ; un backend muet échoue par un 502 ou un 504 en moins de 10 secondes ; le flux SSE délivre assez d'événements sur une fenêtre de cinq secondes ; le WebSocket renvoie son écho ; aucune des 20 requêtes courtes n'échoue pendant qu'un backend est malade ; et un POST dont la réponse est perdue ne compte qu'une fois.

Ce que le lab ne mesure pas compte autant. Il n'exige aucune valeur précise de délai, ne vérifie pas que vous gardez une protection plutôt que de supprimer writeTimeout, et n'attend pas le WebSocket après une période d'inactivité : son client est lancé sans attente. Il ne dit rien non plus de la meilleure organisation entre un seul point d'entrée et un point d'entrée dédié aux flux. Cette part est la vôtre : relisez vos valeurs en vous demandant si elles tiendraient face à votre trafic réel, pas seulement face au test.

Le lab vous donne un Traefik dont quatre réglages sont faux : un transport trop généreux, un délai d'écriture trop court pour un flux, une absence de sonde de santé et un retry qui enveloppe une action non idempotente. Vous corrigez le fichier de routes et le fichier statique, redémarrez Traefik quand c'est nécessaire, puis laissez la validation envoyer ses requêtes depuis un client. Elle vérifie les comportements décrits dans la section précédente, pas la forme de votre configuration.

Un Traefik fiable se règle tronçon par tronçon et se prouve par la mesure.

  • Les bornes Traefik vers backend se posent dans un serversTransport ; celles du client vers Traefik dans le point d'entrée, en configuration statique.
  • responseHeaderTimeout mesure l'attente des en-têtes, pas celle du corps ; un backend muet se borne avec lui, et rend un 504 à l'échéance.
  • writeTimeout est une durée totale, pas une inactivité : un flux SSE actif est coupé à l'échéance, et la valeur 0s supprime la borne.
  • Un writeTimeout plus court que les bornes du transport empêche Traefik de produire son 504 : le client reçoit une connexion fermée.
  • Une sonde de santé n'agit qu'à son prochain passage : l'intervalle d'origine est de 30 secondes, et l'intervalle fixe la fenêtre de détection. Elle ne voit que sa route de santé et ne redémarre rien.
  • retryNonIdempotentMethod: true rejoue un POST dont la réponse s'est perdue : le compteur monte à 2 alors que le client reçoit un 200. Sans ce drapeau, le client reçoit un 502 et le compteur reste à 1.
  • Dans la mesure faite, un WebSocket n'est pas coupé par writeTimeout après la réponse 101, ce qui ne se généralise pas aux autres délais du point d'entrée.
  • Un réglage se prouve avec curl -w '%{http_code} %{time_total}s', pas en relisant la configuration.

Ces questions portent sur la portée de chaque délai, sur la fenêtre de détection d'une sonde et sur le rejeu d'une écriture.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

8 questions
8 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn