Retour au cours

infra / monitoring-observabilite

Exporters personnalisés et instrumentation avancée

Leçon 131 exercice

Explication

Ce que vous allez apprendre

  • Écrire un exporter Prometheus personnalisé pour un système sans client natif
  • Utiliser le pattern "Collector" pour calculer des métriques fraîches à chaque scrape
  • Choisir des buckets d'Histogram adaptés à l'ordre de grandeur réel de la donnée
  • Comprendre le rôle de blackbox_exporter pour sonder des endpoints externes
  • Reconnaître le pattern "exporter proxy" via relabel_configs

Dans quel contexte ?

L'entreprise utilise un système de facturation legacy, vieux de dix ans, sans aucune bibliothèque Prometheus disponible pour son langage. Il expose seulement une API REST maison. Pourtant, l'équipe veut suivre la profondeur de sa file d'attente de traitement et l'expiration de sa licence dans les mêmes dashboards Grafana que le reste de l'infrastructure moderne.

D'abord, il faut désacraliser ce qu'est réellement un "exporter"

Après node_exporter et cAdvisor (leçons précédentes), un exporter peut sembler être un outil complexe et spécialisé. En réalité, ce n'est rien de plus qu'un serveur HTTP qui répond au format texte attendu par Prometheus sur un endpoint /metrics — n'importe quel langage capable de faire tourner un serveur HTTP peut en écrire un.

C'est exactement ce que fait le premier exemple de cette leçon : un script Python qui interroge l'API REST du système legacy toutes les 30 secondes, transforme le résultat en Gauges, et les expose via start_http_server.

ApprocheFraîcheur des donnéesComplexité
Boucle en arrière-plan (while True: collect(); sleep(30))Rafraîchie toutes les 30s, même sans scrapeSimple
Pattern Collector (.collect())Recalculée à CHAQUE scrape PrometheusLégèrement plus structuré

Prérequis

Cette leçon suppose une bonne maîtrise de la leçon sur l'écriture de métriques applicatives (Counter, Gauge, Histogram) ; elle en réutilise directement les concepts, mais hors du contexte d'une API web classique.

Une fois l'exporter basique compris, un problème de fraîcheur des données se pose

La boucle while True du premier exemple actualise les valeurs toutes les 30 secondes, indépendamment du rythme de scraping réel de Prometheus. Si Prometheus scrape toutes les 15 secondes, il récupère parfois une valeur "périmée" de quelques secondes ; si Prometheus scrape moins souvent, la donnée est inutilement recalculée en pure perte entre deux scrapes.

Voici comment on résout ce problème : le pattern Collector

Une classe avec une méthode .collect(), enregistrée auprès du REGISTRY de Prometheus, n'est appelée qu'au moment PRÉCIS où Prometheus vient scraper — jamais avant, jamais après. C'est plus simple à raisonner pour des sources de données lentes ou coûteuses à interroger, puisqu'il n'y a plus de fréquence à synchroniser manuellement entre deux systèmes.

Piège fréquent

Réutiliser les buckets par défaut d'un Histogram pour un job batch qui dure plusieurs minutes produit un histogramme complètement inutilisable : toutes les observations tombent dans le dernier bucket, rendant tout calcul de percentile grossier voire faux. Les buckets doivent toujours être pensés pour l'ordre de grandeur réel de la donnée mesurée (millisecondes pour une API, minutes pour un batch).

Il reste un dernier cas particulier, différent de tout ce qui précède : surveiller des systèmes EXTERNES

Astuce

blackbox_exporter ne surveille rien en interne : il sonde activement des endpoints externes (un site public, une API tierce) pour vérifier leur disponibilité, leur code de statut HTTP ou même l'expiration de leur certificat TLS. C'est le seul exporter de ce cours qui teste depuis l'EXTÉRIEUR, comme le ferait un vrai utilisateur.

Son fonctionnement particulier passe par relabel_configs, qui redirige la cible réelle du scrape vers l'exporter lui-même, tout en transmettant l'URL à sonder comme simple paramètre — un pattern qu'on appelle "exporter proxy".

Après cette leçon technique, la suite du cours change complètement d'angle : comment garder toute cette stack de monitoring elle-même fiable et disponible, même en cas de panne d'un de ses composants.

Commandes & code

Exporters personnalisés et instrumentation avancée

python
# Exporter custom en Python : exposer des métriques venant d'une source SANS client Prometheus natif
# (ex : interroger une API tierce, un fichier, une base legacy)
from prometheus_client import start_http_server, Gauge
import time
import requests

QUEUE_DEPTH = Gauge("legacy_queue_depth", "Profondeur de la file d'attente legacy", ["queue_name"])
LICENSE_DAYS_LEFT = Gauge("license_days_remaining", "Jours restants avant expiration de licence")


def collect():
    resp = requests.get("http://legacy-system/api/queues").json()
    for queue in resp["queues"]:
        QUEUE_DEPTH.labels(queue_name=queue["name"]).set(queue["depth"])

    license_info = requests.get("http://legacy-system/api/license").json()
    LICENSE_DAYS_LEFT.set(license_info["days_remaining"])


if __name__ == "__main__":
    start_http_server(9200)         # expose /metrics sur le port 9200, comme un exporter standard
    while True:
        collect()
        time.sleep(30)
python
# Collector custom (classe) : pattern plus propre pour des métriques calculées à la volée à chaque scrape
from prometheus_client.core import GaugeMetricFamily, REGISTRY
from prometheus_client import start_http_server

class DatabaseConnectionCollector:
    def collect(self):
        metric = GaugeMetricFamily(
            "db_connections_active", "Connexions actives par base", labels=["database"]
        )
        for db_name, count in get_active_connections_from_pool().items():
            metric.add_metric([db_name], count)
        yield metric

REGISTRY.register(DatabaseConnectionCollector())
start_http_server(9201)
# -> collect() est appelé à CHAQUE scrape Prometheus, pas en arrière-plan : toujours une valeur fraîche
python
# Histogram custom avec buckets adaptés au domaine métier (pas les buckets par défaut, souvent inadaptés)
from prometheus_client import Histogram

# Buckets par défaut de prometheus_client (secondes) : mal adaptés à une latence de batch de plusieurs minutes
BATCH_DURATION = Histogram(
    "batch_job_duration_seconds", "Durée des jobs batch",
    buckets=[1, 5, 15, 30, 60, 120, 300, 600, 1800, 3600]   # buckets pensés pour des minutes/heures, pas des ms
)

# Pour des latences API sub-seconde, l'inverse : des buckets fins en dessous de 100ms
API_LATENCY = Histogram(
    "api_call_duration_seconds", "Latence des appels API",
    buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5]
)
yaml
# blackbox_exporter : sonde des endpoints EXTERNES (uptime, TLS, latence DNS) — pas juste des métriques internes
# blackbox.yml
modules:
  http_2xx:
    prober: http
    timeout: 5s
    http:
      valid_status_codes: [200]
      method: GET

  tcp_connect:
    prober: tcp
    timeout: 5s
yaml
# prometheus.yml : scraper via blackbox_exporter (pattern "proxy" de scraping)
scrape_configs:
  - job_name: "blackbox_http"
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://example.com
          - https://api.example.com/health
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115   # remplace la cible réelle par l'exporter qui fait la sonde

Résumé

  • Un exporter custom n'est qu'un serveur HTTP exposant /metrics au format texte Prometheus : aucune magie, juste une convention.
  • Le pattern "Collector" (classe avec .collect()) calcule les valeurs à CHAQUE scrape plutôt qu'en tâche de fond : plus simple à raisonner pour des sources lentes.
  • Les buckets d'un Histogram doivent être choisis selon l'ordre de grandeur réel de la donnée (ms pour de l'API, minutes pour du batch) : les défauts ne conviennent pas à tout.
  • blackbox_exporter illustre le pattern "exporter proxy" : Prometheus scrape l'exporter, qui lui-même sonde une cible externe, via relabel_configs.

Exercices pratiques

1 disponible
1

Mission : brancher le système de facturation legacy

Objectif : Écrire un exporter Prometheus custom pour un système sans client natif, avec des buckets d'Histogram adaptés à l'ordre de grandeur réel de la donnée.

Contexte

Le système de facturation legacy expose une API REST maison, sans aucune bibliothèque Prometheus. L'équipe veut suivre la profondeur de sa file d'attente ET la durée de ses jobs de facturation batch, qui durent typiquement entre 2 et 40 minutes — bien au-delà des buckets par défaut pensés pour des latences web.

Résoudre l’exercice →