Mis à jour le 1er novembre 2026. Article réécrit autour de la surveillance serveur : signaux à suivre, sondes Prometheus, règles d’alerte vérifiées avec
promtool, watchdog Python avec confirmation et retour à la normale, valeurs par défaut de l’autoscaler Kubernetes et rôle encadré de l’IA. Il regroupe désormais notre article sur la création d’un watchdog serveur.
Une saturation découverte par un client est une saturation découverte trop tard. La surveillance d’un serveur sert à voir venir le problème, à prévenir la bonne personne et à lui dire quoi vérifier. Elle ne demande ni Kubernetes ni intelligence artificielle au départ. Elle demande des sondes bien choisies, des seuils observés et une procédure écrite.
Ce guide couvre trois tailles d’infrastructure : un serveur unique, plusieurs instances derrière un répartiteur, une plateforme qui ajoute des instances seule. Il vaut pour un site PrestaShop ou WordPress comme pour une application d’inférence IA. Le diagnostic d’une boutique saturée est traité dans notre guide du serveur PrestaShop surchargé, les réglages de PHP, d’OPcache et de la base dans notre guide de performance serveur d’un CMS.
Ce qu’il faut surveiller
Le livre Site Reliability Engineering de Google retient quatre signaux : la latence, le trafic, les erreurs et la saturation. Sur un serveur web, ils se traduisent ainsi :
| Signal | Sur un CMS | Sur une application IA |
|---|---|---|
| Latence | Temps de réponse des pages clés, du panier, du paiement | Durée d’une inférence, délai du premier jeton |
| Trafic | Requêtes par seconde dans le journal Nginx | Requêtes par seconde, longueur de la file |
| Erreurs | Réponses 5xx, échecs de la sonde externe | Réponses 5xx, délais dépassés |
| Saturation | File d’attente PHP-FPM, mémoire, disque, charge | Mémoire vidéo du GPU, file d’attente |
Ajoutez deux contrôles qui ne relèvent d’aucun signal mais causent des pannes bêtes : l’espace disque et la date d’expiration du certificat TLS.
Choisir l’outillage selon la taille
- Un serveur : une sonde externe sur les pages clés, le watchdog décrit plus bas et un redémarrage du service par systemd en cas de plantage.
- Plusieurs instances : Prometheus pour collecter, Alertmanager pour notifier, Grafana pour afficher, et un répartiteur Nginx ou HAProxy.
- Mise à l’échelle automatique : un
HorizontalPodAutoscalerKubernetes ou les groupes d’auto-scaling d’un fournisseur cloud.
Une sonde placée sur le serveur surveillé tombe avec lui. Gardez au moins une vérification externe, depuis une autre machine ou un service tiers.
Les sondes
node_exporter expose les métriques du système (charge, mémoire, disque, réseau) sur le port 9100. blackbox_exporter interroge des URL en HTTP, HTTPS, DNS, TCP, ICMP ou gRPC ; il renvoie notamment probe_success, probe_http_status_code et probe_ssl_earliest_cert_expiry. Pour un CMS, deux pages de statut complètent le tableau : stub_status de Nginx et la page de statut de PHP-FPM. Réservez-les à l’adresse locale :
server {
listen 127.0.0.1:8080;
location = /nginx-status {
stub_status;
allow 127.0.0.1;
deny all;
}
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_param SCRIPT_NAME /fpm-status;
fastcgi_param SCRIPT_FILENAME /fpm-status;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
}
Le module stub_status n’est pas compilé par défaut dans Nginx : vérifiez sa présence avec nginx -V. La page PHP-FPM suppose pm.status_path = /fpm-status dans le pool ; depuis PHP 8.1, elle sait répondre au format OpenMetrics (?openmetrics). Le port 8080 local est un exemple.
Prometheus et les règles d’alerte
# prometheus.yml (extrait)
rule_files:
- rules.yml
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['web1.interne:9100', 'web2.interne:9100']
- job_name: 'blackbox'
metrics_path: /probe
params:
module: [http_2xx]
static_configs:
- targets:
- https://www.votre-site.example/
- https://www.votre-site.example/panier
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: 127.0.0.1:9115
Les règles suivantes couvrent la disponibilité, le disque et le certificat :
# rules.yml
groups:
- name: serveur-web
rules:
- alert: SiteInjoignable
expr: probe_success{job="blackbox"} == 0
for: 3m
keep_firing_for: 5m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} ne répond plus"
description: "Vérifier le journal d'erreurs Nginx et la page de statut PHP-FPM."
- alert: DisquePleinSous24h
expr: predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 24 * 3600) < 0
for: 30m
labels:
severity: warning
annotations:
summary: "Disque / de {{ $labels.instance }} plein dans moins de 24 h au rythme actuel"
description: "Chercher le dossier qui grossit (journaux, sauvegardes, cache) avant toute suppression."
- alert: CertificatExpireBientot
expr: probe_ssl_earliest_cert_expiry{job="blackbox"} - time() < 14 * 86400
for: 1h
labels:
severity: warning
annotations:
summary: "Certificat de {{ $labels.instance }} expire dans moins de 14 jours"
description: "Vérifier le renouvellement automatique (certbot, panneau de l'hébergeur)."
predict_linear projette la valeur par régression linéaire sur la fenêtre indiquée : l’alerte prévient avant que le disque soit plein, pas après. Validez les fichiers avant de recharger Prometheus :
promtool check config prometheus.yml
promtool check rules rules.yml
Des alertes qui méritent un réveil
for impose que la condition dure avant le déclenchement : un pic bref reste à l’état « pending ». keep_firing_for maintient l’alerte un moment après la fin de la condition, ce qui évite les alertes qui clignotent. Le livre de Google pose une règle simple : chaque alerte qui réveille quelqu’un doit appeler une action et demander du jugement. Si la réponse est mécanique, ce n’est pas une alerte, c’est une tâche à automatiser ou à supprimer.
En pratique, distinguez warning (à traiter dans la journée) et critical (intervention immédiate), joignez à chaque alerte la première vérification à faire et relisez les seuils chaque mois. Alertez sur les erreurs et la file d’attente plutôt que sur le seul processeur.
Un watchdog pour un serveur unique
Sur un petit serveur, Prometheus peut être surdimensionné. Ce script Python, limité à la bibliothèque standard, mesure quatre sondes, exige deux dépassements consécutifs avant d’alerter, signale le retour à la normale et ne modifie jamais le serveur.
#!/usr/bin/env python3
# watchdog.py : sondes en lecture seule, alertes adressées à un humain
import json, logging, os, re, shutil, urllib.error, urllib.request
from collections import deque
from pathlib import Path
URL_SITE = "https://votre-site.example/"
CHEMIN_DISQUE = "/"
JOURNAL_ACCES = "/var/log/nginx/access.log"
DOSSIER = Path(__file__).resolve().parent
FICHIER_JOURNAL = DOSSIER / "watchdog.log"
FICHIER_ETAT = DOSSIER / "etat.json"
# Seuils indicatifs : à remplacer après une à deux semaines d'observation
SEUIL_DISQUE = 85 # pourcentage utilisé
SEUIL_CHARGE = 1.5 # charge sur 1 minute, par cœur
SEUIL_5XX = 20 # réponses 5xx dans les dernières lignes
LIGNES_ANALYSEES = 5000
CONFIRMATIONS = 2 # dépassements consécutifs avant alerte
logging.basicConfig(filename=FICHIER_JOURNAL, encoding="utf-8", level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s")
def envoyer(message):
logging.warning(message)
url = os.environ.get("ALERT_WEBHOOK_URL")
if not url:
return
requete = urllib.request.Request(
url, data=json.dumps({"text": message}).encode("utf-8"),
headers={"Content-Type": "application/json"})
try:
urllib.request.urlopen(requete, timeout=10)
except OSError as exc:
logging.error("Envoi impossible : %s", exc)
def sonde_http():
try:
with urllib.request.urlopen(URL_SITE, timeout=10) as reponse:
return reponse.status
except urllib.error.HTTPError as exc:
return exc.code
except OSError:
return None
def sonde_disque():
usage = shutil.disk_usage(CHEMIN_DISQUE)
return usage.used / usage.total * 100
def sonde_charge():
return os.getloadavg()[0] / (os.cpu_count() or 1)
def sonde_5xx():
try:
with open(JOURNAL_ACCES, encoding="utf-8", errors="replace") as fichier:
lignes = deque(fichier, maxlen=LIGNES_ANALYSEES)
except OSError as exc:
logging.error("Journal d'accès illisible : %s", exc)
return None
return sum(1 for ligne in lignes if re.search(r'" 5\d\d ', ligne))
def charger_etat():
try:
return json.loads(FICHIER_ETAT.read_text(encoding="utf-8"))
except (OSError, ValueError):
return {}
def main():
etat = charger_etat()
statut, disque = sonde_http(), sonde_disque()
charge, erreurs = sonde_charge(), sonde_5xx()
controles = {
"site": (statut is None or statut >= 400,
f"Site en erreur (code {statut}) : {URL_SITE}"),
"disque": (disque >= SEUIL_DISQUE,
f"Disque {CHEMIN_DISQUE} utilisé à {disque:.0f} % (seuil {SEUIL_DISQUE} %)"),
"charge": (charge >= SEUIL_CHARGE,
f"Charge de {charge:.2f} par cœur (seuil {SEUIL_CHARGE})"),
"5xx": (erreurs is not None and erreurs >= SEUIL_5XX,
f"{erreurs} réponses 5xx dans les {LIGNES_ANALYSEES} dernières lignes"),
}
for nom, (anormal, message) in controles.items():
precedent = etat.get(nom, {"compteur": 0, "alerte": False})
compteur = precedent["compteur"] + 1 if anormal else 0
alerte = precedent["alerte"]
if compteur >= CONFIRMATIONS and not alerte:
envoyer("ALERTE " + message)
alerte = True
elif not anormal and alerte:
envoyer(f"Retour à la normale : {nom}")
alerte = False
etat[nom] = {"compteur": compteur, "alerte": alerte}
FICHIER_ETAT.write_text(json.dumps(etat), encoding="utf-8")
logging.info("Relevé : HTTP %s, disque %.0f %%, charge %.2f, 5xx %s",
statut, disque, charge, erreurs)
if __name__ == "__main__":
main()
Le script suppose le format de journal combiné par défaut de Nginx, Python 3.9 ou plus récent, et Linux (os.getloadavg). Planifiez-le toutes les cinq minutes, sous un utilisateur dédié du groupe adm, qui lit les journaux Nginx sous Debian et Ubuntu :
*/5 * * * * cd /home/supervision/watchdog && ALERT_WEBHOOK_URL='https://hooks.example.com/REMPLACER' /usr/bin/python3 watchdog.py
L’URL du webhook donne accès à votre canal : ne la publiez pas. Sans elle, les alertes restent dans watchdog.log et n’atteignent personne. Pour tester, abaissez SEUIL_DISQUE à 1, lancez le script deux fois, puis rétablissez la valeur. Confiez la rotation du journal à logrotate.
La procédure écrite à l’avance
| Alerte | Première vérification | Décision humaine |
|---|---|---|
| Site en erreur | curl -I sur l’URL, journal d’erreurs Nginx | Redémarrage ou retour arrière après diagnostic |
| Disque presque plein | df -h, du -sh /var/log/* | Suppression validée, après sauvegarde |
| Charge élevée | ps trié par CPU, statut PHP-FPM | Arrêt d’un processus en connaissance de cause |
| 5xx en hausse | Dernier déploiement, pic de trafic | Correction testée d’abord en préproduction |
Notez l’heure, l’intervenant, l’action et son résultat. Pour relier une erreur 502 ou 504 à sa cause, voir notre guide d’analyse des logs Nginx.
Répartir la charge entre plusieurs instances
upstream ia_backend {
least_conn;
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://ia_backend;
proxy_read_timeout 120s;
proxy_buffering off; # pour le streaming de jetons ou les Server-Sent Events
}
}
least_conn convient quand la durée des requêtes varie, comme pour une inférence. proxy_read_timeout vaut 60 secondes par défaut : augmentez-le si vos inférences durent plus longtemps. proxy_buffering est actif par défaut, ce qui retarde un flux de jetons. Nginx open source détecte les pannes de façon passive ; HAProxy sonde activement un point de contrôle :
backend ia_backend
balance leastconn
option httpchk GET /healthz
server app1 192.168.1.10:8080 check
server app2 192.168.1.11:8080 check
/healthz ne doit répondre avec succès qu’une fois le modèle chargé, sinon une instance en démarrage reçoit du trafic et renvoie des erreurs. Pour une application IA, nvidia-smi -l 1 affiche la mémoire et l’utilisation du GPU ; l’exporteur DCGM de NVIDIA les publie pour Prometheus. Le choix entre CPU, GPU et API externe est traité dans notre guide d’hébergement IA.
L’auto-scaling : des règles, pas de l’intelligence
L’auto-scaling applique une règle écrite par un humain. Sur Kubernetes, le HorizontalPodAutoscaler calcule ceil(répliques actuelles × valeur mesurée / valeur cible) :
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 75
behavior:
scaleDown:
stabilizationWindowSeconds: 300
Le contrôleur évalue toutes les 15 secondes par défaut. Il ignore les écarts inférieurs à 10 %, tolérance réglable par HPA, stable depuis Kubernetes 1.37. Par défaut, il ajoute des pods sans délai mais attend 300 secondes avant d’en retirer. Le pourcentage CPU se calcule par rapport à resources.requests.cpu : sans cette valeur, l’autoscaler n’agit pas. Le metrics-server doit être installé.
Le CPU est un mauvais signal pour une application limitée par le GPU. Scaler sur la mémoire GPU ou la longueur de file demande des métriques personnalisées (Prometheus Adapter, KEDA). Fixez toujours maxReplicas et une alerte de facturation, et chiffrez trois scénarios : coût au minimum de répliques, coût moyen, coût au maximum prolongé. Pour un CMS, l’auto-scaling exige aussi des sessions, un cache et des fichiers partagés entre instances ; notre guide PrestaShop décrit ce partage.
Le rôle encadré de l’IA
L’IA a deux usages défendables en supervision. Le premier est la détection d’anomalies : un modèle apprend le profil habituel des métriques et signale un écart. Elle suppose un historique régulier et signale l’inhabituel, pas la panne. Le second est la synthèse : un modèle de langage regroupe les alertes d’une journée de watchdog.log et propose un ordre de priorité.
Trois règles l’encadrent :
- Elle lit, elle n’agit pas. Aucune commande, aucun redémarrage, aucune modification de configuration ne part d’un modèle.
- Les données sont nettoyées. Retirez adresses IP, adresses e-mail et jetons avant tout envoi à un service externe, et vérifiez où il traite les données.
- Chaque conclusion se vérifie dans le journal brut. Un modèle peut se tromper de priorité ou inventer une cause.
Les prototypes de prédiction de pannes, souvent fondés sur Isolation Forest, butent sur des pièges connus. Les compteurs d’entrées-sorties disque sont cumulés depuis le démarrage et doivent être convertis en écarts entre deux mesures. Quelques heures de données ne représentent pas une semaine. Une tendance simple, comme predict_linear sur le disque, prévient souvent mieux. Pour la détection d’anomalies sur un CMS, voir notre guide de surveillance PrestaShop et WordPress.
Questions fréquentes
Le watchdog peut-il redémarrer seul un service en panne ?
Nous le déconseillons. Un redémarrage automatique peut effacer des indices et enchaîner des relances sur un service défaillant. Le script alerte, l’astreinte décide.
Faut-il Kubernetes pour surveiller et faire évoluer un serveur ?
Non. Un serveur unique se surveille avec une sonde externe et un script. Kubernetes se justifie pour plusieurs instances sur plusieurs nœuds ; mal maîtrisé, il ajoute ses propres pannes.
Quelle métrique choisir pour l’auto-scaling d’une application IA ?
Celle qui sature en premier. Pour un modèle sur GPU, c’est souvent la mémoire vidéo ou la file d’attente, pas le CPU. Mesurez le délai de démarrage d’une instance : s’il est long, l’auto-scaling arrive après le pic.
Combien de temps observer avant de fixer les seuils ?
Une à deux semaines, avec au moins un pic habituel. Fixez ensuite deux niveaux, exigez une durée minimale avec for et relisez les seuils chaque mois.
Sources
- Google, Site Reliability Engineering, chapitre 6 « Monitoring Distributed Systems » : https://sre.google/sre-book/monitoring-distributed-systems/
- Prometheus, Alerting rules : https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- Prometheus, promtool : https://prometheus.io/docs/prometheus/latest/command-line/promtool/
- Prometheus, Query functions (
predict_linear) : https://prometheus.io/docs/prometheus/latest/querying/functions/ - Prometheus, node_exporter : https://github.com/prometheus/node_exporter
- Prometheus, blackbox_exporter (README et code
prober/) : https://github.com/prometheus/blackbox_exporter - nginx, ngx_http_stub_status_module : https://nginx.org/en/docs/http/ngx_http_stub_status_module.html
- nginx, ngx_http_proxy_module et ngx_http_upstream_module : https://nginx.org/en/docs/http/ngx_http_proxy_module.html, https://nginx.org/en/docs/http/ngx_http_upstream_module.html
- PHP, FPM status page : https://www.php.net/manual/en/fpm.status.php
- Kubernetes, Horizontal Pod Autoscaling : https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/