kube-prometheus-stack : déploiement Helm et Prometheus Operator en production

Développement Renard orange tenant une feuille avec le logo Kubernetes, devant des serveurs, un écran affichant un graphique et un cadran de mesure.

Table des matières :

  1. kube-prometheus-stack : ce que vous déployez vraiment (spoiler : pas « juste Prometheus »)
  2. Choisir une architecture production (avant de lancer Helm comme un sauvage)
  3. Déploiement Helm en production : les réglages qui évitent la panique à J+7
  4. Prometheus Operator au quotidien : ServiceMonitor, PodMonitor, PrometheusRule (et les pièges)
  5. Stockage, rétention, HA et performance : le TSDB n’est pas un sac sans fond
  6. Sécurité et gouvernance : parce que /metrics est souvent plus bavard que votre DSI

kube-prometheus-stack : ce que vous déployez vraiment (spoiler : pas « juste Prometheus »)

Le kube-prometheus-stack (chart Helm maintenu par la communauté Prometheus) n’est pas un « paquet Prometheus + Grafana ». C’est une distribution Kubernetes opinionated qui installe généralement Prometheus Operator, Prometheus, Alertmanager, Grafana, kube-state-metrics et node-exporter, plus un bon paquet de règles et dashboards prêts à l’emploi. La promesse est séduisante : un helm upgrade --install et vous voilà « observables ». La réalité en production : si vous laissez tout par défaut, vous aurez surtout… des alertes inutiles, une TSDB qui gonfle et des équipes qui détestent le monitoring (ce qui est dommage, parce que c’est littéralement son job de sauver votre week-end).

Pour être concret, voilà ce que vous « achetez » en une commande Helm, et pourquoi ça change la donne en prod :

  • Prometheus Operator : un contrôleur Kubernetes qui réconcilie des CRDs (Prometheus, ServiceMonitor, PrometheusRule, etc.) en ressources réelles (StatefulSets, Secrets/Config, RBAC…). Vous ne gérez plus un prometheus.yml à la main : vous gérez des objets Kubernetes.
  • Prometheus : le moteur de collecte (scrape), stockage (TSDB), requêtage (PromQL), évaluation de règles, et envoi d’alertes vers Alertmanager.
  • Alertmanager : le « post-traitement » des alertes (grouping, inhibition, routing, silences). Très vite, c’est ici que se joue votre fatigue d’alerte.
  • Grafana : visualisation et exploration, avec des dashboards souvent préchargés.
  • kube-state-metrics : métriques sur l’état déclaratif de Kubernetes (Deployments, Pods, Jobs…), indispensables pour des alertes de type « rollout bloqué », « replicas indisponibles ».
  • node-exporter : métriques système des nœuds (CPU, mémoire, FS, réseau). Utile… jusqu’à ce qu’un label ou un mountpoint exotique fasse exploser la cardinalité.

Deux définitions, pour qu’on parle de la même chose.

« Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud. » (Prometheus documentation)
Consulter la documentation Prometheus sur son aperçu général

« The Prometheus Operator provides Kubernetes native deployment and management of Prometheus and related monitoring components. » (prometheus-operator README)
Consulter le dépôt GitHub de Prometheus Operator

L’Operator introduit des CRDs (Custom Resource Definitions) comme ServiceMonitor, PodMonitor, PrometheusRule ou AlertmanagerConfig pour décrire le monitoring « comme du Kubernetes », au lieu de bricoler des fichiers de config montés en volume. En contrepartie, vous héritez aussi des problématiques classiques Kubernetes : sélecteurs, labels, RBAC, upgrades de CRDs, et… YAML qui dérive.

Si vous avez besoin d’un rappel sur l’architecture et les concepts (scrape, targets, règles, PromQL, Alertmanager, Grafana), gardez sous le coude ces deux ressources internes : Prometheus : architecture monitoring, PromQL, Alertmanager et Grafana et Prometheus monitoring : quickstart en 5 minutes et mise en production. Ici, on va parler déploiement Helm + Operator en production, donc avec de vrais choix d’architecture, de stockage, de sécurité et de gouvernance.

Choisir une architecture production (avant de lancer Helm comme un sauvage)

Première question qui fâche : un Prometheus par cluster, par environnement, par domaine applicatif, ou par équipe ? Par défaut, kube-prometheus-stack pousse vers « un Prometheus pour tout le cluster ». C’est simple, mais ça impose une gouvernance sur les labels, la cardinalité et l’accès aux données. En e-commerce ou en plateforme multi-tenant, on finit souvent avec un modèle hybride : un Prometheus « infra » (Kubernetes, nodes, ingress, base de données managée) + des Prometheus « apps » (teams produits), puis un agrégateur (Thanos/Mimir/Cortex) pour la rétention longue et la requête multi-cluster.

Pour vous aider à trancher, un tableau de compromis (volontairement pragmatique) :

Modèle Avantages Inconvénients typiques Quand ça marche bien
1 Prometheus / cluster Simple à opérer, moins de composants Gouvernance dure (labels, accès), risque de « bruit » et de cardinalité PME/mono-produit, cluster unique, équipes proches
Prometheus « infra » + Prometheus « apps » Isolation des domaines, limites de cardinalité par équipe Plus d’instances à maintenir, besoin de conventions communes Organisation produit avec plusieurs squads, microservices
Multi-cluster + backend central (remote_write / Thanos) Vue globale, longue rétention, requêtes cross-cluster Coût + complexité (objet storage, dédup, IAM), latence/egress Plusieurs clusters/regions, besoin d’historique 3–12 mois

Dans un contexte européen (et typiquement français), un point revient souvent : où sont stockées vos métriques long terme (objet storage, SaaS, etc.). Même si les métriques ne contiennent idéalement aucune donnée personnelle, la réalité est que certains labels ou valeurs peuvent devenir identifiants (ex. email, user_id, session_id). Dès que ça arrive, vous sortez du « simple monitoring technique » et vous vous rapprochez de considérations RGPD (Règlement (UE) 2016/679) : minimisation, limitation de finalité, et contrôle d’accès deviennent non négociables.

Deuxième question : qui consomme quoi, et avec quels droits ? Grafana en lecture pour tout le monde, Prometheus en accès restreint, Alertmanager réservé aux SRE/OPS… ou l’inverse si vous aimez les surprises. L’Operator aide, mais ne remplace pas la discipline : RBAC, isolation de namespace monitoring, et politique claire sur les métriques exposées. Si vos devs instrumentent tout avec un label user_id parce que « c’est utile », vous venez d’acheter un cluster de monitoring à 5 chiffres (et des requêtes PromQL qui time-out).

Mini-scénario très réaliste : une équipe ajoute un histogramme http_request_duration_seconds avec un label path qui inclut des IDs dynamiques (/orders/123456). Résultat : explosion du nombre de séries, Prometheus passe son temps à compacter, et Grafana devient lent. La correction est rarement « ajouter de la RAM » ; c’est plutôt corriger l’instrumentation (normaliser path, supprimer les labels instables, ou faire du relabeling).

Troisième point souvent sous-estimé : la stratégie « observabilité » globale. Prometheus est excellent pour les métriques, mais si vous voulez corréler traces et logs, vous allez vite vous tourner vers OpenTelemetry (instrumentation et pipeline de collecte/export). Pour l’architecture cible, vous pouvez croiser avec OpenTelemetry : unifier métriques, traces et logs pour l’observabilité. En pratique, beaucoup d’équipes gardent Prometheus « pour le temps réel et l’alerting » et déportent la longue rétention / corrélation vers une stack dédiée (à évaluer selon coûts, souveraineté, et besoins).

Déploiement Helm en production : les réglages qui évitent la panique à J+7

Le déploiement « propre » commence par des basiques (oui, ceux qu’on saute quand on est pressé). Pinner la version du chart, versionner un values.yaml, et éviter de laisser Helm gérer les CRDs n’importe comment. Typiquement :

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

kubectl create namespace monitoring

helm upgrade --install kps prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --version <PINNED_VERSION> \
  -f values.yaml

Avant même de parler tuning, vous gagnez énormément à appliquer une petite check-list « prod » :

  • Version pinning : chart + images (quand c’est possible dans values.yaml), et un changelog d’upgrade.
  • Namespace dédié (monitoring) + quotas/limites si votre cluster est multi-équipes.
  • Rollback plan : sauvegarde/restauration des dashboards Grafana, et stratégie sur les CRDs.
  • Validation en préprod : au minimum, vérifier que les targets scrappées et les alertes sont cohérentes (pas seulement que les Pods sont Running).

Le point « CRDs » mérite 30 secondes de lucidité. Les CRDs évoluent ; un upgrade de chart peut nécessiter un upgrade des CRDs avant le helm upgrade. Le chart fournit des CRDs, mais Helm ne les met pas automatiquement à jour lors d’un upgrade. L’idée est donc de gérer les CRDs explicitement (par exemple avec un workflow GitOps) plutôt que de découvrir en prod que ServiceMonitor a changé de schéma.

Une méthode simple et robuste consiste à prévisualiser les CRDs livrées par le chart puis à les appliquer comme des manifests séparés (workflow typique Argo CD / Flux) :

helm show crds prometheus-community/kube-prometheus-stack --version <PINNED_VERSION> > crds.yaml
kubectl apply -f crds.yaml

Si vous faites du déploiement continu, c’est le moment de relire Pipeline CI/CD Kubernetes : GitHub Actions et Argo CD étape par étape et, surtout, Sécurité CI/CD : protéger dépôts, runners, secrets et artefacts.

Ensuite, un values.yaml « prod-ready » se concentre sur trois axes : persistence, resources, scope. Exemple (extraits) :

prometheus:
  prometheusSpec:
    retention: 15d
    retentionSize: 80GB
    resources:
      requests:
        cpu: 2
        memory: 8Gi
      limits:
        cpu: 6
        memory: 16Gi
    storageSpec:
      volumeClaimTemplate:
        spec:
          storageClassName: fast-ssd
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 100Gi
    serviceMonitorSelectorNilUsesHelmValues: false
    serviceMonitorSelector:
      matchLabels:
        release: kps
    podMonitorSelectorNilUsesHelmValues: false
    podMonitorSelector:
      matchLabels:
        release: kps

grafana:
  enabled: true
  admin:
    existingSecret: grafana-admin
  persistence:
    enabled: true
    size: 10Gi

alertmanager:
  alertmanagerSpec:
    storage:
      volumeClaimTemplate:
        spec:
          storageClassName: fast-ssd
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 5Gi

Deux ajouts fréquents en production (et qui évitent des surprises) :

  • External labels (utiles si vous agrégez plusieurs Prometheus ou utilisez remote_write) :
  prometheus:
    prometheusSpec:
      externalLabels:
        cluster: "prod-paris-1"
        env: "prod"
  • Anti-affinité / PDB : pour éviter que Prometheus et/ou Alertmanager ne tombent lors d’une maintenance node.
  • kube-prometheus-stack permet de configurer affinités/tolerations selon composants ; l’objectif est d’étaler sur plusieurs nœuds et, si possible, plusieurs zones.

Le sarcasme utile : si vous n’activez pas la persistence de Prometheus, votre « monitoring » devient une loterie à chaque reschedule. Et si vous ne configurez pas explicitement les sélecteurs (serviceMonitorSelector et podMonitorSelector), vous risquez de scrapper des targets que vous n’aviez même pas identifiés comme existants (shadow IT, bonjour). En production, l’approche saine est : on whiteliste ce qu’on scrape, on tague proprement, et on documente.

Un compromis simple consiste à standardiser un label release: kps sur vos ServiceMonitor/PodMonitor, puis à configurer Prometheus pour ne sélectionner que ces objets. C’est lisible, auditable, et ça limite les « scrapes accidentels ».

Prometheus Operator au quotidien : ServiceMonitor, PodMonitor, PrometheusRule (et les pièges)

Le cœur du modèle Operator, c’est de transformer la découverte des targets en objets Kubernetes. ServiceMonitor cible des Service (label selectors) et définit les endpoints à scrapper. PodMonitor vise directement des Pods (utile pour certains workloads sans Service stable, ou des jobs). Conceptuellement, c’est de l’« infra as YAML »… avec les mêmes défauts que tout YAML : facile à copier-coller, facile à casser, et magique jusqu’au jour où vous devez débugger du relabeling.

Exemple minimaliste de ServiceMonitor (à adapter) :

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: checkout-api
  namespace: monitoring
  labels:
    release: kps
spec:
  namespaceSelector:
    matchNames: ["prod"]
  selector:
    matchLabels:
      app: checkout-api
  endpoints:
    - port: http-metrics
      path: /metrics
      interval: 30s
      scrapeTimeout: 10s
      honorLabels: true

Le piège n°1, c’est le multi-namespace : vous voulez scrapper des services dans prod, staging, ingress, etc. Si vous ouvrez trop large (any: true), vous vous exposez à scrapper « tout et n’importe quoi » ; si vous êtes trop strict, vous passez à côté d’un composant critique. En production, une approche qui marche bien est :

  • namespaceSelector.matchNames pour des namespaces explicitement autorisés,
  • conventions de labels sur les Service (ex. app, team, expose-metrics=true),
  • et revue de PR obligatoire dès qu’un ServiceMonitor change (comme une règle firewall).

Le piège n°2, c’est la collision de labels (d’où honorLabels à manier avec précaution). Si l’application exporte déjà un label job ou instance, vous pouvez casser vos dashboards/alertes ou rendre les séries ambiguës. Beaucoup d’équipes préfèrent laisser honorLabels: false et gérer finement les labels via relabeling (au prix d’un peu plus de complexité).

Le piège n°3, c’est le relabelling mal maîtrisé qui explose la cardinalité (par exemple en copiant des labels de pods volatils dans les séries). L’Operator vous donne des champs dédiés (relabelings, metricRelabelings) : utilisez-les pour supprimer ce qui est toxique avant stockage. Exemple (illustratif) : supprimer un label path trop dynamique au niveau des métriques applicatives.

endpoints:
  - port: http-metrics
    interval: 30s
    metricRelabelings:
      - action: labeldrop
        regex: path

Pour comprendre précisément ce que fait le relabeling (et les actions possibles), la doc Prometheus reste la référence : Documentation Prometheus sur la configuration du relabeling

Côté exploitation, une routine de debug simple (et très efficace) quand « Grafana ne voit rien » :

  • kubectl -n monitoring get servicemonitor,podmonitor
  • vérifier les labels du ServiceMonitor (ex. release: kps) vs les sélecteurs du Prometheus
  • kubectl -n prod get svc -l app=checkout-api -o wide (vos selectors matchent-ils vraiment ?)
  • côté Prometheus UI : Status → Targets (target down ? mauvais port ? mauvais path ? timeout ?)
  • regarder scrape_duration_seconds et up{job="..."} pour isoler réseau vs exposition applicative

Enfin, PrometheusRule : c’est ici que naissent les alertes… et les nuits blanches. Oui, kube-prometheus-stack fournit des centaines de règles « standard » (Kubernetes mixin, node, etc.). Non, ce n’est pas parce que c’est fourni que c’est adapté à votre contexte. En prod, vous devez trier : garder les alertes actionnables, ajouter des recording rules pour accélérer les requêtes lourdes, et surtout lier chaque alerte à un runbook (même un lien vers votre wiki interne). Sinon, vous fabriquez de l’alert fatigue à grande échelle.

Exemple minimal de règle (avec un runbook) :

apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: checkout-alerts
  namespace: monitoring
  labels:
    release: kps
spec:
  groups:
    - name: checkout.rules
      rules:
        - record: job:http_requests:rate5m
          expr: sum by (job) (rate(http_requests_total[5m]))
        - alert: CheckoutHigh5xxRate
          expr: |
            (sum(rate(http_requests_total{job="checkout-api",code=~"5.."}[5m]))
             /
             sum(rate(http_requests_total{job="checkout-api"}[5m])))
            > 0.02
          for: 10m
          labels:
            severity: page
            team: checkout
          annotations:
            summary: "Taux d'erreur 5xx élevé sur checkout-api"
            runbook_url: "https://wiki.example/runbooks/checkout-5xx"

Ici, la recording rule (job:http_requests:rate5m) sert autant la perf que la lisibilité : les dashboards et d’autres alertes peuvent la réutiliser, et vous évitez de recalculer des expressions lourdes partout.

Stockage, rétention, HA et performance : le TSDB n’est pas un sac sans fond

Prometheus stocke localement dans sa TSDB (Time Series Database). Elle est performante, mais elle n’a pas été conçue pour être votre data lake sur 18 mois. La rétention se dimensionne selon le taux d’ingestion (samples/s) et la cardinalité (séries actives). Règle empirique : si vous avez environ 100 000 samples/s constants et une rétention de 15 jours, prévoyez plutôt des centaines de Go, voire davantage selon la compression, le churn et les métriques. Ajoutez des labels à haute cardinalité, et vous passerez en mode « achat de SSD en urgence ». Oui, c’est vécu.

Plutôt que d’estimer à l’aveugle, appuyez-vous sur les métriques de Prometheus lui-même (elles sont faites pour ça) :

  • prometheus_tsdb_head_series : nombre de séries actives en mémoire (excellent proxy de la pression RAM)
  • prometheus_tsdb_head_samples_appended_total : ingestion
  • prometheus_tsdb_wal_fsync_duration_seconds et prometheus_tsdb_compaction_duration_seconds : stress disque/compaction
  • prometheus_engine_query_duration_seconds : latence requêtes (si ça grimpe, c’est souvent un mix cardinalité + manque de recording rules)

Le choix du stockage Kubernetes compte. Pour Prometheus et Alertmanager, un PVC RWO (ReadWriteOnce) sur une StorageClass SSD (NVMe si possible) est la voie normale. Sur ce point, les notions RWO/RWX, CSI et backends sont bien expliquées ici : Stockage distribué Kubernetes : PVC/CSI, RWO-RWX, Ceph et Longhorn. Concrètement : évitez un stockage réseau lent pour la WAL/compaction, surveillez les IOPS et testez les temps de recovery après restart (la WAL replay peut être longue si vous poussez la rétention et les volumes).

Deux recommandations « prod » souvent oubliées :

  • Dimensionner la rétention par besoins réels : l’alerting et l’investigation récente ont rarement besoin de 90 jours dans Prometheus. Souvent, 7–15 jours suffisent, et le reste part dans un backend long terme via remote_write/Thanos.
  • Mettre un plafond de taille (retentionSize) en plus du temps : ça évite le scénario « un exporteur part en vrille, et la TSDB remplit le disque ».

Pour la haute disponibilité, un Prometheus « singleton » est un SPOF (même si Kubernetes le redémarre, vous perdez des données pendant l’incident). Deux approches : (1) deux réplicas Prometheus qui scrappent les mêmes targets avec des externalLabels distincts et une couche de query/dedup (Thanos Querier, par exemple) ; (2) un Prometheus par zone + remote_write vers un backend HA (Mimir/Cortex/Thanos Receive). kube-prometheus-stack peut s’intégrer à Thanos via sidecar, mais ce n’est pas « gratuit » : il faut aussi penser au stockage objet, à la rétention longue, et au coût (FinOps, coucou).

Un point très opérationnel : si vous faites du multi-réplicas qui scrappent la même chose, attendez-vous à voir des alertes dupliquées si vous ne dédupliquez pas côté query/alerting. Dans ce type d’architecture, il est fréquent de :

  • garder l’évaluation d’alertes sur un seul chemin (ou dédupliquer via Thanos Ruler),
  • et réserver Prometheus « local » à la collecte + troubleshooting court terme.

Pour cadrer les coûts cloud et la rétention longue (objet storage, egress, compute), vous pouvez compléter avec FinOps : optimiser les coûts cloud avec reporting et budgétisation. En Europe, c’est souvent là que se joue le compromis : rétention longue utile, oui, mais pas au prix d’un stockage hors zone de résidence attendue ou d’une facture imprévisible.

Sécurité et gouvernance : parce que /metrics est souvent plus bavard que votre DSI

Le monitoring est une surface d’attaque. Les endpoints /metrics exposent parfois des URLs internes, des versions, des noms de tables, voire des identifiants (oui, certains exporteurs loggent des horreurs). Première brique : RBAC minimal pour l’Operator et Prometheus (cluster-wide si nécessaire, mais pas « admin parce que flemme »). Deuxième brique : NetworkPolicies pour limiter qui parle à qui. Troisième brique : chiffrement en transit (TLS) si vous exposez Grafana/Alertmanager via Ingress.

Deux pratiques qui évitent des fuites « bêtes » :

  • Considérer les métriques comme des données sensibles par défaut : dans beaucoup d’organisations, un endpoint de métriques donne une cartographie technique (versions, dépendances, topologie). Même sans « secret », c’est une mine d’or pour un attaquant.
  • Interdire les identifiants dans les labels (et, idéalement, dans les valeurs) : dès que vous mettez des identifiants stables dans la télémétrie, vous compliquez conformité, rétention, et contrôles d’accès. Si vous avez un doute, traitez-le comme potentiellement concerné par le RGPD (UE 2016/679) et appliquez minimisation + limitation de conservation.

Côté exposition, préférez un accès interne (VPN / mesh / bastion) plutôt qu’un Grafana public. Si vous devez passer par un Ingress, soignez la couche réseau : TLS moderne, rate limiting, auth. Vos décisions d’expo vont dépendre de votre contrôleur (Traefik, Gateway API, etc.) ; si vous êtes en pleine transition, l’article Ingress Kubernetes : migrer d’ingress-nginx archivé vers Traefik ou Gateway API évite quelques chausse-trappes. Et pour le durcissement plus global du cluster (PSA, policies, secrets, etc.), gardez ce plan d’action : Sécurisation Kubernetes : plan d’action 30 jours pour PME.

Sur Grafana, deux points concrets (trop souvent repoussés) :

  • Admin password via Secret (pas en clair dans values.yaml) + rotation.
  • SSO/OIDC (si possible) + désactiver/limiter l’auth anonyme. Grafana est souvent « la porte d’entrée » vers des infos sensibles.

Dernier sujet, très « prod » : la supply chain. kube-prometheus-stack est un chart populaire, donc une cible intéressante pour les erreurs de dépendances, images non pinées, ou upgrades surprises. Pinnez les versions (chart + images), scannez vos manifests, et faites passer les upgrades comme n’importe quel changement critique (environnement de préprod, canary, rollback plan).

Une discipline simple qui marche bien : à chaque upgrade, valider explicitement :

  • la compatibilité CRDs (schémas),
  • les changements de valeurs par défaut (chart release notes),
  • l’impact sur rules/alerts (certaines alertes changent de nom ou de logique),
  • et la consommation ressources (Prometheus/Grafana peuvent varier selon versions).

Si vous avez besoin d’un cadre de gouvernance sécurité plus transverse, l’approche « ne jamais faire confiance par défaut » fonctionne aussi pour la télémétrie : Zero-trust : déployer une architecture NIST SP 800-207 en entreprise.

Kévin DECQ-CAILLET, Directeur associé

Co-fondateur du studio de développement Les Vikings, mon cœur est voué aux paradoxes. Amour de la technologie et de l'Histoire, passion pour la gestion, le potager et le béhourd - si vous ne connaissez pas, ça vaut le détour. Accessoirement, une expérience de plus de 15 ans dans le domaine du numérique. Ce qui implique que j'en sais assez pour constater que j'ai encore beaucoup à apprendre.

Résumé de la politique de confidentialité

Ce site utilise des cookies afin que nous puissions vous fournir la meilleure expérience utilisateur possible. Les informations sur les cookies sont stockées dans votre navigateur et remplissent des fonctions telles que vous reconnaître lorsque vous revenez sur notre site Web et aider notre équipe à comprendre les sections du site que vous trouvez les plus intéressantes et utiles.