RousselTMLEARNING

Observabilité

Recording rules Prometheus : accélérer ses requêtes et ses tableaux de bord

Comprendre et maîtriser les recording rules de Prometheus : évaluation, convention de nommage, pièges (ratios, séries vides), tests avec promtool, supervision et recalcul du passé. Exemples testés sur Prometheus 3.15.

1. Pourquoi des recording rules ? #

Calculer à l'avance plutôt qu'à chaque lecture.

Le problème #

Une requête PromQL est calculée au moment où on la lance. Sur un tableau de bord de vingt panneaux, actualisé toutes les trente secondes et ouvert par dix personnes, Prometheus refait sans cesse les mêmes calculs sur les mêmes échantillons. Le tableau de bord devient lent, et chaque utilisateur supplémentaire aggrave la situation.

Une recording rule (règle d'enregistrement) résout ce problème : Prometheus évalue l'expression à intervalle régulier et écrit le résultat dans une nouvelle série. Les lectures deviennent de simples accès à cette série.

Coût d'une requête avec et sans recording rule

Mesure réelle : sur un Prometheus 3.15 de test, avec stats=all, sum by (job) (rate(prometheus_http_requests_total[5m])) lit 448 échantillons, et la série enregistrée équivalente un seul. L'écart est bien plus grand sur un serveur de production.

Quand en utiliser, et quand s'en passer #

Bonne idéeMauvaise idée
Requête coûteuse et fréquente (tableau de bord très consulté)Requête lancée rarement, à la main
Alerte dont l’expression est lourdeRequête déjà rapide sur peu de séries
Indicateur partagé (SLI, taux d’erreur) à définir une seule foisConserver toutes les étiquettes d’origine : on ne réduit rien
Agrégats à conserver longtemps ou à fédérerRemplacer une exploration : la fenêtre (5m) est figée

Une règle n'est pas gratuite : chaque série enregistrée consomme du stockage et de la mémoire, comme une série collectée. Le gain vient de l'agrégation : remplacer des milliers de séries par quelques dizaines.

2. Comment Prometheus les évalue #

Groupes, intervalle, ordre et absence d'effet rétroactif.

Le fonctionnement #

  • Les règles sont rangées dans des groupes, déclarés dans des fichiers référencés par rule_files.
  • Dans un groupe, elles s'exécutent dans l'ordre, au même horodatage. Une règle peut donc utiliser le résultat de celle qui la précède.
  • L'intervalle est celui du groupe (interval), sinon le evaluation_interval global.
  • Le résultat garde les étiquettes de l'expression ; le nom de la métrique est remplacé par record.
  • Une règle ne calcule que le présent : une nouvelle règle n'a pas d'historique, sauf recalcul manuel (voir plus loin).

Anatomie d'un nom de règle et enchaînement des règles dans un groupe

Attention à l'intervalle : une série qui n'a pas de point depuis plus de cinq minutes (le délai de recherche par défaut) n'apparaît plus dans les requêtes instantanées. Un groupe évalué toutes les dix minutes produit donc des trous.

3. Écrire ses premières règles #

La syntaxe, un exemple complet et les options du groupe.

Un fichier de règles #

Déclarez le fichier dans prometheus.yml, puis écrivez les règles. Ces exemples portent sur les métriques de Prometheus lui-même, pour pouvoir les essayer immédiatement :

global:
  scrape_interval: 5s
  evaluation_interval: 15s

rule_files:
  - /etc/prometheus/rules.yml

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ["localhost:9090"]
groups:
  - name: http_prometheus
    rules:
      - record: job_handler:prometheus_http_requests:rate5m
        expr: sum by (job, handler) (rate(prometheus_http_requests_total[5m]))

      - record: job:prometheus_http_requests:rate5m
        expr: sum by (job) (job_handler:prometheus_http_requests:rate5m)

      - record: job:prometheus_http_requests_errors:rate5m
        expr: sum by (job) (rate(prometheus_http_requests_total{code=~"5.."}[5m]))

      - record: job:prometheus_http_requests_errors:ratio_rate5m
        expr: |
          (job:prometheus_http_requests_errors:rate5m or job:prometheus_http_requests:rate5m * 0)
            / job:prometheus_http_requests:rate5m

      - record: job:prometheus_http_request_duration_seconds:p95_rate5m
        expr: |
          histogram_quantile(0.95,
            sum by (job, le) (rate(prometheus_http_request_duration_seconds_bucket[5m])))

Dans Grafana, la requête devient très simple :

# Avant : recalculé à chaque actualisation de chaque panneau
sum by (job) (rate(http_requests_total[5m]))

# Après : simple lecture de la série enregistrée
job:http_requests:rate5m

Les options d'un groupe #

groups:
  - name: agregats_http
    interval: 30s        # sinon : evaluation_interval du fichier global
    query_offset: 30s    # évalue un peu dans le passé, si les données arrivent en retard
    limit: 1000          # échec de la règle si elle produit plus de 1000 séries
    labels:
      equipe: plateforme # ajouté à toutes les règles du groupe
    rules:
      - record: job:http_requests:rate5m
        expr: sum by (job) (rate(http_requests_total[5m]))

4. Bien nommer ses règles #

La convention niveau:métrique:opérations.

La convention officielle #

La documentation de Prometheus recommande le format niveau:métrique:opérations. Le nom dit ainsi ce que contient la série, sans ouvrir le fichier de règles. Les deux-points sont réservés à cet usage : les exporteurs ne doivent pas en utiliser.

PartieSensExemple
niveauÉtiquettes conservées par l’agrégationjob, instance_path
métriqueNom d’origine, sans le suffixe _total après un ratehttp_requests
opérationsOpérations appliquées, la plus récente en premierrate5m, ratio_rate5m, p95_rate5m

5. Les pièges à éviter #

Ratios, séries absentes, ordre des opérations.

Ne jamais faire la moyenne de ratios #

Un service à 10 requêtes par seconde avec 50 % d'erreurs et un autre à 1000 requêtes par seconde avec 0 % n'ont pas 25 % d'erreurs en moyenne, mais environ 0,5 %. Enregistrez donc le numérateur et le dénominateur, puis divisez leurs sommes :

# Faux : moyenne de ratios, un petit service pèse autant qu'un gros
avg by (job) (instance:http_requests_errors:ratio_rate5m)

# Juste : on agrège le numérateur et le dénominateur séparément
sum by (job) (instance:http_requests_errors:rate5m)
  / sum by (job) (instance:http_requests:rate5m)

Le ratio qui disparaît quand tout va bien #

C'est le piège le plus sournois, constaté en préparant ce chapitre. Si aucune série 5xx n'existe, le filtre ne renvoie rien, et une division par un résultat vide donne un résultat vide, pas 0. Le tableau de bord affiche « No data » : impossible alors de distinguer « aucune erreur » d'une panne de la collecte.

# Vide tant qu'aucune erreur 5xx n'a été observée
job:requests_errors:rate5m / job:requests:rate5m

# Vaut 0 quand il n'y a aucune erreur
(job:requests_errors:rate5m or job:requests:rate5m * 0) / job:requests:rate5m

Les autres erreurs fréquentes #

  • rate d'abord, sum ensuite : sum(rate(x[5m])). Additionner des compteurs avant le rate fausse la détection de leurs remises à zéro.
  • Histogrammes : gardez l'étiquette le dans le sum by, sinon histogram_quantile ne peut rien calculer.
  • Trop d'étiquettes : une règle qui conserve instance ou pod garde presque toute la cardinalité.
  • Changer une règle : si l'expression change de sens, changez aussi le nom : sinon l'ancienne et la nouvelle logique se mélangent dans la même série.

6. Tester ses règles avec promtool #

Vérification et tests unitaires, avant de déployer.

Des tests unitaires, sans serveur #

promtool check rules vérifie la syntaxe. promtool test rules va plus loin : il injecte des séries fictives, évalue les règles et compare le résultat. Le test ci-dessous couvre les deux cas du piège précédent :

rule_files:
  - rules.yml
evaluation_interval: 1m

tests:
  - name: 10 % d'erreurs
    interval: 1m
    input_series:
      - series: 'prometheus_http_requests_total{job="api",handler="/a",code="200"}'
        values: '0+54x10'
      - series: 'prometheus_http_requests_total{job="api",handler="/a",code="500"}'
        values: '0+6x10'
    promql_expr_test:
      - expr: job:prometheus_http_requests:rate5m
        eval_time: 10m
        exp_samples:
          - labels: 'job:prometheus_http_requests:rate5m{job="api"}'
            value: 1
      - expr: job:prometheus_http_requests_errors:ratio_rate5m
        eval_time: 10m
        exp_samples:
          - labels: 'job:prometheus_http_requests_errors:ratio_rate5m{job="api"}'
            value: 0.1

  - name: aucune erreur, le ratio vaut 0 et non vide
    interval: 1m
    input_series:
      - series: 'prometheus_http_requests_total{job="api",handler="/a",code="200"}'
        values: '0+60x10'
    promql_expr_test:
      - expr: job:prometheus_http_requests_errors:ratio_rate5m
        eval_time: 10m
        exp_samples:
          - labels: 'job:prometheus_http_requests_errors:ratio_rate5m{job="api"}'
            value: 0

Comment lire values: '0+54x10' : la série part de 0 et augmente de 54 à chacun des 10 pas d'une minute, soit 0,9 requête par seconde. Avec 0,1 requête en erreur, le ratio attendu est bien 0,1.

7. Déployer #

Recharger la configuration, Kubernetes.

Recharger sans redémarrer #

Après modification, envoyez un signal SIGHUP au processus, ou appelez POST /-/reload si Prometheus a été lancé avec --web.enable-lifecycle. Une erreur dans le fichier laisse l'ancienne configuration en place : vérifiez donc le résultat dans Status > Rule health ou avec /api/v1/rules.

--web.enable-lifecycle permet à quiconque atteint le port de recharger ou d'arrêter Prometheus. Protégez cet accès en production.

Sur Kubernetes #

Avec le Prometheus Operator, les règles sont déclarées par une ressource PrometheusRule. Son étiquette doit correspondre au ruleSelector de votre Prometheus, sinon elle est ignorée sans message.

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: http-recording-rules
  namespace: monitoring
  labels:
    release: kube-prometheus-stack   # doit correspondre au ruleSelector de votre Prometheus
spec:
  groups:
    - name: http
      rules:
        - record: job:http_requests:rate5m
          expr: sum by (job) (rate(http_requests_total[5m]))

8. Superviser ses règles #

Détecter un groupe trop lent ou une règle en échec.

Les métriques à suivre #

Si un groupe met plus de temps à s'évaluer que son intervalle, des évaluations sont sautées et des trous apparaissent. Prometheus expose tout ce qu'il faut pour le détecter :

# Une évaluation dépasse-t-elle son intervalle ? (à surveiller en priorité)
rate(prometheus_rule_group_iterations_missed_total[5m]) > 0

# Part de l'intervalle consommée par chaque groupe
prometheus_rule_group_last_duration_seconds / prometheus_rule_group_interval_seconds

# Règles en échec
rate(prometheus_rule_evaluation_failures_total[5m]) > 0
Bonne pratique : alertez sur prometheus_rule_group_iterations_missed_total. Si un groupe devient trop lent, découpez-le : les groupes s'exécutent en parallèle.

9. Recalculer le passé #

Le backfill avec promtool.

Créer l'historique d'une nouvelle règle #

promtool tsdb create-blocks-from rules interroge l'API d'un Prometheus et écrit des blocs TSDB dans le dossier data/ (option --output-dir). Il faut ensuite les déplacer dans le répertoire de données (storage.tsdb.path) : ils fusionnent avec les blocs existants au prochain compactage.

Limites documentées : une règle qui dépend d'une autre règle en cours de recalcul n'est pas prise en charge : recalculez d'abord les règles de base, déplacez leurs blocs, puis relancez pour les règles dépendantes. Les alertes du fichier sont ignorées. Relancer sur une période qui se chevauche crée à chaque fois des blocs contenant les mêmes données.

Commandes à connaître #

# Vérifier la syntaxe et compter les règles
promtool check rules rules.yml

# Lancer les tests unitaires
promtool test rules tests.yml

# Recharger sans redémarrer (avec --web.enable-lifecycle)
curl -X POST http://localhost:9090/-/reload

# Lister les règles, leur santé et leur durée
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[] | {name, evaluationTime, rules: [.rules[] | {name, health}]}'

# Mesurer le coût d'une requête
curl -s --get --data-urlencode 'query=job:prometheus_http_requests:rate5m' --data-urlencode 'stats=all' \
  http://localhost:9090/api/v1/query | jq .data.stats.samples.totalQueryableSamples

# Recalculer le passé (création de blocs TSDB)
promtool tsdb create-blocks-from rules --start 2026-11-01T00:00:00Z --end 2026-11-02T00:00:00Z \
  --url http://localhost:9090 --eval-interval 60s rules.yml

Sources officielles #

Articles liés

Monitoring de site web : disponibilité, surveillance HTTPS, certificats et performances

Mettre en place le monitoring d'un site web : surveillance de la disponibilité et du HTTPS, expiration des ce…

Prometheus sur Kubernetes et l'interface web de Prometheus

Déployer Prometheus sur Kubernetes avec kube-prometheus-stack (Prometheus Operator, ServiceMonitor, Alertmana…

Prometheus : alerter avec Alertmanager (règles, routage, silences)

Mettre en place l'alerting Prometheus : règles d'alerte, Alertmanager, routage, regroupement, inhibition, sil…

Pour aller plus loin

Soutenir

♥ SOUTENIR

Soutenez RousselTM Learning

Les formations, articles, TP et attestations restent gratuits et sans publicité intrusive. Une contribution libre, du montant de votre choix, finance l’hébergement, les environnements de TP et l’écriture de nouveaux contenus.

Contribuer avec PayPal Montant libre · carte bancaire ou compte PayPalEn savoir plus
QR code PayPal
Ou scannez ce QR code avec votre téléphone

Merci, chaque contribution compte !