# Decision: Monitoring especifico de PBS (punto 1.3) ## El problema PBS no tiene exporter Prometheus nativo. Las metricas hay que extraerlas de alguna forma. Evaluamos 3 vias para alimentar VictoriaMetrics/Grafana con datos de PBS. --- ## Via A: PBS metricas nativas a InfluxDB ### Que es PBS tiene soporte nativo para enviar metricas a InfluxDB. Se configura desde la GUI o CLI sin instalar nada en el PBS. ### Como funciona ``` PBS ----(push nativo)----> InfluxDB ----(datasource)----> Grafana ^ VictoriaMetrics ---------(datasource)---------------------+ ``` Grafana lee de dos backends: VictoriaMetrics (infra) e InfluxDB (PBS). ### Configuracion ```bash # En cada PBS, añadir metric server proxmox-backup-manager metric-server add influxdb mi-influxdb \ --server \ --port 8089 \ --protocol udp ``` ### Metricas que envia PBS nativamente - Uso de CPU, RAM, IO del host PBS - Espacio total/usado/disponible por datastore - Estado de GC (running, last-run) - Trafico de red - Estadisticas de chunks (deduplication ratio) ### Lo que NO envia nativamente - Estado de sync jobs (exito/fallo, ultimo run, bytes transferidos) - Estado de verify jobs (errores encontrados, ultimo run) - Ultimo backup por datastore/cliente - Quota vs uso real por cliente - Lista de snapshots y su antiguedad - Estado de maintenance mode ### Veredicto **Insuficiente como solucion unica.** Util como complemento rapido para metricas basicas de espacio, pero no cubre lo operacionalmente critico. --- ## Via B: natrontech/pbs-exporter + script complementario (RECOMENDADA) ### Que es Combinacion de un exporter comunitario maduro en Go (`natrontech/pbs-exporter`) que cubre datastores, snapshots y host, complementado con un script ligero via textfile collector para las metricas de jobs (GC, verify, sync) que el exporter no cubre. ### pbs-exporter (natrontech) **Repositorio**: https://github.com/natrontech/pbs-exporter **Lenguaje**: Go | **Licencia**: GPL-3.0 | **Version**: v0.8.0 (dic 2025) | **Stars**: 45 **Testado con**: PBS 3.x #### Metricas que expone ```prometheus # === SISTEMA PBS === pbs_up # 1 si PBS responde, 0 si no pbs_version{version="3.3-1"} # Version de PBS # === ALMACENAMIENTO POR DATASTORE === pbs_available{datastore="cliente1"} # Bytes disponibles pbs_size{datastore="cliente1"} # Bytes totales pbs_used{datastore="cliente1"} # Bytes usados # === SNAPSHOTS === pbs_snapshot_count{datastore="cliente1"} # Total snapshots pbs_snapshot_vm_count{id="101",datastore=".."} # Snapshots por VM pbs_snapshot_vm_last_timestamp{id="101",...} # Timestamp ultimo backup por VM pbs_snapshot_vm_last_verify{id="101",...} # Estado verificacion ultimo backup # === HOST === pbs_host_cpu_usage # Uso CPU pbs_host_memory_free / _total / _used # RAM pbs_host_swap_free / _total / _used # Swap pbs_host_disk_available / _total / _used # Disco pbs_host_uptime # Uptime en segundos pbs_host_io_wait # IO wait pbs_host_load1 / _load5 / _load15 # Load averages # === SUSCRIPCION === pbs_host_subscription_status # Estado licencia pbs_host_subscription_due_timestamp_seconds # Expiracion ``` #### Modo multi-target (clave para escalar) Un solo pbs-exporter puede monitorizar multiples PBS: ```yaml # vmagent scrape config - job_name: 'pbs' metrics_path: /metrics params: module: [default] static_configs: - targets: - https://pbs-server1:8007 - https://pbs-server2:8007 - https://pbs-server3:8007 relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance - target_label: __address__ replacement: pbs-exporter:10019 # un solo exporter central ``` Esto significa: **1 instancia de pbs-exporter para N servidores PBS**. No hay que instalar nada en cada PBS. #### Despliegue **Opcion 1: Docker (recomendado para centralizacion)** ```yaml services: pbs-exporter: image: ghcr.io/natrontech/pbs-exporter:latest container_name: pbs-exporter restart: unless-stopped environment: - PBS_API_TOKEN_NAME=pbs-exporter - PBS_API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - PBS_USERNAME=monitoring@pbs - PBS_INSECURE=true ports: - "10019:10019" ``` **Opcion 2: Binario + systemd (en cada PBS si no se usa multi-target)** ```bash # Crear usuario dedicado en PBS (minimos privilegios) proxmox-backup-manager user create monitoring@pbs proxmox-backup-manager user generate-token monitoring@pbs pbs-exporter # Dar solo permisos Audit proxmox-backup-manager acl update / Audit --auth-id monitoring@pbs # Descargar binario wget https://github.com/natrontech/pbs-exporter/releases/download/v0.8.0/pbs-exporter_linux_amd64 chmod +x pbs-exporter_linux_amd64 mv pbs-exporter_linux_amd64 /usr/local/bin/pbs-exporter # Systemd unit cat > /etc/systemd/system/pbs-exporter.service << 'EOF' [Unit] Description=PBS Prometheus Exporter After=network.target proxmox-backup.service [Service] Type=simple EnvironmentFile=/etc/default/pbs-exporter ExecStart=/usr/local/bin/pbs-exporter Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF # Configuracion cat > /etc/default/pbs-exporter << 'EOF' PBS_API_TOKEN_NAME=pbs-exporter PBS_API_TOKEN= PBS_USERNAME=monitoring@pbs PBS_ENDPOINT=https://localhost:8007 PBS_INSECURE=true PBS_LISTEN_ADDRESS=:10019 EOF systemctl enable --now pbs-exporter ``` #### Lo que NO cubre pbs-exporter - Estado de GC jobs (ultimo run, duracion, espacio liberado) - Estado de sync jobs (exito/fallo, ultimo run, bytes, remote) - Estado de verify jobs (como job programado, no como verificacion de snapshot) - Maintenance mode - Quota/reservation ZFS por datastore ### Script complementario para jobs (textfile collector) Solo cubre lo que pbs-exporter no tiene. ~40 lineas. ```bash #!/bin/bash # /usr/local/sbin/pbs-jobs-metrics.sh # Complementa pbs-exporter con metricas de GC, verify y sync jobs # Cron: */10 * * * * /usr/local/sbin/pbs-jobs-metrics.sh OUTPUT="/var/lib/node_exporter/textfile/pbs_jobs.prom" TMPFILE="${OUTPUT}.tmp" PBS_API="https://localhost:8007/api2/json" # Autenticacion con API token (sin password) TOKEN_NAME="monitoring@pbs!pbs-exporter" TOKEN_VALUE="" AUTH_HEADER="Authorization: PBSAPIToken=${TOKEN_NAME}:${TOKEN_VALUE}" api_get() { curl -sk -H "$AUTH_HEADER" "${PBS_API}${1}" } > "$TMPFILE" # GC jobs api_get "/admin/gc" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } store=$(_jq '.store') last=$(_jq '."last-run-endtime"') state=$(_jq '."last-run-state"') ok=0; [ "$state" = "ok" ] && ok=1 echo "pbs_gc_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE" echo "pbs_gc_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE" done # Verify jobs api_get "/admin/verify" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } store=$(_jq '.store') last=$(_jq '."last-run-endtime"') state=$(_jq '."last-run-state"') ok=0; [ "$state" = "ok" ] && ok=1 echo "pbs_verify_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE" echo "pbs_verify_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE" done # Sync jobs api_get "/admin/sync" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } store=$(_jq '.store') remote=$(_jq '.remote') last=$(_jq '."last-run-endtime"') state=$(_jq '."last-run-state"') ok=0; [ "$state" = "ok" ] && ok=1 echo "pbs_sync_last_run_timestamp{datastore=\"${store}\",remote=\"${remote}\"} ${last}" >> "$TMPFILE" echo "pbs_sync_last_status{datastore=\"${store}\",remote=\"${remote}\"} ${ok}" >> "$TMPFILE" done mv "$TMPFILE" "$OUTPUT" ``` ### Arquitectura resultante ``` vmagent scrape │ ├── :9100 node_exporter (sistema + ZFS quotas) │ └── textfile/pbs_jobs.prom (GC/verify/sync jobs via script) │ └── :10019 pbs-exporter (datastores, snapshots, ultimo backup por VM, host) └── multi-target: puede cubrir N servidores PBS ``` **Un unico backend**: VictoriaMetrics. Sin InfluxDB. Todo en PromQL/MetricsQL. ### Ventajas de esta combinacion - pbs-exporter hace el 80% del trabajo pesado (datastores, snapshots por VM, host) - Script de 40 lineas cubre el 20% restante (jobs) - Mismo token de API para ambos (un solo usuario `monitoring@pbs`) - Multi-target: un pbs-exporter central para todos los PBS - Si el exporter crece y añade metricas de jobs en el futuro, se elimina el script ### Desventajas - Dos fuentes de metricas PBS (exporter + textfile), pero ambas en el mismo backend - El script tiene resolucion de cron (10 min), el exporter es real-time - Depende de un proyecto comunitario (45 stars, pero activo y en Go) --- ## Via C: Script completo custom (textfile collector) - DESCARTADA como principal La Via B original del documento anterior. Sigue siendo valida como fallback si pbs-exporter deja de mantenerse o tiene algun problema, pero no tiene sentido reescribir en bash lo que ya existe en Go con mejor rendimiento y multi-target. Se mantiene el script completo en la seccion de referencia por si fuera necesario en el futuro.
Script completo de referencia (click para expandir) ```bash #!/bin/bash # /usr/local/sbin/pbs-metrics-exporter.sh # Version completa: genera TODAS las metricas PBS # Solo usar si pbs-exporter de natrontech no esta disponible OUTPUT="/var/lib/node_exporter/textfile/pbs_metrics.prom" TMPFILE="${OUTPUT}.tmp" PBS_API="https://localhost:8007/api2/json" PBS_USER="root@pam" PBS_PASS="$(cat /etc/pbs-monitoring-password)" TICKET_DATA=$(curl -sk -d "username=${PBS_USER}&password=${PBS_PASS}" \ "${PBS_API}/access/ticket") TICKET=$(echo "$TICKET_DATA" | jq -r '.data.ticket') CSRF=$(echo "$TICKET_DATA" | jq -r '.data.CSRFPreventionToken') api_get() { curl -sk -b "PBSAuthCookie=${TICKET}" -H "CSRFPreventionToken: ${CSRF}" \ "${PBS_API}${1}" } > "$TMPFILE" # Datastore usage api_get "/status/datastore-usage" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } name=$(_jq '.store') echo "pbs_datastore_size_bytes{datastore=\"${name}\"} $(_jq '.total')" >> "$TMPFILE" echo "pbs_datastore_used_bytes{datastore=\"${name}\"} $(_jq '.used')" >> "$TMPFILE" echo "pbs_datastore_available_bytes{datastore=\"${name}\"} $(_jq '.avail')" >> "$TMPFILE" done # GC jobs api_get "/admin/gc" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } store=$(_jq '.store'); last=$(_jq '."last-run-endtime"') state=$(_jq '."last-run-state"'); ok=0; [ "$state" = "ok" ] && ok=1 echo "pbs_gc_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE" echo "pbs_gc_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE" done # Verify jobs api_get "/admin/verify" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } store=$(_jq '.store'); last=$(_jq '."last-run-endtime"') state=$(_jq '."last-run-state"'); ok=0; [ "$state" = "ok" ] && ok=1 echo "pbs_verify_last_run_timestamp{datastore=\"${store}\"} ${last}" >> "$TMPFILE" echo "pbs_verify_last_status{datastore=\"${store}\"} ${ok}" >> "$TMPFILE" done # Sync jobs api_get "/admin/sync" | jq -r '.data[] | @base64' | while read row; do _jq() { echo "$row" | base64 -d | jq -r "$1 // 0"; } store=$(_jq '.store'); remote=$(_jq '.remote') last=$(_jq '."last-run-endtime"') state=$(_jq '."last-run-state"'); ok=0; [ "$state" = "ok" ] && ok=1 echo "pbs_sync_last_run_timestamp{datastore=\"${store}\",remote=\"${remote}\"} ${last}" >> "$TMPFILE" echo "pbs_sync_last_status{datastore=\"${store}\",remote=\"${remote}\"} ${ok}" >> "$TMPFILE" done mv "$TMPFILE" "$OUTPUT" ```
--- ## Comparativa final actualizada | Aspecto | A: InfluxDB nativo | B: pbs-exporter + script (RECOMENDADA) | C: Script completo custom | |---------|-------------------|---------------------------------------|--------------------------| | Esfuerzo de setup | 5 minutos | 30 min exporter + 1h script | 2 horas | | Datastores (espacio) | Si | Si (exporter) | Si | | Snapshots por VM | No | Si (exporter) | Posible (mas script) | | Ultimo backup por VM | No | Si (exporter) | Posible (mas script) | | GC jobs | Basico | Si (script) | Si | | Verify jobs | No | Si (script) | Si | | Sync jobs | No | Si (script) | Si | | Host metrics | Si | Si (exporter) | Manual | | Multi-target (1 instancia para N PBS) | N/A | Si (exporter) | No | | Backend | InfluxDB (extra) | VictoriaMetrics (unificado) | VictoriaMetrics (unificado) | | Dependencias | InfluxDB | Binario Go + bash/curl/jq | bash/curl/jq | | Resolucion | ~30s | Real-time (exporter) + 10min (script) | 5-15 min | | Mantenimiento | Nulo | Bajo | Medio | | Alertas PromQL | No | Si | Si | --- ## Recomendacion final ### Despliegue en fases **Fase 1 (dia 1): pbs-exporter centralizado** 1. Crear usuario `monitoring@pbs` + API token en cada PBS (Ansible playbook) 2. Desplegar un pbs-exporter en Docker (VM de monitoring) en modo multi-target 3. Configurar vmagent para scrapear todos los PBS via multi-target 4. Dashboard Grafana basico: datastores, espacio, ultimo backup por VM **Fase 2 (dia 2-3): script complementario de jobs** 1. Desplegar `pbs-jobs-metrics.sh` en cada PBS via Ansible 2. Configurar cron cada 10 minutos 3. node_exporter ya esta instalado (del punto 1.2), solo añadir textfile path 4. Dashboard Grafana: añadir paneles de GC, verify, sync jobs **Fase 3 (semana 2): alertas** 1. Configurar alertas en Grafana o vmalert: - `pbs_snapshot_vm_last_timestamp` > 48h sin backup - `pbs_gc_last_status` != 1 - `pbs_verify_last_status` != 1 - `pbs_sync_last_status` != 1 - `pbs_used / pbs_size` > 0.90 2. Notificaciones: Telegram para criticas, email para warnings **Fase 4 (mes 2): capacity planning** 1. Dashboard de tendencias de crecimiento por datastore 2. Prediccion de llenado con `predict_linear()` de PromQL 3. Deteccion de quotas sobredimensionadas --- ## Dashboards PBS para Grafana ### Dashboard 1: Vista global PBS (para el NOC / pantalla grande) ``` ┌─────────────────────────────────────────────────────────┐ │ PBS GLOBAL STATUS │ ├──────────────┬──────────────┬──────────────┬────────────┤ │ Datastores │ Espacio │ Jobs OK │ Alertas │ │ 42 │ 78% usado │ 39/42 │ 3 │ ├──────────────┴──────────────┴──────────────┴────────────┤ │ │ │ [Tabla] Datastores con problemas │ │ - cliente7: verify fallido hace 5 dias │ │ - cliente12: sync fallido, ultimo ok hace 45 dias │ │ - cliente23: espacio > 90% quota │ │ │ ├──────────────────────────────────────────────────────────┤ │ [Barras] Top 10 datastores por ocupacion │ │ ████████████████████░░░░ cliente3 82% │ │ ███████████████████░░░░░ cliente1 78% │ │ ... │ ├──────────────────────────────────────────────────────────┤ │ [Timeline] Jobs ejecutados hoy │ │ 09:00 GC cliente1 OK (12min) │ │ 09:30 GC cliente2 OK (8min) │ │ 10:00 Verify cliente5 OK (45min) │ │ 10:30 Verify cliente8 FAIL (errors: 2) │ └──────────────────────────────────────────────────────────┘ ``` ### Dashboard 2: Detalle por datastore (para operaciones) ``` ┌─────────────────────────────────────────────────────────┐ │ DATASTORE: cliente1 Status: ACTIVE │ ├──────────────┬──────────────┬──────────────┬────────────┤ │ Espacio │ Quota │ Dedup ratio │ Snapshots │ │ 322 GB │ 600 GB │ 1.42x │ 45 │ ├──────────────┴──────────────┴──────────────┴────────────┤ │ [Grafico linea] Espacio usado ultimos 90 dias │ │ (tendencia de crecimiento + prediccion de llenado) │ ├──────────────────────────────────────────────────────────┤ │ JOBS │ │ GC: ultimo 2h ago, duro 12min, libero 5.2GB [OK]│ │ Verify: ultimo 1d ago, 45 snaps ok, 0 errores [OK]│ │ Sync: ultimo 25d ago, desde pbs3343, 10.7GB [OK]│ │ Proximo sync: dia 15 a las 09:23 │ ├──────────────────────────────────────────────────────────┤ │ [Tabla] Snapshots recientes │ │ vm/101 2026-03-12 12.3GB verified │ │ vm/101 2026-03-11 12.1GB verified │ │ vm/102 2026-03-12 8.7GB outdated │ └──────────────────────────────────────────────────────────┘ ``` ### Dashboard 3: Capacidad y planificacion (para management) ``` ┌─────────────────────────────────────────────────────────┐ │ CAPACITY PLANNING │ ├──────────────────────────────────────────────────────────┤ │ Pool9: 85TB usados / 120TB total (71%) │ │ [Grafico] Tendencia 12 meses + prediccion │ │ "Al ritmo actual, pool9 se llena en 8 meses" │ ├──────────────────────────────────────────────────────────┤ │ [Tabla] Clientes por crecimiento mensual │ │ cliente3: +15GB/mes (el que mas crece) │ │ cliente1: +12GB/mes │ │ cliente7: +8GB/mes │ ├──────────────────────────────────────────────────────────┤ │ [Tabla] Clientes con quota sobredimensionada │ │ cliente15: usa 20GB de 600GB quota (3%) │ │ cliente22: usa 45GB de 600GB quota (7.5%) │ │ "Oportunidad de reclamar 1.1TB de quota" │ ├──────────────────────────────────────────────────────────┤ │ Resumen comercial │ │ Clientes activos: 40 │ │ GB comerciales vendidos: 8,200 GB │ │ GB reales usados: 28,500 GB (ratio 3.5x real) │ │ GB quota asignada: 49,200 GB (ratio 6x) │ └──────────────────────────────────────────────────────────┘ ```