Tests de brokers

Surveillance externe de la disponibilité MQTT et alertes

Surveillez la disponibilité d'un broker MQTT depuis l'extérieur : sonde publish/subscribe, expiration de certificat, cron, heartbeats, Slack, PagerDuty et SLO.

Mis à jour · 9 min de lecture

Surveillance externe de la disponibilité MQTT et alertes

La surveillance externe de disponibilité répond à une seule question : un appareil situé hors de votre réseau pourrait-il, à l'instant, se connecter au broker et échanger un message ? Les métriques internes ne peuvent pas y répondre. Elles ne voient ni les pannes DNS, ni les certificats expirés, ni les load balancers défaillants, ni un changement de pare-feu. Seule une sonde qui se comporte comme un client et tourne depuis l'extérieur les voit.

Ce guide construit cette sonde, la planifie, achemine les alertes vers Slack ou PagerDuty et présente les calculs de SLO nécessaires pour choisir l'intervalle de vérification et le seuil d'alerte.

Ce que « disponible » veut dire pour un broker MQTT

« Le port est ouvert » est une définition trop faible. Un broker n'est utile que si toutes ces couches fonctionnent :

CoucheVérificationPanne typique détectée
DNSLe nom d'hôte se résoutDomaine expiré, mauvais enregistrement après une migration
TCPLe port 8883 accepte la connexionPare-feu, security group, processus du broker arrêté
TLSLe handshake réussit, le certificat est valide N jours de plusCertificat expiré ou qui ne correspond pas
MQTT CONNECTCONNACK avec un code de succèsBackend d'authentification en panne, identifiants changés, limite de connexions atteinte
Aller-retourLe message publié est reçu dans le délaiRoutage cassé, cluster scindé, surcharge

Vérifiez chaque couche séparément et indiquez laquelle a échoué. « TLS en échec, certificat expiré » se corrige en cinq minutes. « Sonde en échec » lance une enquête d'une demi-heure. Pour des vérifications manuelles ponctuelles, voir vérifier l'état d'un broker MQTT et tester si un port MQTT est ouvert.

Une sonde minimale avec les clients Mosquitto

Si vous avez déjà mosquitto_pub et mosquitto_sub, une sonde shell tient en une douzaine de lignes :

mqtt-probe.sh
#!/usr/bin/env bash
set -u
HOST=broker.connectmq.com; PORT=8883
TOPIC="testmqtt/uptime/$(hostname)-$$"
MSG="ping-$(date +%s)"
OUT=$(mktemp)

mosquitto_sub -h "$HOST" -p "$PORT" --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t "$TOPIC" -q 1 -C 1 -W 10 > "$OUT" &
SUB=$!
sleep 1
mosquitto_pub -h "$HOST" -p "$PORT" --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t "$TOPIC" -q 1 -m "$MSG"
wait $SUB

if grep -qx "$MSG" "$OUT"; then echo "OK"; rm -f "$OUT"; exit 0
else echo "FAIL: no round trip within 10 s"; rm -f "$OUT"; exit 1; fi

-C 1 fait quitter l'abonné après un message, et -W 10 le fait abandonner au bout de dix secondes. Le code de sortie est tout ce dont un planificateur ou un service de heartbeat a besoin.

Une sonde par couches en Python

Cette version (paho-mqtt 2.x) vérifie chaque couche, la chronomètre, indique le nombre de jours avant l'expiration du certificat et affiche une ligne JSON facile à envoyer dans vos logs :

mqtt_uptime_probe.py
import json, os, socket, ssl, sys, threading, time, uuid
import paho.mqtt.client as mqtt

HOST, PORT, TIMEOUT = "broker.connectmq.com", 8883, 10
result = {"host": HOST, "ts": int(time.time())}

def fail(stage, detail):
    result.update(ok=False, stage=stage, detail=str(detail))
    print(json.dumps(result)); sys.exit(1)

t = time.perf_counter()
try:
    socket.getaddrinfo(HOST, PORT)
except OSError as e:
    fail("dns", e)
try:
    ctx = ssl.create_default_context()
    with socket.create_connection((HOST, PORT), timeout=TIMEOUT) as raw:
        with ctx.wrap_socket(raw, server_hostname=HOST) as tls:
            not_after = ssl.cert_time_to_seconds(tls.getpeercert()["notAfter"])
except ssl.SSLError as e:
    fail("tls", e)
except OSError as e:
    fail("tcp", e)
result["cert_days_left"] = int((not_after - time.time()) // 86400)
result["tls_ms"] = round((time.perf_counter() - t) * 1000)

topic = f"testmqtt/uptime/{uuid.uuid4().hex[:8]}"
connack, got = {}, threading.Event()
c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"testmqtt-uptime-{uuid.uuid4().hex[:6]}")
c.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
c.tls_set_context(ctx)
c.on_connect = lambda cl, ud, fl, rc, pr: (connack.update(rc=rc),
                                           None if rc.is_failure else cl.subscribe(topic, qos=1))
c.on_subscribe = lambda cl, ud, mid, rcs, pr: cl.publish(topic, "ping", qos=1)
c.on_message = lambda cl, ud, msg: got.set()

t = time.perf_counter()
try:
    c.connect(HOST, PORT, keepalive=15)
except OSError as e:
    fail("connect", e)
c.loop_start()
ok = got.wait(TIMEOUT)
c.disconnect(); c.loop_stop()
if "rc" not in connack:
    fail("connect", "no CONNACK")
if connack["rc"].is_failure:
    fail("connect", connack["rc"])
if not ok:
    fail("roundtrip", f"no message within {TIMEOUT} s")
result.update(ok=True, roundtrip_ms=round((time.perf_counter() - t) * 1000))
print(json.dumps(result))
sys.exit(2 if result["cert_days_left"] < 14 else 0)

Le code de sortie 0 signifie que tout va bien, 1 que le service est indisponible (le champ stage indique où), et 2 que le service répond mais que le certificat expire dans moins de deux semaines. Ce dernier cas est un avertissement, pas une alerte d'astreinte. Quand l'étape connect échoue, le reason code (par exemple Not authorized) indique s'il s'agit d'un problème d'identifiants ou d'ACL. Consultez-le avec l'outil de reason codes ou dans les erreurs de connexion MQTT.

Planifier la sonde

  • cron ou un timer systemd sur une petite VM hors du réseau du broker : * * * * * /opt/probe/mqtt_uptime_probe.py >> /var/log/mqtt-probe.log 2>&1. C'est simple et ça tourne chaque minute.
  • Plusieurs emplacements. Lancez la même sonde depuis deux ou trois régions ou fournisseurs. Alertez quand la majorité échoue. Si un seul emplacement échoue, c'est en général le réseau de cet emplacement.
  • Le schedule de GitHub Actions convient aux vérifications peu critiques, mais l'intervalle minimal est de cinq minutes et les exécutions peuvent démarrer en retard. Ce n'est pas adapté à l'astreinte.
  • Uptime Kuma (auto-hébergé) propose un type de moniteur MQTT et convient bien si vous préférez une interface graphique.

Ce qui exécute la sonde peut aussi tomber en panne. Utilisez un heartbeat (dead man's switch) : après chaque exécution réussie, la sonde envoie un ping à une URL d'un service de heartbeat comme Healthchecks.io ou un push monitor d'Uptime Kuma. Si les pings cessent, c'est que le broker ou la machine de la sonde est tombé, et vous êtes alerté dans les deux cas :

Ligne cron avec heartbeat
* * * * * /opt/probe/mqtt-probe.sh && curl -fsS -m 10 --retry 3 "$HEARTBEAT_URL" > /dev/null

Canaux d'alerte

Envoyez les alertes là où quelqu'un les verra vraiment, et seulement quand les preuves sont suffisantes. Une règle courante : trois échecs consécutifs depuis au moins deux emplacements, avec un message de rétablissement quand tout revient à la normale.

Incoming webhook Slack
curl -fsS -X POST -H 'Content-Type: application/json' \
  -d '{"text":":red_circle: MQTT probe failed at stage tls (broker.connectmq.com:8883)"}' \
  "$SLACK_WEBHOOK_URL"
PagerDuty Events API v2
curl -fsS -X POST https://events.pagerduty.com/v2/enqueue \
  -H 'Content-Type: application/json' \
  -d '{"routing_key":"'"$PD_ROUTING_KEY"'","event_action":"trigger",
       "dedup_key":"mqtt-probe-broker",
       "payload":{"summary":"MQTT round trip failing","source":"mqtt-probe","severity":"critical"}}'

Envoyez la même requête avec "event_action":"resolve" et la même dedup_key pour clore l'incident automatiquement. Gardez les avertissements d'expiration de certificat dans Slack ou par e-mail. Ils demandent une action dans la semaine, pas à 3 heures du matin. Si vous utilisez déjà Prometheus, exportez la sonde sous forme de métriques et alertez depuis Prometheus. Voir superviser MQTT avec Prometheus et Grafana.

Calculs de SLO

Un objectif de disponibilité n'a de sens qu'avec son budget d'indisponibilité :

DisponibilitéIndisponibilité sur 30 joursIndisponibilité par an
99 %7 h 12 min3,65 jours
99,5 %3 h 36 min1,83 jour
99,9 %43,2 min8 h 46 min
99,95 %21,6 min4 h 23 min
99,99 %4,3 min52,6 min

Mesurez-la comme sondes réussies ÷ sondes totales. Une sonde par minute donne 43 200 échantillons en 30 jours, donc 99,9 % autorise 43 sondes en échec. Trois conséquences en découlent :

  • L'intervalle limite la précision. Avec une sonde par minute, une panne de 20 secondes peut passer inaperçue ou compter pour une minute entière. Pour mesurer 99,99 %, sondez toutes les 10 à 15 secondes.
  • Le temps de détection consomme le budget. Trois échecs à une minute d'intervalle, c'est environ trois minutes avant que quelqu'un soit alerté. C'est acceptable pour 99,9 % (43 minutes) et c'est presque tout le budget à 99,99 %.
  • Votre SLO n'est pas le SLA du fournisseur. Le SLA couvre le broker. Votre sonde mesure tout le chemin, y compris votre DNS, vos certificats et votre réseau. Lisez SLA et disponibilité des brokers MQTT avant de comparer les deux.

Suivez votre budget d'erreur : si 30 de vos 43 minutes sont consommées à la mi-mois, gelez les changements risqués sur le broker jusqu'à ce qu'il se reconstitue.

Testez la chaîne d'alerte

Une sonde qui n'a jamais échoué n'a pas été testée. Avant de vous y fier :

  1. Mettez un mauvais mot de passe et vérifiez que l'étape connect échoue et que l'alerte arrive dans Slack ou PagerDuty.
  2. Pointez HOST vers un nom qui ne se résout pas et vérifiez stage: dns.
  3. Arrêtez la tâche cron et vérifiez que le service de heartbeat vous alerte.
  4. Vérifiez à la main que la vision de la sonde correspond à la réalité : connectez-vous avec le client MQTT en ligne et publiez sur le topic de la sonde pendant son exécution.

La surveillance externe de disponibilité se résume à un petit script, un planificateur et un heartbeat. Vérifiez chaque couche séparément, n'alertez que sur des échecs répétés depuis plusieurs emplacements, déduisez l'intervalle de la sonde de votre SLO, et cassez volontairement la configuration une fois pour prouver que les alertes arrivent.

Questions fréquentes

Vérifier que le port 1883 est ouvert suffit-il pour surveiller la disponibilité MQTT ?

Non. Un broker peut accepter des connexions TCP tout en refusant les logins, en échouant sur TLS ou en ne routant pas les messages. Une vraie vérification se connecte avec des identifiants, s'abonne, publie un message et confirme qu'il arrive dans un délai donné.

À quelle fréquence une sonde de disponibilité MQTT doit-elle tourner ?

Toutes les minutes est une bonne valeur par défaut. L'intervalle limite la précision de la mesure : avec une vérification par minute, les pannes de moins d'une minute peuvent passer inaperçues, et un budget mensuel de 99,99 % (environ quatre minutes) est épuisé avant qu'une alerte à trois échecs ne se déclenche.

Combien d'indisponibilité 99,9 % autorise-t-il ?

Environ 43 minutes par mois de 30 jours, soit à peu près 8 heures 46 minutes par an. 99,95 % autorise environ 22 minutes par mois, et 99,99 % environ 4,3 minutes.