Aller au contenu
Outils medium

Prometheus sur Kubernetes

22 min de lecture

Ce guide couvre le déploiement de Prometheus sur Kubernetes avec kube-prometheus-stack. Vous apprendrez le pattern Operator, les ServiceMonitors, et comment faire remonter les métriques de vos applications.

  • Installer kube-prometheus-stack avec Helm, version épinglée
  • Déclarer une cible de collecte avec un ServiceMonitor ou un PodMonitor
  • Écrire une alerte sous forme de PrometheusRule
  • Adapter les valeurs Helm pour un déploiement de production (stockage, HA, secrets)
  • Diagnostiquer une cible absente des targets Prometheus

Pourquoi un Operator (et pas juste un Deployment) ?

Section intitulée « Pourquoi un Operator (et pas juste un Deployment) ? »

Sur Kubernetes, on pourrait déployer Prometheus "classiquement" (Deployment + ConfigMap). Mais ça pose des problèmes :

ProblèmeAvec ConfigMapAvec Operator
Ajouter une cibleÉditer ConfigMap + reloadCréer un ServiceMonitor
Multi-équipeConflits sur le même fichierChaque équipe gère ses CRDs
ValidationAucune (YAML brut)Schéma Kubernetes
DécouverteManuelleAutomatique (labels)

Le pattern Operator : au lieu de modifier un fichier de config central, chaque équipe déclare un ServiceMonitor (CRD Kubernetes). L'Operator génère automatiquement la config Prometheus.

Un helm install déploie six composants d'un coup, et c'est utile de savoir lesquels vous pourriez désactiver. Les quatre premiers forment le coeur fonctionnel. Les deux derniers sont des sources de métriques : Node Exporter remonte l'état des machines, kube-state-metrics l'état des objets Kubernetes. Si vous avez déjà l'un des deux dans le cluster, laissez-le et désactivez celui de la stack, sinon vous collecterez les mêmes séries deux fois.

ComposantRôle
Prometheus OperatorSurveille les CRDs, génère la config
PrometheusCollecte et stockage (TSDB)
AlertmanagerRouting des alertes
GrafanaDashboards préconfigurés
Node ExporterMétriques système des nodes
kube-state-metricsÉtat des objets K8s (pods, deployments…)

Architecture kube-prometheus-stack : Operator, Prometheus, Alertmanager, Grafana, Node Exporter et kube-state-metrics

Les quatre étapes ci-dessous partent d'un cluster vierge et se terminent par une vérification qui prouve que la collecte fonctionne, pas seulement que les pods démarrent. C'est la distinction importante : un Prometheus Running qui ne scrape rien reste un Prometheus inutile. La dernière étape compte les cibles actives pour lever ce doute.

  1. Ajouter le repository Helm

    Fenêtre de terminal
    helm repo add prometheus-community \
    https://prometheus-community.github.io/helm-charts
    helm repo update
  2. Installer la stack (version pinnée)

    Fenêtre de terminal
    helm install prometheus prometheus-community/kube-prometheus-stack \
    --namespace monitoring \
    --create-namespace \
    --version 87.10.1
  3. Vérifier le déploiement (tous les pods Running)

    Fenêtre de terminal
    kubectl get pods -n monitoring -w

    Attendez ~2-3 minutes. Tous les pods doivent être Running :

    NAME READY STATUS
    alertmanager-prometheus-kube-prometheus-alertmanager-0 2/2 Running
    prometheus-grafana-xxx 3/3 Running
    prometheus-kube-prometheus-operator-xxx 1/1 Running
    prometheus-kube-state-metrics-xxx 1/1 Running
    prometheus-prometheus-kube-prometheus-prometheus-0 2/2 Running
    prometheus-prometheus-node-exporter-xxx 1/1 Running
  4. Vérification rapide : Prometheus fonctionne

    Fenêtre de terminal
    kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090 &
    curl -s localhost:9090/api/v1/targets | jq '.data.activeTargets | length'
    # Attendu : un nombre > 10 (les targets K8s)

Aucune de ces interfaces n'est exposée hors du cluster par défaut, ce qui est volontaire : Prometheus et Alertmanager n'ont pas d'authentification native et Grafana démarre avec un mot de passe connu. kubectl port-forward ouvre un tunnel local le temps de la consultation, sans rien publier. Notez le 3000:80 pour Grafana : le Service écoute sur le port 80, pas sur 3000.

Fenêtre de terminal
# Prometheus
kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090
# Grafana (mot de passe par défaut: prom-operator)
kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80
# Alertmanager
kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-alertmanager 9093:9093

L'Operator surveille cinq Custom Resource Definitions, et la colonne « Qui le crée » est celle qui compte pour organiser le travail. Les trois premières appartiennent aux équipes applicatives et vivent dans leurs dépôts, à côté du code. Les deux dernières décrivent les instances elles-mêmes et restent aux mains de l'équipe plateforme, parce qu'une erreur à ce niveau affecte toute la collecte.

CRDRôleQui le crée
ServiceMonitor"Scrappe ce Service"Équipe app
PodMonitor"Scrappe ces Pods" (sans Service)Équipe app
PrometheusRuleAlertes et recording rulesÉquipe app/SRE
PrometheusInstance PrometheusOps/Platform
AlertmanagerInstance AlertmanagerOps/Platform

Workflow typique :

  1. L'équipe platform installe kube-prometheus-stack
  2. Chaque équipe app crée ses ServiceMonitors + PrometheusRules
  3. L'Operator génère la config automatiquement

Créons un ServiceMonitor pour une application qui expose /metrics.

Deux détails de ce manifeste conditionnent la suite. Le conteneur déclare un port nommé metrics, et le Service reprend ce même nom : c'est par ce nom, et non par le numéro, que le ServiceMonitor désignera la cible. Ensuite, le Service porte le label app: my-app, celui que le ServiceMonitor ira chercher. Sans ces deux éléments alignés, la collecte ne démarrera jamais.

app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
labels:
app: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.4.2 # jamais :latest, sinon le rollback est impossible
ports:
- containerPort: 8080
name: http
- containerPort: 9090
name: metrics
---
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: default
labels:
app: my-app
spec:
ports:
- port: 8080
name: http
- port: 9090
name: metrics
selector:
app: my-app

Le ServiceMonitor fait le lien entre deux mondes de labels, et c'est là que se trompent la plupart des débutants. Le spec.selector désigne le Service à scraper, tandis que le label release: prometheus de metadata sert à l'Operator pour décider si ce ServiceMonitor le concerne. Les deux sont indispensables et n'ont rien à voir l'un avec l'autre.

servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app
namespace: default # Peut être dans le namespace de l'app
labels:
release: prometheus # OBLIGATOIRE, voir l'encadré ci-dessous
spec:
selector:
matchLabels:
app: my-app # Matche le label du Service
endpoints:
- port: metrics # Nom du port dans le Service
interval: 15s
path: /metrics

Vérifiez dans cet ordre. La première commande confirme seulement que l'objet existe dans etcd, ce qui ne prouve rien sur la collecte. C'est la page Status puis Targets de Prometheus qui fait foi : votre cible doit y apparaître avec l'état UP. Une cible absente signifie que l'Operator a ignoré le ServiceMonitor ; une cible présente mais DOWN signifie qu'il l'a bien prise en compte mais que le point de collecte ne répond pas.

Fenêtre de terminal
# ServiceMonitor créé ?
kubectl get servicemonitor -A
# Visible dans Prometheus Targets ?
kubectl port-forward -n monitoring svc/prometheus-kube-prometheus-prometheus 9090:9090
# Puis : Status → Targets → cherchez "my-app"

Certains pods n'ont pas de Service devant eux : les Jobs et CronJobs n'en ont pas besoin, et les pods pilotés par un contrôleur maison non plus. Le PodMonitor sélectionne alors directement les pods par leurs labels. La différence de structure avec le ServiceMonitor tient en un champ : podMetricsEndpoints remplace endpoints. Attention avec namespaceSelector: any: true, qui étend la sélection à tout le cluster et peut faire exploser le nombre de cibles si le label choisi est trop courant.

podmonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: batch-jobs
namespace: monitoring
labels:
release: prometheus
spec:
selector:
matchLabels:
type: batch-job
namespaceSelector:
any: true # Tous les namespaces
podMetricsEndpoints:
- port: metrics
interval: 30s

Les alertes vivent elles aussi dans des objets Kubernetes, ce qui permet à chaque équipe de livrer ses règles avec son application. Deux champs pilotent le comportement : expr est la requête PromQL évaluée en continu, for est la durée pendant laquelle elle doit rester vraie avant que l'alerte parte. Ce for est votre principal filtre anti-bruit : sans lui, un pic de deux secondes réveille l'astreinte. Le champ runbook_url n'a pas d'effet technique mais change tout pour la personne réveillée à trois heures du matin.

prometheusrule.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: my-app-alerts
namespace: monitoring
labels:
release: prometheus # Même piège que le ServiceMonitor
spec:
groups:
- name: my-app
rules:
- alert: MyAppDown
expr: up{job="my-app"} == 0
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "{{ $labels.instance }} is down"
runbook_url: "https://wiki/runbooks/my-app-down"
- alert: MyAppHighErrorRate
expr: |
sum(rate(http_requests_total{job="my-app", status=~"5.."}[5m]))
/ sum(rate(http_requests_total{job="my-app"}[5m])) > 0.05
for: 3m
labels:
severity: warning
team: backend
annotations:
summary: "Error rate > 5%"

Ce fichier de valeurs regroupe les réglages qui distinguent un déploiement de test d'un déploiement durable. Trois blocs méritent votre attention : le storageSpec, sans lequel toutes les métriques disparaissent au redémarrage du pod ; les trois lignes ...NilUsesHelmValues: false, qui suppriment le filtre sur le label release et font scraper tous les ServiceMonitors du cluster ; et le couple retention / retentionSize, où c'est la première limite atteinte qui déclenche la purge.

values.yaml
# Prometheus
prometheus:
prometheusSpec:
retention: 15d
retentionSize: 50GB
replicas: 2 # HA
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi
# IMPORTANT : scraper tous les ServiceMonitors (pas seulement release=prometheus)
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
# Alertmanager
alertmanager:
alertmanagerSpec:
replicas: 3
storage:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
config:
global:
resolve_timeout: 5m
route:
receiver: 'slack-default'
group_by: ['alertname', 'namespace', 'service']
routes:
- matchers:
- severity="critical"
receiver: 'pagerduty'
receivers:
- name: 'slack-default'
slack_configs:
- api_url: 'https://hooks.slack.com/services/...'
channel: '#alerts'
- name: 'pagerduty'
pagerduty_configs:
- routing_key: '$PAGERDUTY_KEY'
# Grafana
grafana:
adminPassword: 'VotreMotDePasseSecurise'
persistence:
enabled: true
size: 10Gi
# Node Exporter sur TOUS les nodes (y compris taints)
prometheus-node-exporter:
tolerations:
- operator: Exists
# kube-state-metrics : labels custom sur les pods
kube-state-metrics:
metricLabelsAllowlist:
- pods=[app,version,team]
- deployments=[app,version]
Fenêtre de terminal
helm upgrade prometheus prometheus-community/kube-prometheus-stack \
-n monitoring \
-f values.yaml \
--version 87.10.1

Un dernier réglage mérite d'être isolé, car c'est celui qui débloque le plus souvent une collecte incomplète. Par défaut, l'Operator n'accepte que les ServiceMonitors portant le label release: prometheus. En passant serviceMonitorSelectorNilUsesHelmValues à false dans prometheus.prometheusSpec, ce filtre disparaît et tous les ServiceMonitors du cluster sont pris en compte. Le confort a une contrepartie : n'importe quelle équipe peut alors ajouter des cibles à votre Prometheus, et la cardinalité vous échappe.

prometheus:
prometheusSpec:
serviceMonitorSelectorNilUsesHelmValues: false

Sans que vous ayez rien à déclarer, la stack collecte déjà deux familles de métriques. La distinction entre les deux est la première chose à comprendre pour ne pas chercher au mauvais endroit : kube-state-metrics décrit l'état souhaité et constaté des objets Kubernetes, cAdvisor mesure la consommation réelle des conteneurs. Une question sur un Deployment se répond avec la première source, une question sur la mémoire consommée avec la seconde.

Ces métriques décrivent ce que l'API Kubernetes déclare, pas ce que les machines consomment. Elles répondent à des questions du type « combien de replicas sont disponibles » ou « ce pod redémarre-t-il en boucle ». C'est la source à interroger pour surveiller l'orchestration ; pour la consommation réelle, il faut les métriques du bloc suivant.

MétriqueDescription
kube_pod_status_phasePhase du pod (Pending, Running…)
kube_pod_container_status_restarts_totalRestarts
kube_deployment_status_replicas_availableReplicas ready
kube_node_status_conditionÉtat des nodes

Ici on mesure la consommation observée, relevée par cAdvisor intégré au kubelet. Le suffixe _total des deux premières métriques signale un compteur qui ne fait que croître : sa valeur brute n'a aucun intérêt, il faut toujours lui appliquer rate() pour obtenir une consommation par seconde. container_memory_usage_bytes est en revanche une jauge, lisible telle quelle.

MétriqueDescription
container_cpu_usage_seconds_totalCPU par container
container_memory_usage_bytesMémoire par container
container_network_receive_bytes_totalRéseau

Ces cinq requêtes couvrent les questions les plus fréquentes en exploitation. Le filtre container!="" des requêtes de consommation n'est pas décoratif : il écarte les séries agrégées au niveau du pod, sans lequel vous compteriez chaque ressource deux fois. Les trois dernières requêtes renvoient un résultat seulement quand quelque chose ne va pas, ce qui les rend directement réutilisables comme expressions d'alerte.

# Pods en CrashLoopBackOff
kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} > 0
# CPU par pod (cores)
sum(rate(container_cpu_usage_seconds_total{container!=""}[5m])) by (pod, namespace)
# Mémoire par namespace (GB)
sum(container_memory_usage_bytes{container!=""}) by (namespace) / 1e9
# Deployments avec replicas manquants
kube_deployment_status_replicas_available < kube_deployment_spec_replicas
# Nodes NotReady
kube_node_status_condition{condition="Ready", status="true"} == 0

kube-prometheus-stack inclut des règles d'alerte préconfigurées :

AlerteSévéritéDescription
KubeNodeNotReadyWarningNode non prêt
KubePodCrashLoopingWarningPod en crash loop
KubeDeploymentReplicasMismatchWarningReplicas manquants
KubePersistentVolumeFillingUpCriticalPV bientôt plein
PrometheusNotConnectedToAlertmanagersWarningPrometheus → Alertmanager cassé
TargetDownWarningTarget non scrapable
Fenêtre de terminal
# Lister toutes les règles
kubectl get prometheusrules -n monitoring
# Voir le détail
kubectl get prometheusrules -n monitoring \
prometheus-kube-prometheus-kubernetes-apps -o yaml

Tout ne tourne pas dans le cluster : bases de données historiques, équipements réseau, serveurs physiques. Pour ces cibles, il n'existe ni Service ni Pod à sélectionner, donc ni ServiceMonitor ni PodMonitor. La solution passe par un bloc de configuration Prometheus classique, injecté depuis un Secret que l'Operator concatène à la configuration qu'il génère. Le format attendu à l'intérieur du Secret est celui de la section scrape_configs de Prometheus.

external-targets.yaml
apiVersion: v1
kind: Secret
metadata:
name: additional-scrape-configs
namespace: monitoring
stringData:
additional-scrape-configs.yaml: |
- job_name: 'external-nodes'
static_configs:
- targets:
- 'external-db.example.com:9104'
- 'legacy-server.example.com:9100'

Référencez dans values.yaml :

prometheus:
prometheusSpec:
additionalScrapeConfigsSecret:
enabled: true
name: additional-scrape-configs
key: additional-scrape-configs.yaml

Ces cinq situations ont un point commun qui les rend pénibles : rien n'échoue visiblement. Aucun objet n'est rejeté, aucun pod ne redémarre, la cible est simplement absente des targets. Le réflexe qui les couvre presque toutes est de comparer, dans l'ordre, le label release, le namespaceSelector, puis le nom du port dans le Service.

PiègeSymptômeSolution
Label release manquantServiceMonitor ignoréAjouter release: prometheus
Namespace non surveilléTarget absentVérifier namespaceSelector
Port name incorrectScrape timeoutMatcher le nom du port Service
Selector trop largeExplosion targetsÊtre plus spécifique
Pas de PVCDonnées perdues au restartConfigurer storageSpec

Quand une cible manque, l'information de référence n'est ni dans Prometheus ni dans vos manifestes, mais dans la configuration générée par l'Operator. La commande donnée plus bas la décompresse et l'affiche : si votre job n'y figure pas, le problème est en amont, au niveau de la sélection des ServiceMonitors. S'il y figure, le problème est réseau ou applicatif.

SymptômeCommande de debug
ServiceMonitor pas scrapékubectl get servicemonitor -A -o yaml | grep -A3 labels
Prometheus OOMkubectl top pod -n monitoring + réduire cardinalité
Config généréeVoir ci-dessous
Alertes pas envoyéeskubectl logs -n monitoring alertmanager-...

Voir la config Prometheus générée :

Fenêtre de terminal
kubectl get secret -n monitoring \
prometheus-prometheus-kube-prometheus-prometheus \
-o jsonpath='{.data.prometheus\.yaml\.gz}' | base64 -d | gunzip | head -100

Logs utiles :

Fenêtre de terminal
# Operator (génère la config)
kubectl logs -n monitoring deploy/prometheus-kube-prometheus-operator
# Prometheus
kubectl logs -n monitoring prometheus-prometheus-kube-prometheus-prometheus-0 -c prometheus
# Alertmanager
kubectl logs -n monitoring alertmanager-prometheus-kube-prometheus-alertmanager-0
  • Operator pattern : les CRDs remplacent les fichiers de config
  • ServiceMonitor : déclare un Service à scraper
  • Label release: prometheus : obligatoire par défaut (le piège #1)
  • Dashboards inclus : observabilité K8s prête à l'emploi
  • HA : replicas: 2 pour Prometheus, replicas: 3 pour Alertmanager
  • Stockage : toujours configurer storageSpec en production

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn