Pruebas de brokers

Monitorizar MQTT con Prometheus y Grafana

Monitoriza un broker MQTT con Prometheus y Grafana: mosquitto-exporter, métricas de EMQX, un exporter de sondeo en Python, Docker Compose, dashboards y alertas.

Actualizado · 9 min de lectura

Monitorizar MQTT con Prometheus y Grafana

Prometheus y Grafana son la combinación habitual para monitorizar un broker MQTT. Prometheus recoge métricas por HTTP y evalúa las reglas de alerta. Grafana dibuja los dashboards. Los brokers exponen sus métricas de dos maneras: algunos tienen un endpoint Prometheus nativo (EMQX, VerneMQ, HiveMQ mediante una extensión), mientras que otros, como Mosquitto, solo publican topics $SYS y necesitan un exporter que los traduzca.

Esta guía conecta las tres fuentes de datos que probablemente necesitarás: un exporter para Mosquitto, el endpoint nativo de EMQX y una sonda del lado del cliente que funciona con cualquier broker, incluidos los gestionados. Después añade dashboards y alertas.

De dónde salen las métricas

BrokerCómo obtiene Prometheus las métricas
Mosquittomosquitto-exporter se suscribe a $SYS/# y sirve /metrics (puerto 9234 por defecto)
EMQX 5Endpoint integrado /api/v5/prometheus/stats en el puerto del dashboard/API (18083), o envío a un Pushgateway
HiveMQHiveMQ Prometheus Monitoring Extension, sirve /metrics (puerto 9399 por defecto)
VerneMQ/metrics integrado en su listener HTTP (puerto 8888 por defecto)
Broker gestionadoMétricas del proveedor si las ofrece, más tu propia sonda del lado del cliente

Las métricas del broker te dicen qué está haciendo el broker: conexiones, throughput, colas y descartes. Una sonda del lado del cliente te dice lo que viven tus dispositivos: ¿pueden conectarse y cuánto tarda un mensaje? Necesitas ambas. Si solo puedes tener una, elige la sonda, porque es la que detecta fallos de DNS, TLS, balanceador y autenticación.

Si $SYS es nuevo para ti, lee primero monitorizar un broker con topics $SYS. El exporter de abajo es una capa fina encima.

Un stack local con Docker Compose

Este stack ejecuta Mosquitto, el exporter, Prometheus y Grafana juntos. Es un buen laboratorio para construir dashboards antes de apuntarlos a producción.

docker-compose.yml
services:
  mosquitto:
    image: eclipse-mosquitto:2
    ports: ["1883:1883"]
    volumes:
      - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro

  mosquitto-exporter:
    image: sapcc/mosquitto-exporter
    command: ["--endpoint", "tcp://mosquitto:1883"]
    ports: ["9234:9234"]
    depends_on: [mosquitto]

  prometheus:
    image: prom/prometheus
    ports: ["9090:9090"]
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - ./alerts.yml:/etc/prometheus/alerts.yml:ro

  grafana:
    image: grafana/grafana
    ports: ["3000:3000"]
    volumes:
      - ./grafana-datasource.yml:/etc/grafana/provisioning/datasources/prometheus.yml:ro
mosquitto.conf (solo laboratorio local)
listener 1883
allow_anonymous true
sys_interval 10
grafana-datasource.yml
apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    url: http://prometheus:9090
    isDefault: true

allow_anonymous true solo es aceptable en un portátil. En un broker real, crea un usuario de monitorización que pueda leer $SYS/# y nada más, y pásaselo al exporter con --user y --pass. El generador de mosquitto.conf puede crear los bloques de listener y autenticación por ti. Para un laboratorio más completo con más servicios, consulta el stack MQTT con Docker Compose.

El exporter convierte cada topic $SYS numérico en una métrica cuyo nombre sigue la ruta del topic; por ejemplo, $SYS/broker/clients/connected pasa a ser broker_clients_connected. Abre http://localhost:9234/metrics para ver los nombres exactos que genera tu versión antes de escribir consultas.

Un exporter de sondeo para cualquier broker

Este exporter en Python (paho-mqtt 2.x y prometheus_client) se conecta por TLS cada 15 segundos, se suscribe a un topic único, publica un mensaje y espera a que vuelva. Exporta si ha funcionado y cuánto ha tardado cada paso:

mqtt_probe_exporter.py
import os, ssl, threading, time, uuid
import paho.mqtt.client as mqtt
from prometheus_client import Gauge, Histogram, start_http_server

HOST, PORT, INTERVAL = "broker.connectmq.com", 8883, 15
success = Gauge("mqtt_probe_success", "1 if the last connect/publish/receive cycle worked")
connect_s = Gauge("mqtt_probe_connect_seconds", "TCP + TLS + CONNACK time of the last probe")
roundtrip = Histogram("mqtt_probe_roundtrip_seconds", "Subscribe, publish and receive time",
                      buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5))

def probe() -> bool:
    topic = f"testmqtt/probe/{uuid.uuid4().hex[:8]}"
    connected, received = threading.Event(), threading.Event()
    c = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                    client_id=f"testmqtt-probe-{uuid.uuid4().hex[:6]}")
    c.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
    c.tls_set(cert_reqs=ssl.CERT_REQUIRED)
    c.on_connect = lambda cl, ud, fl, rc, pr: connected.set() if not rc.is_failure else None
    c.on_subscribe = lambda cl, ud, mid, rcs, pr: cl.publish(topic, "ping", qos=1)
    c.on_message = lambda cl, ud, msg: received.set()
    t0 = time.perf_counter()
    try:
        c.connect(HOST, PORT, keepalive=10)
        c.loop_start()
        if not connected.wait(5):
            return False
        connect_s.set(time.perf_counter() - t0)
        t1 = time.perf_counter()
        c.subscribe(topic, qos=1)
        if not received.wait(5):
            return False
        roundtrip.observe(time.perf_counter() - t1)
        return True
    except OSError:
        return False
    finally:
        c.disconnect()
        c.loop_stop()

if __name__ == "__main__":
    start_http_server(9105)          # métricas en http://localhost:9105/metrics
    while True:
        success.set(1 if probe() else 0)
        time.sleep(INTERVAL)

Instálalo con pip install paho-mqtt prometheus-client. Abrir una conexión nueva en cada ciclo es intencionado: así se ejercitan DNS, TLS y autenticación cada vez, igual que un dispositivo que se reinicia. Usa un client ID único en cada ejecución. El generador de client IDs explica por qué los duplicados se expulsan entre sí.

Configuración de scrape

prometheus.yml
global:
  scrape_interval: 15s

rule_files: ["/etc/prometheus/alerts.yml"]

scrape_configs:
  - job_name: mosquitto
    static_configs:
      - targets: ["mosquitto-exporter:9234"]

  - job_name: mqtt-probe
    static_configs:
      - targets: ["host.docker.internal:9105"]   # donde se ejecuta la sonda

  - job_name: emqx
    metrics_path: /api/v5/prometheus/stats
    static_configs:
      - targets: ["emqx:18083"]

Elimina los jobs que no uses. En las versiones recientes de EMQX se puede configurar el endpoint de estadísticas para que exija autenticación. En ese caso, añade basic_auth con una API key y su secreto. Para probar EMQX en local, consulta EMQX en Docker. host.docker.internal funciona en Docker Desktop. En Linux, ejecuta la sonda como otro servicio de Compose.

Dashboards que responden preguntas reales

Un dashboard MQTT útil tiene pocos paneles, y cada uno responde una pregunta que alguien hará durante un incidente:

PanelPromQL (ejemplo)
¿Hay clientes conectados?broker_clients_connected o sum(emqx_connections_count)
Mensajes de entrada / salida por segundorate(broker_messages_received[5m]), rate(emqx_messages_sent[5m])
¿Estamos descartando mensajes?rate(broker_publish_messages_dropped[5m]) o rate(emqx_messages_dropped[5m])
¿Puede conectarse un cliente ahora mismo?mqtt_probe_success
¿Cómo de lenta es la entrega?histogram_quantile(0.99, sum by (le) (rate(mqtt_probe_roundtrip_seconds_bucket[10m])))
Tiempo de conexiónmqtt_probe_connect_seconds

Usa rate() sobre los contadores y nunca grafiques el valor bruto del contador. Pon los paneles de la sonda arriba, porque muestran lo que ven los usuarios. En grafana.com hay dashboards de la comunidad para el exporter de Mosquitto y para EMQX. Son un buen punto de partida, pero comprueba que sus nombres de métricas coinciden con tu versión del exporter.

Reglas de alerta

alerts.yml
groups:
  - name: mqtt
    rules:
      - alert: MqttProbeFailing
        expr: max_over_time(mqtt_probe_success[3m]) == 0
        labels: { severity: page }
        annotations:
          summary: "No successful MQTT round trip for 3 minutes"

      - alert: MqttProbeSlow
        expr: histogram_quantile(0.99, sum by (le) (rate(mqtt_probe_roundtrip_seconds_bucket[10m]))) > 0.5
        for: 10m
        labels: { severity: ticket }

      - alert: MqttClientsDropped
        expr: broker_clients_connected < 0.7 * (broker_clients_connected offset 10m)
        for: 2m
        labels: { severity: page }

      - alert: MqttExporterDown
        expr: up{job="mosquitto"} == 0
        for: 2m
        labels: { severity: ticket }

MqttClientsDropped detecta el patrón clásico de incidente: una gran parte de los dispositivos se desconecta a la vez, tras la caducidad de un certificado, un cambio de red o un reinicio del broker. Enruta las alertas page mediante Alertmanager a tu canal de guardia y las ticket a un sitio más tranquilo. Para comprobaciones externas y los cálculos de SLO, consulta monitorización de disponibilidad MQTT.

Prueba la propia monitorización

Antes de fiarte de las alertas, haz que cada una salte a propósito:

  1. Fallo de la sonda: cambia MQTT_PASS por un valor incorrecto y reinicia la sonda. mqtt_probe_success debería bajar a 0 y MqttProbeFailing debería saltar en unos tres minutos.
  2. Caída de clientes: conecta un grupo de clientes de prueba (un simulador de dispositivos o varias pestañas del cliente online), espera diez minutos y ciérralos. El panel de clientes conectados debería bajar en consecuencia.
  3. Exporter caído: docker compose stop mosquitto-exporter debería disparar MqttExporterDown.
  4. Latencia: añade retardo con un network shaper, o compara los números de la sonda con un test de latencia dedicado.

Una alerta que nunca ha saltado en una prueba puede que tampoco salte durante una caída real.

Prometheus y Grafana te dan histórico, dashboards y alertas que $SYS por sí solo no puede ofrecer. Combina las métricas del broker con una sonda del lado del cliente, mantén el dashboard pequeño y prueba cada alerta una vez antes de depender de ella.

Preguntas frecuentes

¿Mosquitto tiene un endpoint de Prometheus integrado?

No. Mosquitto publica sus estadísticas en topics $SYS. Un exporter como mosquitto-exporter se suscribe a $SYS/# y expone los valores en un endpoint HTTP /metrics que Prometheus puede recoger.

¿Puedo monitorizar un broker MQTT gestionado con Prometheus?

Sí, desde el lado del cliente. Aunque no puedas instalar un exporter junto al broker, una pequeña sonda que se conecta, publica y recibe un mensaje puede exportar métricas de éxito y latencia. Suele ser la señal más útil de todas formas.

¿Sobre qué debería alertar primero?

Empieza con tres alertas: la sonda fallando durante unos minutos, una caída brusca de clientes conectados y mensajes descartados por encima de cero. Añade alertas de latencia cuando conozcas tu p99 normal.