Retour au blog
Architecture2 octobre 2026Lecture 9 min

Quand le canal d’alerte dépend du système surveillé

Pendant quatre-vingt-dix jours, ma surveillance n’a envoyé aucune alerte. J’ai lu ce silence comme une bonne nouvelle. Elle était morte le 27 juin, et je l’ai appris le 25 septembre — par accident, en déployant autre chose.

Le 19 mai 2026, un de mes serveurs sature sa mémoire. Pas de partition d’échange, le noyau commence à tuer des processus, et les poignées de main TLS expirent. Incident classique, correctif classique : quatre gigaoctets d’échange, et une tâche planifiée qui surveille la mémoire et m’écrit si elle dépasse un seuil. J’ajoute dans la foulée une seconde tâche qui surveille l’expiration de mes certificats.

Le 27 juin, mon fournisseur de messagerie rejette le mot de passe du compte d’envoi. Les deux tâches continuent de s’exécuter à l’heure, de détecter correctement, et d’échouer silencieusement à l’envoi. Je ne reçois plus rien. Je n’en conclus rien, parce qu’il n’y avait rien à conclure : ne rien recevoir, c’était déjà l’état normal.

Je l’apprends le 25 septembre, en déployant un site sans rapport, parce qu’un formulaire de contact refuse d’envoyer un courriel. Quatre-vingt-dix jours. L’alerte que j’avais construite en réaction à un incident avait vécu trente-neuf jours avant de mourir sans bruit.

Et le détail que je trouve le plus inconfortable : parmi les tâches muettes figurait la surveillance d’expiration de mes certificats. Ils sont dix-neuf, émis individuellement, et ils arrivaient à échéance pendant cette fenêtre précise. L’alerte conçue pour ce risque était hors service au moment exact du risque.

Le défaut tient en une ligne

Voici la forme réduite de ce que faisait ma tâche. Elle détecte correctement. C’est la dernière instruction qui pose problème.

ram-watch.sh — appelé par cron toutes les cinq minutes
USED=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')
[ "$USED" -lt 90 ] && exit 0

curl --ssl-reqd smtps://smtp.exemple.ca:465 \
     --user "$MAIL_USER:$MAIL_PASS" \
     --mail-from "$MAIL_FROM" --mail-rcpt "$ALERT_TO" \
     --upload-file /tmp/alerte.txt

# Le code de retour de curl n'est jamais lu.
# L'envoi echoue, le script sort 0, cron ne signale rien.

Le serveur de messagerie refuse l’authentification, curl sort en erreur, et personne ne lit ce code de retour. La tâche se termine en succès apparent. Du point de vue du planificateur, tout s’est bien passé cinq minutes durant, quatre-vingt-dix jours de suite.

Une classe de défaillance, pas un accident

Il serait confortable de ranger ça parmi les étourderies, et de conclure qu’il suffisait de tester le code de retour. Ce serait passer à côté. Supposons que je l’aie testé :

La correction évidente — et insuffisante
if ! curl --fail --silent ... ; then
  echo "$(date -Is) envoi d'alerte en echec" >> /var/log/alertes.log
fi

# Mieux : la panne laisse une trace.
# Mais la trace est ecrite SUR la machine en difficulte,
# et personne ne va la lire tant que rien ne l'annonce.

On a déplacé le problème sans le résoudre. La preuve de la panne existe désormais, mais elle reste à l’intérieur du périmètre, et sa lecture dépend d’un signalement qui ne viendra pas. C’est le même défaut, d’un cran plus profond.

L’ingénierie de la fiabilité a un nom pour cette famille. Une défaillance de mode commun est une cause unique qui met en échec plusieurs composants que l’on croyait indépendants. Le concept vient des industries où la redondance est vitale : on y a appris qu’aligner trois systèmes de secours ne sert à rien s’ils partagent la même alimentation.

Mes deux tâches étaient indépendantes l’une de l’autre. Elles surveillaient des objets différents, selon des logiques différentes, à des rythmes différents. Mais elles empruntaient le même canal de sortie, et ce canal appartenait au périmètre qu’elles étaient censées surveiller. Une panne unique les a donc neutralisées toutes les deux, simultanément, sans produire le moindre signal.

Quatre défauts, un seul symptôme

DéfautConséquence
Le canal d’alerte partage une dépendance avec le système surveilléUne panne unique supprime la détection et le signalement
Le canal d’alerte n’est surveillé par rienSa mort ne produit aucun événement
L’envoi est une poussée sans accusé de réceptionL’émetteur ignore s’il a été entendu
La santé est déduite de l’absence de plainteLe silence est interprété comme une confirmation

Les trois premiers sont des défauts d’architecture et ils se corrigent. Le quatrième est un défaut de raisonnement, et c’est le plus coûteux : il transforme chacun des trois autres en angle mort durable.

L’inversion

Presque toute surveillance repose sur une assertion positive de panne : quelque chose casse, le système me prévient. Ce modèle porte une faille native — il exige que le composant défaillant soit encore assez sain pour signaler sa propre défaillance. On demande au malade de composer lui-même le numéro des secours.

La réponse connue est une assertion négative de santé. Le système n’annonce plus ses pannes, il annonce sa vie. Ce qui déclenche l’alarme n’est plus l’arrivée d’un message, c’est son absence — et l’observateur qui constate cette absence se tient hors du périmètre surveillé.

Le silence d’un dispositif de surveillance n’est pas une bonne nouvelle. C’est une absence d’information, et la plupart des architectures la traitent comme une confirmation de santé.

L’interrupteur d’homme mort

Ce renversement est outillé depuis longtemps. Prometheus, l’un des systèmes de surveillance les plus répandus, embarque une règle dont la condition est toujours vraie :

La règle Watchdog : une alerte qui se déclenche en permanence, volontairement
groups:
  - name: meta
    rules:
      - alert: Watchdog
        expr: vector(1)        # toujours vraie
        annotations:
          summary: "Preuve de vie de la chaine d'alerte."

Cette alerte n’a aucune valeur prise isolément : elle est routée vers un service de battement externe, qui attend de la recevoir à intervalle régulier. Tant qu’elle arrive, la chaîne vit. Si elle s’interrompt, c’est le service externe qui prévient, par un autre chemin.

Le routage réserve un piège qui mérite d’être cité, parce qu’il montre que la pratique a été éprouvée par des gens qui s’y sont brûlés :

Le piège : l’intervalle réel n’est pas celui qu’on croit avoir écrit
route:
  group_interval: 5m          # <- c'est cette valeur qui s'applique
  routes:
    - matchers: [ alertname = "Watchdog" ]
      receiver: battement-externe
      repeat_interval: 1m     # ignore : Alertmanager retient la plus grande

Un seuil de détection calibré sur la minute annoncée se déclencherait donc à tort, toutes les cinq minutes. Le dispositif censé détecter les silences se mettrait à crier faux — et on finirait par l’ignorer, ce qui ramène exactement au point de départ.

Quatre parades, et ce qu’elles coûtent

ParadeCe qu’elle règleCe qu’elle coûte
Second canal indépendantLe mode communUn abonnement ; reste une poussée non confirmée
Interrupteur d’homme mort externeLa détection du silenceUne dépendance et une frontière de confiance de plus
Battement auto-hébergé ailleursLe silence, sans tiersUne seconde machine, à surveiller à son tour
Message de vie périodique attenduLe cas réel, à peu prèsRien, sinon une habitude humaine

La version la plus simple tient sur une ligne de planificateur. Le signal de vie n’est émis que si la tâche s’est réellement bien terminée, et il part vers une adresse qui n’a rien à voir avec la machine surveillée.

crontab — le battement ne part que si la tâche a réussi
*/5 * * * * /opt/scripts/ram-watch.sh \
            && curl -fsS https://battement.exemple.ca/ping/<jeton>

# Le && est le coeur du dispositif : un echec de la tache
# supprime le battement, et c'est l'absence qui alerte.

La dernière ligne du tableau mérite qu’on s’y arrête, parce qu’elle est la moins élégante et la plus efficace à court terme. Un message envoyé chaque lundi, qu’on s’attend à recevoir, transforme une personne en détecteur de silence. C’est fragile, ça ne passe pas à l’échelle, et ça aurait réduit mes quatre-vingt-dix jours à sept.

Les deux parades du milieu sont les bonnes réponses durables, et elles s’opposent sur un seul point : accepter ou non une dépendance externe. Un service tiers vous sort du périmètre — c’est précisément ce qu’on cherche — mais il ajoute un fournisseur à votre chaîne critique. Un battement auto-hébergé vous garde maître de tout, au prix d’une seconde machine qui devient à son tour un composant à surveiller.

La règle

De cet incident je retiens une seule contrainte, et elle se vérifie sur un schéma, sans écrire une ligne de code : toute chaîne de surveillance doit se terminer hors du système qu’elle surveille. Chez un tiers, sur une autre machine, ou dans une attente humaine — mais dehors.

Si elle boucle sur elle-même, son silence devient indistinguable de sa santé. Et une information qu’on ne peut pas distinguer de son contraire n’est pas une information.

Sources

  1. Beyer, Jones, Petoff et Murphy, Site Reliability Engineering, O’Reilly, 2016, chapitre 6 « Monitoring Distributed Systems », consultable librement sur sre.google/sre-book.
  2. Documentation Prometheus Alertmanager : règle Watchdog bâtie sur vector(1), et routage vers un service de battement externe.
  3. « A Dead Man’s Switch for Your Monitoring Stack », dev.to, sur la prééminence de group_interval sur repeat_interval dans le routage du battement.
  4. Journal d’incident Jov’s Inc. : saturation mémoire du 19 mai 2026, rejet d’authentification SMTP du 27 juin 2026, découverte le 25 septembre 2026.
Un projet en tête?
Cadrage clair, réponse en 24 h, aucun engagement.
Voir l'offre