Supervision complète avec Prometheus & Grafana
Ceci met en place une stack Prometheus + Grafana qui scrape de vraies cibles, alerte sur des symptômes réellement ressentis par les utilisateurs plutôt que sur des métriques de ressources brutes comme le CPU% par défaut, et provisionne le dashboard en tant que code.
Scraper les cibles avec prometheus.yml
Prometheus va chercher les métriques plutôt que de les recevoir passivement, donc l'essentiel de la configuration se résume à : où regarder, et à quelle fréquence.
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: "checkout-api"
metrics_path: /metrics
kubernetes_sd_configs:
- role: pod
namespaces:
names: ["payments"]
relabel_configs:
- source_labels: [__meta_kubernetes_pod_label_app]
regex: checkout-api
action: keep
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- job_name: "node-exporter"
kubernetes_sd_configs:
- role: endpointslice
namespaces:
names: ["monitoring"]
relabel_configs:
# une cible par pod node-exporter, donc une par node
- source_labels: [__meta_kubernetes_service_name]
regex: node-exporter
action: keep
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
rule_files:
- "alert.rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]kubernetes_sd_configs fait de la découverte de service, pour ne pas avoir à maintenir une liste de cibles à la main à chaque fois qu'un pod est reprogrammé ailleurs, et le bloc relabel_configs filtre cette découverte pour ne garder que l'application ciblée, sans lui, Prometheus scrape tous les pods du namespace. L'action keep couplée à un regex sur un label de pod est le pattern que j'utilise en premier réflexe.
Le job node-exporter utilise la découverte pour une autre raison. Une cible statique node-exporter:9100 passe par le Service, qui répartit chaque scrape vers le pod node-exporter de son choix. Vous obtiendriez donc les métriques d'un node différent toutes les 15 secondes, sous une seule série. Le rôle endpointslice, lui, déplie le Service en ses pods sous-jacents, avec une cible par pod du DaemonSet, labellisée avec le node sur lequel il tourne. Prometheus a besoin de droits RBAC pour lister et surveiller les pods, les services et les EndpointSlices. Si vous utilisez le Prometheus Operator (kube-prometheus-stack), vous exprimeriez ces deux jobs sous forme de ServiceMonitor ou de PodMonitor plutôt que d'écrire les scrape_configs à la main, et le chart fournit déjà celui de node-exporter.
Choisir quoi alerter : les méthodes RED et USE
Le moyen le plus rapide de se retrouver avec de la fatigue d'alerte, c'est d'alerter sur tout ce que l'exporter expose par défaut. Deux frameworks corrigent ça :
- RED (pour les services orientés requêtes) : Rate (débit), Errors (erreurs), Duration (latence). Si un service traite des requêtes, ces trois métriques couvrent ce que l'utilisateur vit réellement.
- USE (pour les ressources) : Utilization, Saturation, Errors. À utiliser pour l'infrastructure sous-jacente : nodes, disques, files d'attente.
L'utilisation CPU est une métrique USE qui concerne le node. « Les requêtes timeout » est une métrique RED qui concerne l'expérience de l'utilisateur. Alertez sur le second, et servez-vous du premier comme contexte de debug une fois que vous êtes déjà en train d'intervenir.
Si vous ne pouvez pas terminer « un utilisateur est actuellement en train de subir X » à partir du nom d'une alerte, c'est probablement une métrique de cause, pas de symptôme. Gardez-la comme panneau de dashboard, pas comme page d'astreinte.
Une vraie règle d'alerte : taux d'erreur avec for:
Voici l'alerte que je déploie pour un service HTTP, construite autour du taux d'erreur de RED :
groups:
- name: checkout-api.rules
rules:
- alert: CheckoutAPIHighErrorRate
expr: |
sum(rate(http_requests_total{job="checkout-api", status=~"5.."}[5m]))
/
sum(rate(http_requests_total{job="checkout-api"}[5m]))
> 0.02
for: 10m
labels:
severity: page
team: payments
annotations:
summary: "Taux d'erreur de checkout-api au-dessus de 2% pendant 10 minutes"
description: "{{ $value | humanizePercentage }} des requêtes vers checkout-api renvoient un 5xx sur les 5 dernières minutes."
runbook_url: "https://runbooks.internal/checkout-api-errors"Le champ for: 10m est le plus sous-utilisé de l'alerting Prometheus. Sans lui, l'alerte se déclenche à l'instant où l'expression dépasse 2%, même pour un pic isolé de 30 secondes pendant un déploiement, puis se résout aussitôt. Exiger que la condition tienne dix minutes d'affilée filtre ce bruit transitoire tout en gardant la détection de ce qui est durable. J'ajuste la durée par alerte : 2-3 minutes pour quelque chose d'urgent, 10-15 minutes pour tout ce qui s'auto-résout par un comportement normal de retry/backoff.
for: échange de la vitesse de détection contre de la qualité de signal. Le régler trop court recrée de la fatigue d'alerte ; le régler trop long retarde une vraie page.
Routage et regroupement dans Alertmanager
Prometheus décide si une alerte doit se déclencher ; Alertmanager décide qui en entend parler et à quelle fréquence. Routez en vous basant sur les labels que vos règles définissent déjà :
route:
receiver: "default-slack"
group_by: ["alertname", "team"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers:
- severity="page"
- team="payments"
receiver: "payments-oncall-pagerduty"
continue: false
receivers:
- name: "default-slack"
slack_configs:
- api_url_file: /etc/alertmanager/secrets/slack-webhook-url
channel: "#alerts-general"
- name: "payments-oncall-pagerduty"
pagerduty_configs:
- routing_key_file: /etc/alertmanager/secrets/pagerduty-paymentsAlertmanager ne template pas son propre fichier de configuration, donc les secrets doivent arriver sous forme de fichiers. api_url_file et routing_key_file lisent l'URL du webhook Slack et la clé d'intégration PagerDuty au chargement, et aucune des deux ne se retrouve dans le dépôt. Placez-les dans un même Secret Kubernetes et montez-le en lecture seule sur /etc/alertmanager/secrets dans le pod Alertmanager. Chaque clé devient un fichier du même nom :
kubectl -n monitoring create secret generic alertmanager-secrets \
--from-literal=slack-webhook-url='https://hooks.slack.com/services/XXX/YYY/ZZZ' \
--from-literal=pagerduty-payments='<pagerduty-integration-key>'Dans une démarche GitOps, je ferais créer ce Secret par External Secrets (ou un sealed secret) depuis le gestionnaire de secrets du cloud, au lieu de lancer cette commande à la main. Avec le Prometheus Operator, déclarez-le plutôt sous spec.secrets de la ressource Alertmanager. L'opérateur le monte dans /etc/alertmanager/secrets/alertmanager-secrets/, adaptez donc les chemins.
group_by est ce qui empêche un déploiement défaillant de vous envoyer quinze notifications pour quinze pods du même rollout. group_wait laisse une courte fenêtre aux alertes liées pour arriver ensemble avant la première notification ; repeat_interval contrôle la fréquence de relance d'une alerte non résolue, qui doit être suffisamment longue pour que l'astreinte ne soit pas re-sollicitée toutes les quelques minutes.
Provisionner le dashboard Grafana en code
Le dashboard qui compte est celui qui est livré avec le service, pas celui que quelqu'un construit à la main dans l'interface. Le provisioning permet ça :
Grafana lit les sources de données et les fournisseurs de dashboards dans des dossiers distincts sous /etc/grafana/provisioning/, il faut donc deux fichiers. La source de données :
# /etc/grafana/provisioning/datasources/prometheus.yaml
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: trueEt le fournisseur de dashboards, qui indique à Grafana où trouver le JSON du dashboard :
# /etc/grafana/provisioning/dashboards/checkout-api.yaml
apiVersion: 1
providers:
- name: "checkout-api"
orgId: 1
folder: "Payments"
type: file
disableDeletion: false
updateIntervalSeconds: 30
allowUiUpdates: false
options:
path: /var/lib/grafana/dashboards/checkout-apiFixer l'uid de la source de données permet au JSON du dashboard de la référencer par un identifiant stable d'un environnement à l'autre. Je définis le dossier explicitement avec folder. L'alternative, foldersFromFilesStructure: true, déduit les dossiers de l'arborescence et exige que folder et folderUid restent vides. Il faut donc choisir l'un ou l'autre.
Associez ça à un fichier JSON de dashboard dans /var/lib/grafana/dashboards/checkout-api/ (généré une première fois depuis l'interface, puis versionné dans le dépôt), et le dashboard, sa source de données et les règles d'alerte se déploient tous ensemble via le même pipeline que le service. Sans cette étape, le dashboard dérive dès que quelqu'un retouche un panneau à la main, et on finit par ne plus lui faire confiance.
Comment les pièces s'assemblent
Pour conclure
Une configuration de scraping, une règle d'alerte, un arbre de routage, un dashboard provisionné. Ce qui distingue une équipe qui fait confiance à ses alertes de celle qui finit par couper le canal, c'est si chaque alerte répond à « un utilisateur est-il affecté en ce moment », soutenue par une fenêtre for: assez longue pour ignorer le bruit.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez