Plan2026/03-decision-monitoring-pbs.md

493 lines
21 KiB
Markdown

# 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 <ip-influxdb> \
--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=<token-generado>
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="<mismo-token-que-pbs-exporter>"
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.
<details>
<summary>Script completo de referencia (click para expandir)</summary>
```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"
```
</details>
---
## 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) │
└──────────────────────────────────────────────────────────┘
```