Surveillance serveur : sondes, alertes, auto-scaling et IA
Hébergement

Surveillance serveur : sondes, alertes, auto-scaling et IA

Monitoring d'un serveur web ou d'une application IA : sondes, Prometheus, alertes utiles, watchdog, auto-scaling borné et rôle encadré de l'IA.

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 :

SignalSur un CMSSur une application IA
LatenceTemps de réponse des pages clés, du panier, du paiementDurée d’une inférence, délai du premier jeton
TraficRequêtes par seconde dans le journal NginxRequêtes par seconde, longueur de la file
ErreursRéponses 5xx, échecs de la sonde externeRéponses 5xx, délais dépassés
SaturationFile d’attente PHP-FPM, mémoire, disque, chargeMé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

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

AlertePremière vérificationDécision humaine
Site en erreurcurl -I sur l’URL, journal d’erreurs NginxRedémarrage ou retour arrière après diagnostic
Disque presque pleindf -h, du -sh /var/log/*Suppression validée, après sauvegarde
Charge élevéeps trié par CPU, statut PHP-FPMArrêt d’un processus en connaissance de cause
5xx en hausseDernier déploiement, pic de traficCorrection 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 :

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

Agence web PrestaShop & WordPress

Agence web spécialisée PrestaShop et WordPress : création, maintenance, sécurité, performance et référencement. Nos tutoriels sont issus de nos interventions sur de vraies boutiques.

L'agence Genisoft → LinkedIn →