StackPractices
beginner Por Mathias Paulenko

Monitorear Uso de Disco

Alerta cuando el espacio en disco cruza umbrales usando un script de Bash que verifica mount points y notifica operadores.

Visión General

El espacio en disco es un asesino silencioso de servicios productivos. Cuando un log, caché o base de datos llena el disco, las aplicaciones se caen, las escrituras fallan y la recuperación se vuelve urgente. Un simple script de Bash que verifica mount points y alerta cuando el uso cruza un umbral te da advertencia temprana y puede disparar limpieza antes de que la situación se vuelva crítica.

Cuándo Usar

Usa este recurso cuando:

  • Quieras monitoreo ligero sin instalar un agente completo.
  • Necesites alertar por email, Slack o archivo de log cuando el uso de disco sea alto.
  • Ejecutes contenedores, VMs o servidores bare-metal con disco limitado.
  • Quieras disparar limpieza automática cuando el uso cruza un umbral.

Solución

Script de monitoreo de uso de disco

#!/usr/bin/env bash
set -euo pipefail

THRESHOLD="${1:-80}"
EMAIL="${2:-admin@example.com}"

# Verificar todos los mount points locales
df -Hl | awk 'NR>1 && /^\/dev/{print $1, $5, $6}' | while read -r fs usage mount; do
    usage_val="${usage%%}"
    if (( usage_val >= THRESHOLD )); then
        echo "WARNING: $mount ($fs) is at $usage"
        # Enviar alerta (ejemplo con mail)
        echo "Disk usage on $mount is $usage" | mail -s "Disk alert: $mount" "$EMAIL"
    else
        echo "OK: $mount is at $usage"
    fi
done

# Opcional: disparar limpieza para mounts específicos
# if (( usage_val >= 90 )); then
#     /usr/local/bin/cleanup-logs.sh
# fi

Explicación

El script usa df -Hl para listar filesystems locales y sus porcentajes de uso. awk filtra el encabezado y las entradas que no son dispositivos. Para cada mount point, elimina el signo de porcentaje, compara el valor numérico con el umbral e imprime una advertencia o mensaje OK. Si se excede el umbral, envía un alerta por email. El bloque de limpieza está comentado porque el borrado automático debe revisarse cuidadosamente antes de habilitarse.

Variantes

Canal de alertaHerramientaMejor para
EmailmailServidores simples con MTA local
Slackwebhook curlEquipos que ya usan Slack
PagerDutyevent APIEscalación on-call en producción
Log de archivoredirect a syslogAgregación centralizada de logs

Lo que funciona

  1. Configura umbrales por debajo del 100%. Alerta al 80% y actúa al 90% para tener tiempo de responder.
  2. Monitorea mount points, no solo el disco total. Un /tmp o /var/log pequeño puede llenarse independientemente del disco raíz.
  3. Incluye el filesystem en la alerta. Saber qué partición está llena acelera la respuesta.
  4. Ejecuta desde cron cada pocos minutos. El uso de disco puede crecer rápido durante incidentes.
  5. Empareja monitoreo con limpieza. Una alerta de uso alto sin plan de limpieza es solo media solución.

Errores Comunes

  1. Usar df -h sin cuidado en el parsing. La columna de porcentaje puede estar vacía para filesystems especiales; filtra entradas /dev/.
  2. Alertar demasiado tarde. Esperar al 95% deja casi ningún tiempo para reaccionar.
  3. Ignorar mounts efímeros. /tmp y volúmenes de docker pueden llenarse rápido y colapsar servicios.
  4. Enviar alertas a individuos. Usa un alias de equipo o rotación on-call para que las vacaciones no rompan el alerting.
  5. No manejar fallos de correo. Si el MTA está caído, la alerta nunca llega; registra en un segundo canal.

Mejores Prácticas Adicionales

  1. Monitorea el uso de inodos por separado. Un disco puede tener espacio libre pero quedarse sin inodos (slots de archivos). Esto pasa con workloads que crean millones de archivos pequeños — servidores de mail, directorios de caché, almacenamiento de sesiones. Verifica los inodos con df -i y alerta con los mismos umbrales.

  2. Usa alertas predictivas. En lugar de alertar sobre el uso actual, alerta sobre el uso predicho. Prometheus predict_linear() puede pronosticar cuándo se llenará un disco basándose en la tasa de cambio. Esto te da horas o días de advertencia en lugar de minutos.

  3. Excluye mounts de solo lectura y tmpfs. Filtra tmpfs, devtmpfs, overlay y filesystems de solo lectura del monitoreo. O no se pueden limpiar o los gestiona el kernel:

# Monitorear solo dispositivos de bloque reales, excluyendo tmpfs y overlays
df -Hl -x tmpfs -x devtmpfs -x overlay -x squashfs | awk 'NR>1 && /^\/dev/{print $1, $5, $6}'
  1. Registra tendencias de uso de disco. Loguea el uso diario a un archivo o base de datos. Las tendencias revelan qué mounts crecen más rápido y ayudan a planificar upgrades de capacidad antes de que las alertas se disparen:
# Agregar uso diario a un CSV para análisis de tendencias
echo "$(date -Iseconds),$(hostname),$mount,$usage" >> /var/log/disk-usage-trends.csv

Errores Comunes Adicionales

  1. No monitorear el almacenamiento de Docker. Docker usa /var/lib/docker que puede crecer rápidamente con imágenes, contenedores y volúmenes. Monitorea esta ruta por separado y programa jobs regulares de docker system prune. Una partición de almacenamiento Docker llena impide que nuevos contenedores arranquen.

  2. Olvidar mounts de red montados. NFS y SMB mounts pueden llenarse en el servidor remoto, causando que las escrituras locales se cuelguen. Monitorea mounts de red con timeouts más cortos y alerta sobre latencia además de uso. Usa timeout con df para evitar colgarse en servidores NFS no responsivos:

timeout 10 df -Hl "$NFS_MOUNT" || echo "NFS mount $NFS_MOUNT no responde"
  1. Usar umbrales de porcentaje en discos muy grandes o muy pequeños. En un disco de 100TB, 80% significa 20TB libres — mucho espacio. En un disco de 10GB, 80% significa 2GB libres — crítico. Usa umbrales absolutos en bytes para discos pequeños y umbrales de porcentaje para grandes:
# Alertar si el espacio libre es menor a 5GB O el uso es mayor a 90%
avail_bytes=$(df -B1 "$mount" | awk 'NR>1 {print $4}')
if (( avail_bytes < 5368709120 )) || (( usage_val >= 90 )); then
    echo "WARNING: $mount tiene solo $((avail_bytes / 1073741824))GB libres"
fi

Preguntas frecuentes

Integración de alertas por Slack
#!/usr/bin/env bash
set -euo pipefail

SLACK_WEBHOOK="${SLACK_WEBHOOK:-https://hooks.slack.com/services/XXX}"
THRESHOLD="${1:-80}"
HOSTNAME=$(hostname)

send_slack_alert() {
    local mount="$1" usage="$2" fs="$3"
    local payload
    payload=$(cat <<EOF
{
    "text": "Alerta de disco en ${HOSTNAME}",
    "attachments": [{
        "color": "danger",
        "fields": [
            {"title": "Mount", "value": "${mount}", "short": true},
            {"title": "Uso", "value": "${usage}", "short": true},
            {"title": "Filesystem", "value": "${fs}", "short": true},
            {"title": "Host", "value": "${HOSTNAME}", "short": true}
        ]
    }]
}
EOF
)
    curl -s -X POST -H 'Content-Type: application/json' -d "$payload" "$SLACK_WEBHOOK"
}

df -Hl | awk 'NR>1 && /^\/dev/{print $1, $5, $6}' | while read -r fs usage mount; do
    usage_val="${usage%\%}"
    if (( usage_val >= THRESHOLD )); then
        send_slack_alert "$mount" "$usage" "$fs"
        echo "[$(date -Iseconds)] WARNING: $mount ($fs) at $usage — alerta Slack enviada"
    fi
done
Monitoreo de inodos (el disco puede tener espacio pero no inodos)
#!/usr/bin/env bash
set -euo pipefail

INODE_THRESHOLD="${1:-80}"

df -i | awk 'NR>1 && /^\/dev/{print $1, $5, $6}' | while read -r fs iusage mount; do
    iusage_val="${iusage%\%}"
    if (( iusage_val >= INODE_THRESHOLD )); then
        echo "WARNING: $mount ($fs) uso de inodos en $iusage"
        # Encontrar directorios con más archivos
        top_dirs=$(find "$mount" -xdev -type d -exec sh -c 'echo $(find "$0" -maxdepth 1 -type f | wc -l) "$0"' {} \; 2>/dev/null | sort -rn | head -5)
        echo "Directorios con más archivos:"
        echo "$top_dirs"
    fi
done
Timer de systemd para monitoreo de disco (reemplaza cron)
# /etc/systemd/system/disk-monitor.service
[Unit]
Description=Monitoreo de uso de disco
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/disk-monitor.sh 80 admin@example.com
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/disk-monitor.timer
[Unit]
Description=Ejecutar monitor de disco cada 5 minutos

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
AccuracySec=30s

[Install]
WantedBy=timers.target
# Habilitar e iniciar el timer
sudo systemctl daemon-reload
sudo systemctl enable --now disk-monitor.timer
systemctl list-timers disk-monitor.timer
Alertas de disco con Prometheus node_exporter
# prometheus-alerts.yml
groups:
  - name: disk_usage
    rules:
      - alert: HighDiskUsage
        expr: |
          (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Uso de disco alto en {{ $labels.instance }}"
          description: "{{ $labels.mountpoint }} está al {{ printf \"%.1f\" $value }}% de uso"

      - alert: CriticalDiskUsage
        expr: |
          (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 > 90
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "Uso de disco crítico en {{ $labels.instance }}"
          description: "{{ $labels.mountpoint }} está al {{ printf \"%.1f\" $value }}% de uso"

      - alert: HighInodeUsage
        expr: |
          (1 - node_filesystem_files_free / node_filesystem_files) * 100 > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Uso de inodos alto en {{ $labels.instance }}"
          description: "{{ $labels.mountpoint }} tiene {{ printf \"%.1f\" $value }}% de uso de inodos"

      - alert: DiskWillFillIn24h
        expr: |
          predict_linear(node_filesystem_avail_bytes[1h], 24 * 3600) < 0
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "El disco se llenará en 24h en {{ $labels.instance }}"
          description: "{{ $labels.mountpoint }} predicho a llenarse según la tasa actual"
Limpieza automática con trigger de rotación de logs
#!/usr/bin/env bash
set -euo pipefail

CLEANUP_THRESHOLD="${1:-90}"
LOG_DIR="${2:-/var/log/app}"
ARCHIVE_DIR="${3:-/var/log/archive}"

df -Hl | awk 'NR>1 && /^\/dev/{print $1, $5, $6}' | while read -r fs usage mount; do
    usage_val="${usage%\%}"
    if (( usage_val >= CLEANUP_THRESHOLD )); then
        echo "[$(date -Iseconds)] CRITICAL: $mount at $usage — disparando limpieza"

        # Comprimir logs con más de 7 días
        find "$LOG_DIR" -name "*.log" -mtime +7 -exec gzip -9 {} \;

        # Mover logs comprimidos a archivo
        find "$LOG_DIR" -name "*.gz" -mtime +1 -exec mv {} "$ARCHIVE_DIR/" \;

        # Eliminar archivos con más de 90 días
        find "$ARCHIVE_DIR" -name "*.gz" -mtime +90 -delete

        # Limpiar caché del gestor de paquetes
        if command -v apt-get &>/dev/null; then
            apt-get clean
        elif command -v yum &>/dev/null; then
            yum clean all
        fi

        # Limpiar Docker si está presente
        if command -v docker &>/dev/null; then
            docker system prune -f --volumes
        fi

        # Reportar espacio liberado
        new_usage=$(df -Hl "$mount" | awk 'NR>1 {print $5}')
        echo "[$(date -Iseconds)] Limpieza completa. $mount ahora en $new_usage"
    fi
done