monitoring: alertar sobre la cuota y la papelera de MEGA
El incidente del 2026-07-25 (MEGA lleno -> dos CronJobs de backup caídos) solo se detectó por el síntoma. La causa llevaba meses creciendo sin que nada la mirase. - backup-system/mega-quota-exporter: Deployment que sondea `rclone about` y `rclone size` cada 15 min y los sirve a Prometheus vía ServiceMonitor. mega_trash_bytes (usado - visible) es la métrica que habría cazado esto. - monitoring: dos reglas de Grafana, cuota > 80% y papelera > 2GB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# Exporter de la cuota de MEGA.
|
||||
#
|
||||
# Contexto: el 2026-07-25 la papelera de MEGA (que cuenta cuota) llenó los 20GB
|
||||
# de la cuenta y tumbó rclone-mega-backup y reddit-intel-backup con "Request
|
||||
# over quota". El aviso llegó del síntoma (jobs fallidos), no de la causa, que
|
||||
# llevaba meses engordando en silencio. Esto expone la cuota a Prometheus para
|
||||
# poder alertar con días de margen.
|
||||
#
|
||||
# mega_trash_bytes = usado - visible: es la métrica que habría cazado aquello.
|
||||
# Con --mega-hard-delete en las rotaciones debería quedarse cerca de 0; si
|
||||
# vuelve a crecer, es que algún borrado se está yendo a la papelera otra vez.
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: mega-quota-exporter
|
||||
namespace: backup-system
|
||||
data:
|
||||
export.sh: |
|
||||
#!/bin/sh
|
||||
# Imagen BusyBox (rclone) — sin GNU-ismos.
|
||||
set -u
|
||||
|
||||
CONF=/rclone/rclone.conf
|
||||
OUT=/metrics/metrics
|
||||
INTERVAL="${INTERVAL:-900}"
|
||||
|
||||
# Extrae el entero que sigue a "clave". Hay que anclar en la clave y no
|
||||
# limpiar la línea entera: `rclone size --json` saca {"count":N,"bytes":M}
|
||||
# en una sola línea y un sed global concatenaría los dos números.
|
||||
num() { sed -n 's/.*"'"$1"'"[^0-9]*\([0-9][0-9]*\).*/\1/p' "$2" 2>/dev/null | head -1; }
|
||||
|
||||
# Últimos valores buenos: si un scrape falla preferimos republicarlos con
|
||||
# success=0 antes que dejar la serie en NoData y disparar un falso positivo.
|
||||
L_TOTAL=""; L_USED=""; L_FREE=""; L_VB=""; L_VC=""
|
||||
|
||||
while true; do
|
||||
OK=1
|
||||
rclone about mega: --json --config "$CONF" > /tmp/about.json 2>/tmp/about.err || OK=0
|
||||
rclone size mega: --json --config "$CONF" > /tmp/size.json 2>/tmp/size.err || OK=0
|
||||
|
||||
TOTAL=$(num total /tmp/about.json)
|
||||
USED=$(num used /tmp/about.json)
|
||||
FREE=$(num free /tmp/about.json)
|
||||
VB=$(num bytes /tmp/size.json)
|
||||
VC=$(num count /tmp/size.json)
|
||||
|
||||
for v in "$TOTAL" "$USED" "$FREE" "$VB" "$VC"; do
|
||||
[ -n "$v" ] || OK=0
|
||||
done
|
||||
|
||||
if [ "$OK" = "1" ]; then
|
||||
L_TOTAL="$TOTAL"; L_USED="$USED"; L_FREE="$FREE"; L_VB="$VB"; L_VC="$VC"
|
||||
else
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] scrape fallido: $(cat /tmp/about.err /tmp/size.err 2>/dev/null | tr '\n' ' ')"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "# HELP mega_quota_scrape_success 1 si el ultimo sondeo a MEGA funciono."
|
||||
echo "# TYPE mega_quota_scrape_success gauge"
|
||||
echo "mega_quota_scrape_success $OK"
|
||||
echo "# HELP mega_quota_last_scrape_timestamp_seconds Momento del ultimo sondeo."
|
||||
echo "# TYPE mega_quota_last_scrape_timestamp_seconds gauge"
|
||||
echo "mega_quota_last_scrape_timestamp_seconds $(date +%s)"
|
||||
|
||||
if [ -n "$L_TOTAL" ]; then
|
||||
TRASH=$((L_USED - L_VB))
|
||||
[ "$TRASH" -lt 0 ] && TRASH=0
|
||||
echo "# HELP mega_quota_bytes_total Cuota contratada en MEGA."
|
||||
echo "# TYPE mega_quota_bytes_total gauge"
|
||||
echo "mega_quota_bytes_total $L_TOTAL"
|
||||
echo "# HELP mega_quota_bytes_used Espacio consumido (incluye la papelera)."
|
||||
echo "# TYPE mega_quota_bytes_used gauge"
|
||||
echo "mega_quota_bytes_used $L_USED"
|
||||
echo "# HELP mega_quota_bytes_free Espacio libre segun MEGA."
|
||||
echo "# TYPE mega_quota_bytes_free gauge"
|
||||
echo "mega_quota_bytes_free $L_FREE"
|
||||
echo "# HELP mega_visible_bytes Tamano de los ficheros visibles."
|
||||
echo "# TYPE mega_visible_bytes gauge"
|
||||
echo "mega_visible_bytes $L_VB"
|
||||
echo "# HELP mega_visible_objects Numero de ficheros visibles."
|
||||
echo "# TYPE mega_visible_objects gauge"
|
||||
echo "mega_visible_objects $L_VC"
|
||||
echo "# HELP mega_trash_bytes Estimacion de la papelera (usado - visible)."
|
||||
echo "# TYPE mega_trash_bytes gauge"
|
||||
echo "mega_trash_bytes $TRASH"
|
||||
fi
|
||||
} > "${OUT}.tmp" && mv "${OUT}.tmp" "$OUT"
|
||||
|
||||
sleep "$INTERVAL"
|
||||
done
|
||||
nginx.conf: |
|
||||
worker_processes 1;
|
||||
error_log /dev/stderr warn;
|
||||
pid /tmp/nginx.pid;
|
||||
events { worker_connections 64; }
|
||||
http {
|
||||
access_log off;
|
||||
client_body_temp_path /tmp/client_body;
|
||||
proxy_temp_path /tmp/proxy;
|
||||
fastcgi_temp_path /tmp/fastcgi;
|
||||
uwsgi_temp_path /tmp/uwsgi;
|
||||
scgi_temp_path /tmp/scgi;
|
||||
server {
|
||||
listen 8080;
|
||||
# El fichero no tiene extension: sin esto nginx lo sirve como
|
||||
# application/octet-stream y Prometheus se queja del formato.
|
||||
default_type text/plain;
|
||||
location = /metrics { alias /metrics/metrics; }
|
||||
location / { return 404; }
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: mega-quota-exporter
|
||||
namespace: backup-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mega-quota-exporter
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: mega-quota-exporter
|
||||
spec:
|
||||
containers:
|
||||
- name: rclone
|
||||
# OJO: imagen BusyBox — el script debe evitar GNU-ismos
|
||||
image: docker.io/rclone/rclone:1.74.3
|
||||
command: ["/bin/sh", "/scripts/export.sh"]
|
||||
env:
|
||||
- name: INTERVAL
|
||||
value: "900"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
volumeMounts:
|
||||
- mountPath: /rclone
|
||||
name: rclone-conf
|
||||
- mountPath: /scripts
|
||||
name: scripts
|
||||
- mountPath: /metrics
|
||||
name: metrics
|
||||
- name: nginx
|
||||
image: docker.io/library/nginx:1.27-alpine
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: 8080
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
volumeMounts:
|
||||
- mountPath: /etc/nginx/nginx.conf
|
||||
name: scripts
|
||||
subPath: nginx.conf
|
||||
- mountPath: /metrics
|
||||
name: metrics
|
||||
readOnly: true
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: metrics
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 20
|
||||
volumes:
|
||||
# RCLONE_CONF (Infisical /backup-system) contiene mega+b2
|
||||
- name: rclone-conf
|
||||
secret:
|
||||
secretName: rclone-conf-infisical
|
||||
items:
|
||||
- key: RCLONE_CONF
|
||||
path: rclone.conf
|
||||
- name: scripts
|
||||
configMap:
|
||||
name: mega-quota-exporter
|
||||
defaultMode: 0755
|
||||
- name: metrics
|
||||
emptyDir: {}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mega-quota-exporter
|
||||
namespace: backup-system
|
||||
labels:
|
||||
app: mega-quota-exporter
|
||||
spec:
|
||||
selector:
|
||||
app: mega-quota-exporter
|
||||
ports:
|
||||
- name: metrics
|
||||
port: 8080
|
||||
targetPort: metrics
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: mega-quota-exporter
|
||||
namespace: backup-system
|
||||
labels:
|
||||
# Requerido por serviceMonitorSelector de kube-prometheus-stack
|
||||
release: kube-prometheus-stack
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: mega-quota-exporter
|
||||
endpoints:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: 5m
|
||||
Reference in New Issue
Block a user