Lab 6 bonus — Une métrique qui va et vient
Cette page fait naître une métrique sans écrire une ligne de code — le collecteur la dérive des spans qu’il voit passer — puis explique le comportement déroutant qui s’ensuit : app_spans_errors_total apparaît, disparaît, revient. Ce qu’il révèle du modèle de données des métriques vaut le détour.
1. Dériver une métrique depuis les spans — le connector count
Aucun des labs précédents n’en dépend, et le fichier de values suit exactement le modèle du Lab 3.
Ajouter le connector
count: comme au Lab 3, un fichier de values,manifests/61-otel-metrics-spans-values.yaml, dont la configuration du collecteur vit sous la sectionopentelemetry-collector.config:Il doit compter les spans en erreur et exposer le résultat en métrique
app.spans.errors.La documentation du connector
countdonne la structure attendue (spans:, puis une entrée par métrique avec sesconditions:) ; la condition elle-même s’écrit en OTTL, dont les fonctions sont répertoriées ici.
Comme au Lab 3, relisez la ConfigMap pour voir ce que Helm a réellement produit de vos values :
Ou, pour aller droit au connector que vous venez d’ajouter :
C’est le seul endroit qui dit la vérité sur la configuration en vigueur : vos values sont un calque, la ConfigMap est ce que le collecteur lit au démarrage.
- Provoquer des erreurs et vérifier : créez un avis pour un produit inexistant (le service échoue en 500) :
Dans Prometheus, cherchez app_spans_errors_total : votre première métrique dérivée des traces, sans une ligne de code. Ventilez-la par service :
Votre unique requête a fait monter la série de review-service de 3. Pourquoi pas de 1 ?
💡 Si vous revenez sur cette métrique plus tard, elle aura disparu. Elle n’est alimentée que lorsqu’une erreur survient, et une requête instantanée ne regarde que les cinq dernières minutes. Vos données sont pourtant bien là : ouvrez l’onglet Graph sur la dernière heure, ou demandez la dernière valeur connue avec
last_over_time(app_spans_errors_total[1h]).Le pourquoi — delta, cumulative, et ce que le collecteur garde en mémoire — occupe le reste de cette page.
2. Le symptôme
Vous provoquez une erreur, la métrique apparaît dans Prometheus. Vous revenez dix minutes plus tard, et la même requête répond :
Rien n’est cassé, rien n’est perdu. Trois mécanismes se superposent, et chacun est utile à connaître.
3. Delta ou cumulative : ce que porte un point
Une métrique se transporte de deux façons, et c’est un choix de l’émetteur :
| Ce que porte chaque point de mesure | |
|---|---|
| Cumulative | le total depuis le démarrage — le compteur ne fait que monter |
| Delta | l’incrément depuis l’export précédent |
Le SDK OpenTelemetry exporte en cumulative : toutes les 60 secondes, il republie le total courant, même s’il n’a pas bougé. C’est le cas de votre reviews_created_total.
Le connector count, lui, émet en delta : à chaque cycle il annonce « tant de spans en erreur depuis la dernière fois ». Et quand il n’y en a eu aucun, il n’envoie rien du tout — pas même un zéro.
Prometheus, de son côté, ne sait travailler qu’en cumulatif : rate() calcule une pente, ce qui suppose une courbe qui monte. C’est tout le rôle du processor deltatocumulative ajouté à la section 1 : il additionne les deltas au fil de l’eau pour reconstituer un total.
Sans lui, l’endpoint OTLP de Prometheus rejette les points. Vérifié en retirant le processor du pipeline sur le cluster de la formation — voici ce que le collecteur écrit alors, une ligne par export :
Deux mots comptent dans ce message. Permanent error : le collecteur ne réessaiera pas, il n’y a rien à espérer d’un nouvel envoi. Et Dropping data : les points sont perdus, définitivement. Côté Grafana, vous ne verriez qu’un panel vide — c’est un exercice de débogage classique, et le réflexe qu’il enseigne vaut pour toute la chaîne : quand une donnée manque, lisez d’abord les logs du composant qui l’émet.
Le processor est-il vraiment obligatoire ?
Oui ici, mais ce n’est pas une bonne pratique du connector count : c’est la destination qui l’impose. Si vous exportiez vers un backend qui parle nativement delta — Datadog, une passerelle StatsD — le processor serait inutile, et il faudrait même faire l’inverse (cumulativetodelta existe pour cela).
Et le collecteur n’est pas le seul endroit possible. Prometheus 3 sait faire la conversion lui-même, derrière un drapeau :
Il n’est pas activé sur le Prometheus de la formation — seul exemplar-storage l’est — d’où l’échec sans le processor.
Alors, où convertir ? Dans le collecteur, pour trois raisons : l’état d’accumulation reste près de la source, la même configuration fonctionne quel que soit le backend, et le drapeau Prometheus est encore expérimental. Avec une réserve, développée au § 5 : le collecteur garde cet état en mémoire, donc un redémarrage de son pod remet les compteurs dérivés à zéro. Côté Prometheus, l’état serait persisté — c’est vraisemblablement la raison d’être de ce drapeau.
4. Pourquoi elle sort des résultats
Une requête instantanée — app_spans_errors_total sans fonction de fenêtre — ne cherche pas la valeur à la milliseconde près. Elle prend le dernier point situé dans les cinq minutes qui précèdent, un paramètre de Prometheus nommé query.lookback-delta.
Or le connector cesse d’émettre dès que les erreurs s’arrêtent. Passé cinq minutes, plus aucun point dans la fenêtre : la série n’est plus retournée.
Elle n’est pas supprimée pour autant. Sur le Prometheus de la formation, les données sont conservées une semaine (storage.tsdb.retention.time).
Le contraste avec votre compteur applicatif est net. Relevé sur 30 minutes, au pas d’une minute :
| Métrique | Points relevés |
|---|---|
reviews_created_total (SDK, cumulative) | 31 sur 30 — un à chaque pas |
app_spans_errors_total (connector, delta) | 15 à 18 — la série est trouée |
Une métrique dérivée des traces n’existe que tant qu’il se passe quelque chose.
5. La retrouver
Demandez une fenêtre plutôt qu’un instant :
qui renvoie la dernière valeur connue de chaque série sur l’heure écoulée. L’onglet Graph de Prometheus fait la même chose visuellement : les points passés y restent visibles, trous compris.
⚠️ Faux ami : count_over_time compte les points de mesure, pas les erreurs. Sur une même série, il affiche 18 là où le compteur vaut 3. La requête « marche », et le nombre n’a rien à voir avec ce que vous cherchez.
6. Ce que le cumul ne survit pas
Le total reconstitué par deltatocumulative vit dans la mémoire du collecteur. Deux conséquences :
- le collecteur de la démo est un DaemonSet — chaque
helm upgraderedémarre son pod, et les compteurs dérivés repartent de zéro ; - le processor ne garde pas indéfiniment l’état d’une série inactive.
Le second point se mesure. Relevé sur le cluster de la formation, en provoquant des erreurs puis en laissant le service tranquille :
Le compteur ne reprend pas à 15 : il repart de zéro. Le processor avait oublié la série faute de nouveaux deltas.
C’est une différence de fond avec un compteur applicatif, dont le total est tenu par le SDK dans la mémoire du service et republié à chaque cycle, actif ou non. En pratique, cela veut dire qu’une métrique dérivée n’est pas un compteur au long cours : sa valeur absolue ne raconte rien. On la lit avec rate() ou increase(), qui détectent ces remises à zéro et les traitent correctement — là où l’œil, lui, y verra une baisse inexpliquée.
7. Compter des requêtes plutôt que des spans
Le Lab 6 le montre : une seule requête en échec fait monter app_spans_errors_total de 3 pour review-service, parce que l’exception traverse trois spans. Pour compter des requêtes, il faut ne retenir que les spans serveur — il n’y en a qu’un par service et par requête.
Retour aux mains dans le cambouis, et c’est court. Un même connector peut produire plusieurs métriques : il suffit d’une seconde entrée sous spans:. Le fichier de référence est 61-otel-metrics-values.yaml :
kind == SPAN_KIND_SERVER ne retient que les spans de requêtes reçues : un service qui échoue en appelant un autre ne compte pas ici, seul l’échec qu’il renvoie à son propre appelant est compté.
Il s’empile sur celui de la section 1 : Helm fusionne les maps, donc app.requests.errors s’ajoute à app.spans.errors sans la remplacer. Rien d’autre à redéclarer — ni les pipelines, ni deltatocumulative :
Provoquez trois erreurs comme à l’étape 8 du Lab 6, patientez un cycle d’export, et comparez les deux métriques :
Les deux répondent à deux questions différentes — « combien de requêtes ont échoué ? » et « quelle est l’ampleur de la panne dans le système ? » — et il vaut mieux savoir laquelle on lit.