Prometheus no sabe consultar una base de datos directamente: necesita un exportador que se conecte a ella, lea sus estadísticas internas y las publique en formato Prometheus. En este tutorial instalarás mysqld_exporter para MySQL y postgres_exporter para PostgreSQL en Ubuntu 24.04, cada uno con un usuario de base de datos con los permisos mínimos y como servicio de systemd. Después los añadirás a Prometheus, revisarás las métricas más útiles y crearás alertas para caídas y exceso de conexiones.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS de 64 bits (x86_64), por ejemplo un VPS de CubePath, con MySQL 8.0 (paquete mysql-server), PostgreSQL 16 (paquete postgresql) o ambos. Si solo usas uno, sigue únicamente su parte.
  • Un usuario no root con privilegios sudo.
  • Un servidor Prometheus con acceso de red a este servidor, con su configuración en /etc/prometheus/prometheus.yml. En la guía su IP aparece como prometheus_server_ip.

Las secciones de MySQL también sirven para MariaDB, que acepta los mismos permisos.

Paso 1: Crear el usuario de monitorización en MySQL

El exportador solo necesita leer estadísticas, así que conviene darle un usuario propio con permisos mínimos y un límite de conexiones para que nunca pueda saturar el servidor. Abre la consola de MySQL como root (en Ubuntu, root se autentica por socket):

sudo mysql

Ejecuta las siguientes sentencias, sustituyendo your_strong_password por una contraseña larga y aleatoria:

CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'your_strong_password' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';

PROCESS permite leer la lista de procesos y el estado de InnoDB, REPLICATION CLIENT el estado de la replicación, y SELECT las tablas de information_schema y performance_schema. Comprueba los permisos y sal:

SHOW GRANTS FOR 'exporter'@'localhost';
EXIT;
+----------------------------------------------------------------------------+
| Grants for exporter@localhost                                              |
+----------------------------------------------------------------------------+
| GRANT SELECT, PROCESS, REPLICATION CLIENT ON *.* TO `exporter`@`localhost` |
+----------------------------------------------------------------------------+

Paso 2: Instalar mysqld_exporter

Crea un usuario de sistema para ejecutar el exportador:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin mysqld_exporter

Consulta la última versión en la página de releases (0.20.0 al escribir esta guía), descárgala y verifica la suma de comprobación:

MYSQLD_EXPORTER_VERSION=0.20.0
cd /tmp
curl -fsSLO "https://github.com/prometheus/mysqld_exporter/releases/download/v${MYSQLD_EXPORTER_VERSION}/mysqld_exporter-${MYSQLD_EXPORTER_VERSION}.linux-amd64.tar.gz"
curl -fsSL -o mysqld_exporter_sha256sums.txt "https://github.com/prometheus/mysqld_exporter/releases/download/v${MYSQLD_EXPORTER_VERSION}/sha256sums.txt"
sha256sum -c --ignore-missing mysqld_exporter_sha256sums.txt
mysqld_exporter-0.20.0.linux-amd64.tar.gz: OK

Descomprime e instala el binario:

tar -xzf "mysqld_exporter-${MYSQLD_EXPORTER_VERSION}.linux-amd64.tar.gz"
sudo install -m 0755 "mysqld_exporter-${MYSQLD_EXPORTER_VERSION}.linux-amd64/mysqld_exporter" /usr/local/bin/
mysqld_exporter --version
mysqld_exporter, version 0.20.0 (branch: HEAD, revision: ...)

Paso 3: Guardar las credenciales de MySQL

Las versiones actuales de mysqld_exporter ya no leen la variable DATA_SOURCE_NAME: las credenciales van en un archivo con formato my.cnf. Créalo en un directorio propio:

sudo mkdir -p /etc/mysqld_exporter
sudo nano /etc/mysqld_exporter/my.cnf
[client]
user = exporter
password = your_strong_password

Sin host ni port, el exportador se conecta a localhost:3306. Protege el archivo para que solo root y el usuario del servicio puedan leerlo:

sudo chown root:mysqld_exporter /etc/mysqld_exporter/my.cnf
sudo chmod 640 /etc/mysqld_exporter/my.cnf

Paso 4: Crear el servicio de mysqld_exporter

sudo nano /etc/systemd/system/mysqld_exporter.service
[Unit]
Description=Prometheus MySQL Exporter
Wants=network-online.target
After=network-online.target mysql.service

[Service]
User=mysqld_exporter
Group=mysqld_exporter
ExecStart=/usr/local/bin/mysqld_exporter \
  --config.my-cnf=/etc/mysqld_exporter/my.cnf \
  --web.listen-address=:9104
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

Los recolectores activos por defecto cubren el estado global (SHOW GLOBAL STATUS), las variables de configuración y el estado de la replicación. No actives recolectores adicionales como --collect.info_schema.tables en servidores con miles de tablas: cada scrape consultaría information_schema completo.

Arranca el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now mysqld_exporter

Comprueba que el exportador se conecta a MySQL. La métrica mysql_up vale 1 si la conexión funciona:

curl -s http://localhost:9104/metrics | grep -E '^mysql_(up|global_status_threads_connected|global_variables_max_connections) '
mysql_global_status_threads_connected 2
mysql_global_variables_max_connections 151
mysql_up 1

Si mysql_up vale 0, revisa el motivo con sudo journalctl -u mysqld_exporter -n 20. Normalmente es un error de contraseña (Access denied) o de permisos del archivo my.cnf.

Paso 5: Crear el usuario de monitorización en PostgreSQL

PostgreSQL incluye el rol predefinido pg_monitor, que da acceso de lectura a todas las vistas de estadísticas sin permisos sobre los datos. Abre psql como el usuario postgres:

sudo -u postgres psql

Crea el rol, sustituyendo your_strong_password por una contraseña distinta de la de MySQL:

CREATE USER postgres_exporter WITH PASSWORD 'your_strong_password' CONNECTION LIMIT 3;
GRANT pg_monitor TO postgres_exporter;
\q
CREATE ROLE
GRANT ROLE

La configuración por defecto de pg_hba.conf en Ubuntu permite conexiones con contraseña (scram-sha-256) desde 127.0.0.1, así que no hace falta modificarla.

Paso 6: Instalar y configurar postgres_exporter

Crea el usuario de sistema, descarga la última versión desde la página de releases (0.20.1 al escribir esta guía) y verifícala:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin postgres_exporter
POSTGRES_EXPORTER_VERSION=0.20.1
cd /tmp
curl -fsSLO "https://github.com/prometheus-community/postgres_exporter/releases/download/v${POSTGRES_EXPORTER_VERSION}/postgres_exporter-${POSTGRES_EXPORTER_VERSION}.linux-amd64.tar.gz"
curl -fsSL -o postgres_exporter_sha256sums.txt "https://github.com/prometheus-community/postgres_exporter/releases/download/v${POSTGRES_EXPORTER_VERSION}/sha256sums.txt"
sha256sum -c --ignore-missing postgres_exporter_sha256sums.txt
postgres_exporter-0.20.1.linux-amd64.tar.gz: OK

Instala el binario:

tar -xzf "postgres_exporter-${POSTGRES_EXPORTER_VERSION}.linux-amd64.tar.gz"
sudo install -m 0755 "postgres_exporter-${POSTGRES_EXPORTER_VERSION}.linux-amd64/postgres_exporter" /usr/local/bin/

postgres_exporter lee la cadena de conexión de la variable de entorno DATA_SOURCE_NAME. Guárdala en un archivo de entorno que solo root pueda leer (systemd lo carga antes de cambiar al usuario del servicio):

sudo mkdir -p /etc/postgres_exporter
sudo nano /etc/postgres_exporter/postgres_exporter.env
DATA_SOURCE_NAME=postgresql://postgres_exporter:[email protected]:5432/postgres?sslmode=disable

Si la contraseña contiene caracteres como @, : o /, codifícalos en formato URL (por ejemplo, @ se escribe %40). La opción sslmode=disable es adecuada porque la conexión no sale de la máquina.

sudo chmod 600 /etc/postgres_exporter/postgres_exporter.env

Crea la unidad de systemd:

sudo nano /etc/systemd/system/postgres_exporter.service
[Unit]
Description=Prometheus PostgreSQL Exporter
Wants=network-online.target
After=network-online.target postgresql.service

[Service]
User=postgres_exporter
Group=postgres_exporter
EnvironmentFile=/etc/postgres_exporter/postgres_exporter.env
ExecStart=/usr/local/bin/postgres_exporter \
  --web.listen-address=:9187
Restart=on-failure
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

Arranca el servicio y comprueba las métricas:

sudo systemctl daemon-reload
sudo systemctl enable --now postgres_exporter
curl -s http://localhost:9187/metrics | grep -E '^pg_(up|settings_max_connections) '
pg_settings_max_connections 100
pg_up 1

En el journal verás un aviso Error loading config sobre postgres_exporter.yml. Ese archivo solo es necesario para funciones avanzadas (autenticación para varios destinos) y el aviso es inofensivo.

Paso 7: Abrir los puertos a Prometheus

Los exportadores no tienen autenticación y las métricas revelan nombres de bases de datos y usuarios, así que solo deben ser accesibles desde el servidor de Prometheus:

sudo ufw allow from prometheus_server_ip to any port 9104 proto tcp
sudo ufw allow from prometheus_server_ip to any port 9187 proto tcp
sudo ufw status

Si Prometheus corre en este mismo servidor, no abras los puertos y cambia --web.listen-address a 127.0.0.1:9104 y 127.0.0.1:9187 en las unidades.

Paso 8: Añadir los exportadores a Prometheus

En el servidor de Prometheus, edita la configuración:

sudo nano /etc/prometheus/prometheus.yml

Añade dos trabajos dentro de scrape_configs, sustituyendo db_server_ip por la IP del servidor de base de datos:

  - job_name: "mysql"
    static_configs:
      - targets: ["db_server_ip:9104"]
        labels:
          environment: "production"

  - job_name: "postgresql"
    static_configs:
      - targets: ["db_server_ip:9187"]
        labels:
          environment: "production"

Valida y reinicia Prometheus:

promtool check config /etc/prometheus/prometheus.yml
sudo systemctl restart prometheus

En Status > Targets los trabajos mysql y postgresql deben aparecer como UP. Ejecuta mysql_up y pg_up en la pestaña Graph para confirmar que ambos devuelven 1.

Paso 9: Consultar las métricas clave

Estas son las consultas PromQL más útiles para empezar, válidas tanto en Prometheus como en paneles de Grafana.

MySQL

Qué mideConsulta
Consultas por segundorate(mysql_global_status_queries[5m])
Conexiones abiertasmysql_global_status_threads_connected
Uso de conexiones (%)mysql_global_status_threads_connected / mysql_global_variables_max_connections * 100
Hilos ejecutando consultasmysql_global_status_threads_running
Consultas lentas por segundorate(mysql_global_status_slow_queries[5m])
Conexiones fallidas por segundorate(mysql_global_status_aborted_connects[5m])

Slow_queries cuenta las consultas que superan long_query_time (10 segundos por defecto), aunque el log de consultas lentas esté desactivado. Si quieres que sea útil, baja ese valor en la configuración de MySQL, por ejemplo a 1 segundo.

PostgreSQL

Qué mideConsulta
Conexiones por base de datospg_stat_database_numbackends
Uso de conexiones (%)sum by (instance, job) (pg_stat_database_numbackends) / pg_settings_max_connections * 100
Transacciones confirmadas por segundosum by (datname) (rate(pg_stat_database_xact_commit[5m]))
Ratio de aciertos de caché (%)sum(rate(pg_stat_database_blks_hit[5m])) / (sum(rate(pg_stat_database_blks_hit[5m])) + sum(rate(pg_stat_database_blks_read[5m]))) * 100
Tamaño de cada base de datospg_database_size_bytes
Bloqueos mutuos (deadlocks)increase(pg_stat_database_deadlocks[1h])

Un ratio de aciertos de caché por debajo del 99 % de forma sostenida en una base de datos OLTP suele indicar que shared_buffers o la RAM del servidor se han quedado cortos.

Para Grafana, en grafana.com hay paneles publicados por la comunidad para ambos exportadores: busca "mysqld_exporter" o "postgres_exporter" e impórtalos desde Dashboards > New > Import.

Paso 10: Crear alertas

Crea un archivo de reglas en el servidor de Prometheus:

sudo nano /etc/prometheus/database_alerts.yml
groups:
  - name: databases
    rules:
      - alert: MySQLDown
        expr: mysql_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "MySQL no responde en {{ $labels.instance }}"
          description: "mysqld_exporter no puede conectarse a MySQL desde hace más de 1 minuto."

      - alert: MySQLTooManyConnections
        expr: mysql_global_status_threads_connected / mysql_global_variables_max_connections > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "MySQL usa más del 80 % de sus conexiones en {{ $labels.instance }}"

      - alert: PostgreSQLDown
        expr: pg_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "PostgreSQL no responde en {{ $labels.instance }}"
          description: "postgres_exporter no puede conectarse a PostgreSQL desde hace más de 1 minuto."

      - alert: PostgreSQLTooManyConnections
        expr: sum by (instance, job) (pg_stat_database_numbackends) / pg_settings_max_connections > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "PostgreSQL usa más del 80 % de sus conexiones en {{ $labels.instance }}"

      - alert: PostgreSQLDeadlocks
        expr: increase(pg_stat_database_deadlocks[10m]) > 0
        labels:
          severity: warning
        annotations:
          summary: "Deadlocks en la base de datos {{ $labels.datname }} de {{ $labels.instance }}"

Si el exportador entero deja de responder, mysql_up y pg_up desaparecen en lugar de valer 0. Para cubrir ese caso, Prometheus genera la métrica up para cada destino: una regla con up{job=~"mysql|postgresql"} == 0 avisa cuando el exportador está caído.

Registra el archivo en /etc/prometheus/prometheus.yml dentro de rule_files:

rule_files:
  - "database_alerts.yml"

Valida y reinicia:

promtool check rules /etc/prometheus/database_alerts.yml
promtool check config /etc/prometheus/prometheus.yml
sudo systemctl restart prometheus
Checking /etc/prometheus/database_alerts.yml
  SUCCESS: 5 rules found

Para probar la alerta, detén MySQL (sudo systemctl stop mysql) en un entorno de pruebas: mysql_up pasará a 0 y, al cabo de un minuto, MySQLDown aparecerá en estado Firing en la pestaña Alerts. Vuelve a arrancarlo con sudo systemctl start mysql.

Solución de problemas

mysql_up 0 con Access denied for user 'exporter'@'localhost'. La contraseña de my.cnf no coincide con la del usuario, o el usuario se creó con otro host (por ejemplo 'exporter'@'%'). Compruébalo con SELECT user, host FROM mysql.user WHERE user = 'exporter';.

pg_up 0 con password authentication failed. Revisa la contraseña de DATA_SOURCE_NAME y que los caracteres especiales estén codificados en formato URL. Recuerda reiniciar el servicio tras editar el archivo de entorno.

Faltan métricas de algunas vistas de PostgreSQL. El rol no tiene pg_monitor. Comprueba con \du postgres_exporter en psql que aparece {pg_monitor} en la columna de pertenencia.

Scrapes lentos o context deadline exceeded en Prometheus. Algún recolector tarda demasiado en una base de datos grande. Revisa sudo journalctl -u mysqld_exporter o sudo journalctl -u postgres_exporter para ver cuál y desactívalo con su opción --no-collect.<nombre> (MySQL) o --no-collector.<nombre> (PostgreSQL).

Conclusión

Tienes MySQL y PostgreSQL publicando sus métricas en Prometheus mediante exportadores con usuarios de solo lectura, limitados en conexiones y accesibles solo desde Prometheus, además de alertas para caídas, saturación de conexiones y deadlocks. Como siguientes pasos, puedes activar la extensión pg_stat_statements y el recolector --collector.stat_statements para ver las consultas más costosas de PostgreSQL, enviar las alertas a Alertmanager o Grafana Alerting, y añadir node_exporter al mismo servidor para correlacionar la carga de la base de datos con CPU, memoria y disco.