Monitoreo con Scripts Bash Personalizados

Introducción

Si bien las soluciones de monitoreo empresariales como Prometheus, Nagios y Zabbix ofrecen características completas, los scripts Bash personalizados proporcionan una alternativa ligera, flexible y rentable para monitorear servidores Linux. Los scripts Bash pueden adaptarse a las necesidades específicas de tu infraestructura, desplegarse instantáneamente sin dependencias e integrarse sin problemas con herramientas y flujos de trabajo existentes.

Los scripts de monitoreo personalizados destacan en escenarios donde necesitas un despliegue rápido, métricas específicas no cubiertas por herramientas estándar o monitoreo ligero para entornos con recursos limitados. Son particularmente valiosos para monitorear aplicaciones personalizadas, aplicar lógica de negocio específica y crear soluciones de monitoreo que se integren directamente con tus sistemas de notificación.

Esta guía completa te enseña cómo construir scripts de monitoreo robustos, desde verificaciones simples de recursos hasta sistemas avanzados de monitoreo multi-servidor. Aprenderás a monitorear CPU, memoria, disco, red, servicios y métricas de aplicaciones personalizadas, implementar mecanismos de alerta, crear dashboards y seguir mejores prácticas para scripts de monitoreo mantenibles y listos para producción.

Requisitos Previos

Antes de construir scripts de monitoreo personalizados, asegúrate de tener:

  • Un servidor Linux (Ubuntu 20.04/22.04, Debian 10/11, CentOS 7/8, Rocky Linux 8/9 o similar)
  • Acceso root o sudo para monitoreo a nivel de sistema
  • Conocimiento básico a intermedio de scripting en Bash
  • Familiaridad con comandos del sistema Linux
  • Editor de texto (nano, vim o vi)

Conocimiento Recomendado:

  • Comprensión de gestión de procesos
  • Conocimiento básico de awk, grep y sed
  • Familiaridad con cron para programación
  • Comprensión de códigos de salida y manejo de errores

Herramientas Opcionales:

  • Agente de transferencia de correo (para alertas por email)
  • curl o wget (para notificaciones webhook)
  • bc (para cálculos de punto flotante)
  • jq (para procesamiento JSON)

Estructura Básica de Scripts de Monitoreo

Componentes Esenciales

Un script de monitoreo bien estructurado debe incluir:

#!/bin/bash
#
# Nombre del Script: monitor-system.sh
# Descripción: Script básico de monitoreo del sistema
# Autor: Tu Nombre
# Fecha: 2024-01-11
# Versión: 1.0

# Salir en caso de error
set -e

# Configuración
LOG_FILE="/var/log/monitoring/system-monitor.log"
ALERT_EMAIL="[email protected]"
HOSTNAME=$(hostname)

# Umbrales
CPU_THRESHOLD=80
MEMORY_THRESHOLD=85
DISK_THRESHOLD=90

# Funciones
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

send_alert() {
    local subject="$1"
    local message="$2"
    echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
    log_message "Alerta enviada: $subject"
}

# Lógica principal de monitoreo
main() {
    log_message "Iniciando monitoreo del sistema"

    # Las verificaciones de monitoreo van aquí

    log_message "Monitoreo completado"
}

# Ejecutar función principal
main "$@"

Manejo de Errores y Registro

#!/bin/bash

# Manejo de errores mejorado
set -euo pipefail  # Salir en error, variables indefinidas, fallos de pipe

# Trampa de errores
trap 'error_handler $? $LINENO' ERR

error_handler() {
    local exit_code=$1
    local line_number=$2
    log_message "ERROR: Script falló en la línea $line_number con código de salida $exit_code"
    send_alert "Script de Monitoreo Falló" \
        "El script falló en $HOSTNAME en la línea $line_number"
    exit "$exit_code"
}

# Registro con niveles
log_level() {
    local level=$1
    shift
    local message="$*"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" | tee -a "$LOG_FILE"
}

log_info() { log_level "INFO" "$@"; }
log_warn() { log_level "WARN" "$@"; }
log_error() { log_level "ERROR" "$@"; }

Scripts de Monitoreo de CPU

Verificación Básica de Uso de CPU

#!/bin/bash
# cpu-monitor.sh - Monitorear uso de CPU

CPU_THRESHOLD=80
LOG_FILE="/var/log/monitoring/cpu.log"

# Obtener uso de CPU (porcentaje de tiempo inactivo restado de 100)
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
CPU_USAGE_INT=${CPU_USAGE%.*}  # Convertir a entero

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log_message "Uso actual de CPU: ${CPU_USAGE}%"

if [ "$CPU_USAGE_INT" -gt "$CPU_THRESHOLD" ]; then
    log_message "ADVERTENCIA: Uso de CPU por encima del umbral (${CPU_USAGE}% > ${CPU_THRESHOLD}%)"

    # Obtener principales consumidores de CPU
    TOP_PROCESSES=$(ps aux --sort=-%cpu | head -n 6)

    # Enviar alerta
    {
        echo "Uso alto de CPU detectado en $(hostname)"
        echo "Uso actual: ${CPU_USAGE}%"
        echo "Umbral: ${CPU_THRESHOLD}%"
        echo ""
        echo "Principales consumidores de CPU:"
        echo "$TOP_PROCESSES"
    } | mail -s "Alerta de CPU: $(hostname)" [email protected]
fi

Monitoreo Avanzado de CPU con Análisis por Núcleo

#!/bin/bash
# cpu-detailed-monitor.sh - Monitoreo detallado de CPU

monitor_cpu_cores() {
    local threshold=$1

    # Obtener uso de CPU por núcleo
    mpstat -P ALL 1 1 | tail -n +4 | while read line; do
        if [[ $line =~ ^[0-9] ]]; then
            core=$(echo "$line" | awk '{print $2}')
            idle=$(echo "$line" | awk '{print $NF}')
            usage=$(echo "100 - $idle" | bc)
            usage_int=${usage%.*}

            echo "Núcleo $core: ${usage}%"

            if (( $(echo "$usage_int > $threshold" | bc -l) )); then
                echo "ADVERTENCIA: Uso del núcleo $core: ${usage}%"
            fi
        fi
    done
}

# Obtener promedios de carga
get_load_average() {
    local load_1min load_5min load_15min
    read load_1min load_5min load_15min < /proc/loadavg

    # Obtener número de núcleos de CPU
    local cpu_cores=$(nproc)

    # Calcular carga por núcleo
    local load_per_core=$(echo "scale=2; $load_1min / $cpu_cores" | bc)

    echo "Promedio de Carga: $load_1min (1m), $load_5min (5m), $load_15min (15m)"
    echo "Núcleos de CPU: $cpu_cores"
    echo "Carga por Núcleo: $load_per_core"

    # Alertar si la carga por núcleo excede 1.0
    if (( $(echo "$load_per_core > 1.0" | bc -l) )); then
        echo "ADVERTENCIA: Alta carga por núcleo: $load_per_core"
        return 1
    fi
}

# Ejecución principal
echo "=== Reporte de Monitoreo de CPU ==="
echo "Marca de tiempo: $(date)"
echo ""

echo "--- Promedio de Carga ---"
get_load_average
echo ""

echo "--- Uso por Núcleo ---"
monitor_cpu_cores 80

Monitoreo de Tiempo de Espera de CPU

#!/bin/bash
# cpu-iowait-monitor.sh - Monitorear tiempo de espera de E/S

IOWAIT_THRESHOLD=20

# Obtener porcentaje de espera de E/S
get_iowait() {
    iostat -c 1 2 | tail -1 | awk '{print $4}'
}

IOWAIT=$(get_iowait)
IOWAIT_INT=${IOWAIT%.*}

echo "Espera de E/S actual: ${IOWAIT}%"

if [ "$IOWAIT_INT" -gt "$IOWAIT_THRESHOLD" ]; then
    echo "ADVERTENCIA: Alta espera de E/S detectada"

    # Identificar procesos intensivos en E/S
    echo ""
    echo "Principales procesos de E/S:"
    sudo iotop -b -n 1 -o | head -n 20

    # Verificar estadísticas de disco
    echo ""
    echo "Estadísticas de E/S de disco:"
    iostat -x 1 2 | tail -n +4
fi

Scripts de Monitoreo de Memoria

Monitor Completo de Memoria

#!/bin/bash
# memory-monitor.sh - Monitoreo completo de memoria

MEMORY_THRESHOLD=85
SWAP_THRESHOLD=50
LOG_FILE="/var/log/monitoring/memory.log"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Obtener estadísticas de memoria
get_memory_stats() {
    local total used free shared buffers cached available
    read -r _ total used free shared buffers cached <<< \
        "$(free -m | grep Mem:)"

    available=$(free -m | grep Mem: | awk '{print $7}')

    local mem_percent=$(echo "scale=2; ($used / $total) * 100" | bc)
    local mem_percent_int=${mem_percent%.*}

    echo "Total: ${total}MB"
    echo "Usado: ${used}MB"
    echo "Libre: ${free}MB"
    echo "Disponible: ${available}MB"
    echo "Uso: ${mem_percent}%"

    if [ "$mem_percent_int" -gt "$MEMORY_THRESHOLD" ]; then
        log_message "ADVERTENCIA: Uso de memoria ${mem_percent}% excede umbral ${MEMORY_THRESHOLD}%"
        return 1
    fi

    return 0
}

# Obtener estadísticas de swap
get_swap_stats() {
    local total used free
    read -r _ total used free <<< "$(free -m | grep Swap:)"

    if [ "$total" -eq 0 ]; then
        echo "Swap: No configurado"
        return 0
    fi

    local swap_percent=$(echo "scale=2; ($used / $total) * 100" | bc)
    local swap_percent_int=${swap_percent%.*}

    echo "Total de Swap: ${total}MB"
    echo "Swap Usado: ${used}MB"
    echo "Uso de Swap: ${swap_percent}%"

    if [ "$swap_percent_int" -gt "$SWAP_THRESHOLD" ]; then
        log_message "ADVERTENCIA: Uso de swap ${swap_percent}% excede umbral ${SWAP_THRESHOLD}%"
        return 1
    fi

    return 0
}

# Obtener principales consumidores de memoria
get_top_memory_processes() {
    echo "Top 10 Procesos que Consumen Memoria:"
    ps aux --sort=-%mem | head -n 11 | awk '{printf "%-10s %-8s %-8s %s\n", $1, $2, $4"%", $11}'
}

# Verificar fugas de memoria
check_memory_growth() {
    local current_usage previous_usage

    # Obtener uso de memoria actual
    current_usage=$(free -m | grep Mem: | awk '{print $3}')

    # Leer uso anterior si existe
    if [ -f /tmp/memory_usage.tmp ]; then
        previous_usage=$(cat /tmp/memory_usage.tmp)

        local growth=$((current_usage - previous_usage))

        echo "Crecimiento de memoria desde última verificación: ${growth}MB"

        if [ "$growth" -gt 500 ]; then
            log_message "ADVERTENCIA: Crecimiento rápido de memoria detectado: ${growth}MB"
        fi
    fi

    # Guardar uso actual
    echo "$current_usage" > /tmp/memory_usage.tmp
}

# Ejecución principal
main() {
    log_message "Iniciando monitoreo de memoria"

    echo "=== Reporte de Monitoreo de Memoria ==="
    echo "Marca de tiempo: $(date)"
    echo ""

    echo "--- Memoria Física ---"
    if ! get_memory_stats; then
        get_top_memory_processes
        echo "" | mail -s "Alerta de Memoria: $(hostname)" [email protected]
    fi

    echo ""
    echo "--- Memoria Swap ---"
    get_swap_stats

    echo ""
    check_memory_growth

    echo ""
    get_top_memory_processes
}

main

Detección de OOM Killer

#!/bin/bash
# oom-monitor.sh - Detectar eventos del Out Of Memory killer

LOG_FILE="/var/log/monitoring/oom.log"

# Verificar eventos del OOM killer
check_oom_events() {
    local oom_events

    # Buscar eventos OOM en logs del kernel de la última hora
    oom_events=$(journalctl -k --since "1 hour ago" | grep -i "Out of memory\|oom-killer\|Killed process")

    if [ -n "$oom_events" ]; then
        echo "Eventos del OOM Killer Detectados:"
        echo "$oom_events"

        # Registrar el evento
        echo "[$(date)] Eventos OOM detectados" >> "$LOG_FILE"
        echo "$oom_events" >> "$LOG_FILE"

        # Enviar alerta
        {
            echo "Eventos Out Of Memory detectados en $(hostname)"
            echo "Hora: $(date)"
            echo ""
            echo "Eventos:"
            echo "$oom_events"
            echo ""
            echo "Estado Actual de Memoria:"
            free -h
            echo ""
            echo "Principales Consumidores de Memoria:"
            ps aux --sort=-%mem | head -n 11
        } | mail -s "Alerta OOM: $(hostname)" [email protected]

        return 1
    fi

    return 0
}

check_oom_events

Scripts de Monitoreo de Disco

Monitor de Espacio en Disco

#!/bin/bash
# disk-monitor.sh - Monitorear uso de espacio en disco

DISK_THRESHOLD=85
LOG_FILE="/var/log/monitoring/disk.log"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Verificar uso de disco para todos los sistemas de archivos montados
check_disk_usage() {
    local alert_triggered=0

    df -h | tail -n +2 | while read filesystem size used avail percent mountpoint; do
        # Eliminar signo % del porcentaje
        usage=${percent%\%}

        echo "Verificando $mountpoint: $percent usado"

        if [ "$usage" -gt "$DISK_THRESHOLD" ]; then
            log_message "ADVERTENCIA: $mountpoint está ${percent} lleno"
            alert_triggered=1

            # Encontrar directorios más grandes
            echo "Directorios más grandes en $mountpoint:"
            du -h "$mountpoint" 2>/dev/null | sort -rh | head -n 10

            # Enviar alerta
            {
                echo "Alerta de espacio en disco en $(hostname)"
                echo "Punto de montaje: $mountpoint"
                echo "Uso: $percent"
                echo "Umbral: ${DISK_THRESHOLD}%"
                echo ""
                echo "Directorios más grandes:"
                du -h "$mountpoint" 2>/dev/null | sort -rh | head -n 10
            } | mail -s "Alerta de Espacio en Disco: $mountpoint" [email protected]
        fi
    done

    return $alert_triggered
}

# Verificar uso de inodos
check_inode_usage() {
    local inode_threshold=85

    df -i | tail -n +2 | while read filesystem inodes iused ifree ipercent mountpoint; do
        # Eliminar signo %
        usage=${ipercent%\%}

        if [ "$usage" -gt "$inode_threshold" ]; then
            log_message "ADVERTENCIA: Uso de inodos en $mountpoint es ${ipercent}"

            {
                echo "Alerta de uso de inodos en $(hostname)"
                echo "Punto de montaje: $mountpoint"
                echo "Uso de inodos: $ipercent"
                echo ""
                echo "Directorios con más archivos:"
                find "$mountpoint" -xdev -type d -exec sh -c \
                    'echo $(find "{}" -maxdepth 1 | wc -l) "{}"' \; 2>/dev/null | \
                    sort -rn | head -n 10
            } | mail -s "Alerta de Inodos: $mountpoint" [email protected]
        fi
    done
}

# Verificar directorios de crecimiento rápido
check_directory_growth() {
    local watch_dirs=("/var/log" "/tmp" "/var/tmp" "/home")

    for dir in "${watch_dirs[@]}"; do
        if [ ! -d "$dir" ]; then
            continue
        fi

        current_size=$(du -sm "$dir" 2>/dev/null | awk '{print $1}')
        state_file="/tmp/dirsize_$(echo $dir | tr '/' '_').tmp"

        if [ -f "$state_file" ]; then
            previous_size=$(cat "$state_file")
            growth=$((current_size - previous_size))

            if [ "$growth" -gt 1000 ]; then  # Alertar si creció >1GB
                log_message "ADVERTENCIA: $dir creció ${growth}MB"

                {
                    echo "Crecimiento rápido de directorio en $(hostname)"
                    echo "Directorio: $dir"
                    echo "Crecimiento: ${growth}MB"
                    echo ""
                    echo "Subdirectorios más grandes:"
                    du -h "$dir" 2>/dev/null | sort -rh | head -n 10
                } | mail -s "Alerta de Crecimiento de Directorio: $dir" [email protected]
            fi
        fi

        echo "$current_size" > "$state_file"
    done
}

# Ejecución principal
main() {
    log_message "Iniciando monitoreo de disco"

    echo "=== Monitoreo de Espacio en Disco ==="
    check_disk_usage

    echo ""
    echo "=== Uso de Inodos ==="
    check_inode_usage

    echo ""
    echo "=== Verificación de Crecimiento de Directorio ==="
    check_directory_growth
}

main

Monitor de Rendimiento de E/S de Disco

#!/bin/bash
# disk-io-monitor.sh - Monitorear rendimiento de E/S de disco

IOPS_THRESHOLD=1000
LATENCY_THRESHOLD=100  # milisegundos

# Obtener estadísticas de E/S de disco
get_disk_io_stats() {
    iostat -x 1 2 | tail -n +4 | tail -n +7 | while read line; do
        device=$(echo "$line" | awk '{print $1}')
        tps=$(echo "$line" | awk '{print $2}')
        read_kb=$(echo "$line" | awk '{print $3}')
        write_kb=$(echo "$line" | awk '{print $4}')
        await=$(echo "$line" | awk '{print $10}')

        echo "Dispositivo: $device"
        echo "  Transacciones/seg: $tps"
        echo "  Lectura: ${read_kb} kB/s"
        echo "  Escritura: ${write_kb} kB/s"
        echo "  Await: ${await} ms"

        # Verificar umbrales
        if (( $(echo "$await > $LATENCY_THRESHOLD" | bc -l) )); then
            echo "  ADVERTENCIA: Alta latencia detectada"

            # Obtener procesos haciendo E/S
            sudo iotop -b -n 1 -o | head -n 20
        fi
    done
}

get_disk_io_stats

Scripts de Monitoreo de Red

Monitor de Tráfico de Red

#!/bin/bash
# network-monitor.sh - Monitorear tráfico de red y conexiones

BANDWIDTH_THRESHOLD=80  # Porcentaje de capacidad de la interfaz
LOG_FILE="/var/log/monitoring/network.log"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Monitorear tráfico de interfaz de red
monitor_interface_traffic() {
    local interface=$1

    # Obtener estadísticas de interfaz
    rx_bytes_before=$(cat /sys/class/net/"$interface"/statistics/rx_bytes)
    tx_bytes_before=$(cat /sys/class/net/"$interface"/statistics/tx_bytes)

    sleep 1

    rx_bytes_after=$(cat /sys/class/net/"$interface"/statistics/rx_bytes)
    tx_bytes_after=$(cat /sys/class/net/"$interface"/statistics/tx_bytes)

    # Calcular bytes por segundo
    rx_bps=$((rx_bytes_after - rx_bytes_before))
    tx_bps=$((tx_bytes_after - tx_bytes_before))

    # Convertir a Mbps
    rx_mbps=$(echo "scale=2; $rx_bps * 8 / 1000000" | bc)
    tx_mbps=$(echo "scale=2; $tx_bps * 8 / 1000000" | bc)

    echo "Interfaz: $interface"
    echo "  RX: ${rx_mbps} Mbps"
    echo "  TX: ${tx_mbps} Mbps"
}

# Verificar cantidad de conexiones
check_connection_count() {
    local established=$(ss -tan | grep ESTAB | wc -l)
    local time_wait=$(ss -tan | grep TIME-WAIT | wc -l)
    local total=$(ss -tan | tail -n +2 | wc -l)

    echo "Estadísticas de Conexión:"
    echo "  Total: $total"
    echo "  Establecidas: $established"
    echo "  Time-Wait: $time_wait"

    if [ "$established" -gt 1000 ]; then
        log_message "ADVERTENCIA: Alto número de conexiones establecidas: $established"

        # Mostrar principales fuentes de conexión
        echo "  Principales fuentes de conexión:"
        ss -tan | grep ESTAB | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -n 10
    fi
}

# Verificar errores de red
check_network_errors() {
    local interface=$1

    local rx_errors=$(cat /sys/class/net/"$interface"/statistics/rx_errors)
    local tx_errors=$(cat /sys/class/net/"$interface"/statistics/tx_errors)
    local rx_dropped=$(cat /sys/class/net/"$interface"/statistics/rx_dropped)
    local tx_dropped=$(cat /sys/class/net/"$interface"/statistics/tx_dropped)

    echo "Errores de interfaz $interface:"
    echo "  Errores RX: $rx_errors"
    echo "  Errores TX: $tx_errors"
    echo "  RX descartados: $rx_dropped"
    echo "  TX descartados: $tx_dropped"

    if [ "$rx_errors" -gt 100 ] || [ "$tx_errors" -gt 100 ]; then
        log_message "ADVERTENCIA: Errores de red detectados en $interface"
    fi
}

# Monitorear disponibilidad de puertos
check_port_listening() {
    local ports=("80" "443" "22" "3306")

    echo "Disponibilidad de Puertos:"
    for port in "${ports[@]}"; do
        if ss -tuln | grep -q ":$port "; then
            echo "  Puerto $port: ESCUCHANDO"
        else
            echo "  Puerto $port: NO ESCUCHANDO"
            log_message "ADVERTENCIA: Puerto $port no está escuchando"
        fi
    done
}

# Ejecución principal
main() {
    local interface=${1:-eth0}

    echo "=== Reporte de Monitoreo de Red ==="
    echo "Marca de tiempo: $(date)"
    echo ""

    echo "--- Tráfico de Interfaz ---"
    monitor_interface_traffic "$interface"

    echo ""
    echo "--- Estadísticas de Conexión ---"
    check_connection_count

    echo ""
    echo "--- Errores de Red ---"
    check_network_errors "$interface"

    echo ""
    echo "--- Estado de Puertos ---"
    check_port_listening
}

main "$@"

Monitor de Conexiones con Geo-IP

#!/bin/bash
# connection-geoip-monitor.sh - Monitorear conexiones con información geográfica

# Verificar patrones de conexión sospechosos
monitor_connections() {
    echo "=== Análisis de Conexiones Activas ==="

    # Obtener IPs únicas con cantidad de conexiones
    ss -tan | grep ESTAB | awk '{print $5}' | cut -d: -f1 | \
        grep -v "^127\|^::1\|^$" | sort | uniq -c | sort -rn | \
        while read count ip; do
            echo "IP: $ip (Conexiones: $count)"

            # Alertar sobre alto número de conexiones desde una IP única
            if [ "$count" -gt 50 ]; then
                echo "  ADVERTENCIA: Alto número de conexiones desde $ip"

                # Obtener información GeoIP si geoiplookup está disponible
                if command -v geoiplookup &> /dev/null; then
                    geo=$(geoiplookup "$ip")
                    echo "  Ubicación: $geo"
                fi
            fi
        done
}

monitor_connections

Scripts de Monitoreo de Servicios

Monitor Completo de Servicios

#!/bin/bash
# service-monitor.sh - Monitorear servicios críticos

SERVICES=("nginx" "mysql" "redis" "ssh")
LOG_FILE="/var/log/monitoring/services.log"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

# Verificar si el servicio está ejecutándose
check_service() {
    local service=$1

    if systemctl is-active --quiet "$service"; then
        echo "Servicio $service: EJECUTÁNDOSE"
        return 0
    else
        echo "Servicio $service: NO EJECUTÁNDOSE"
        log_message "ERROR: Servicio $service no está ejecutándose"

        # Intentar iniciar servicio
        log_message "Intentando iniciar $service"
        if systemctl start "$service"; then
            log_message "Servicio $service iniciado exitosamente"

            # Enviar notificación
            echo "El servicio $service estaba caído y ha sido reiniciado en $(hostname)" | \
                mail -s "Servicio Recuperado: $service" [email protected]
        else
            log_message "ERROR: Falló al iniciar $service"

            # Obtener estado del servicio
            status=$(systemctl status "$service" --no-pager)

            # Enviar alerta
            {
                echo "CRÍTICO: El servicio $service falló al iniciar en $(hostname)"
                echo "Hora: $(date)"
                echo ""
                echo "Estado del Servicio:"
                echo "$status"
                echo ""
                echo "Logs Recientes:"
                journalctl -u "$service" -n 50 --no-pager
            } | mail -s "CRÍTICO: Servicio $service Falló" [email protected]
        fi

        return 1
    fi
}

# Verificar tiempo de respuesta del servicio
check_service_response() {
    local service=$1
    local url=$2
    local max_time=5

    if [ -z "$url" ]; then
        return 0
    fi

    response_time=$(curl -o /dev/null -s -w '%{time_total}' --max-time "$max_time" "$url" 2>/dev/null || echo "timeout")

    if [ "$response_time" = "timeout" ]; then
        echo "  Respuesta: TIMEOUT"
        log_message "ADVERTENCIA: Timeout de respuesta de $service"
        return 1
    else
        echo "  Tiempo de respuesta: ${response_time}s"

        # Alertar si el tiempo de respuesta es lento
        if (( $(echo "$response_time > 2" | bc -l) )); then
            log_message "ADVERTENCIA: Respuesta lenta de $service: ${response_time}s"
        fi
    fi

    return 0
}

# Verificar puertos de servicio
check_service_port() {
    local service=$1
    local port=$2

    if [ -z "$port" ]; then
        return 0
    fi

    if ss -tuln | grep -q ":$port "; then
        echo "  Puerto $port: ESCUCHANDO"
        return 0
    else
        echo "  Puerto $port: NO ESCUCHANDO"
        log_message "ERROR: Puerto $port de $service no escuchando"
        return 1
    fi
}

# Bucle principal de monitoreo
main() {
    log_message "Iniciando monitoreo de servicios"

    echo "=== Reporte de Monitoreo de Servicios ==="
    echo "Marca de tiempo: $(date)"
    echo ""

    local failed_services=0

    for service in "${SERVICES[@]}"; do
        if ! check_service "$service"; then
            ((failed_services++))
        fi

        # Verificaciones específicas por servicio
        case "$service" in
            nginx|apache2)
                check_service_port "$service" "80"
                check_service_response "$service" "http://localhost"
                ;;
            mysql|mariadb)
                check_service_port "$service" "3306"
                ;;
            redis)
                check_service_port "$service" "6379"
                # Probar conexión de redis
                if command -v redis-cli &> /dev/null; then
                    if redis-cli ping &>/dev/null; then
                        echo "  Redis: RESPONDIENDO"
                    else
                        echo "  Redis: NO RESPONDIENDO"
                        log_message "ERROR: Redis no respondiendo"
                    fi
                fi
                ;;
            ssh)
                check_service_port "$service" "22"
                ;;
        esac

        echo ""
    done

    if [ "$failed_services" -gt 0 ]; then
        log_message "ADVERTENCIA: $failed_services servicio(s) fallaron verificaciones"
        exit 1
    fi

    log_message "Todos los servicios operacionales"
    exit 0
}

main

Monitor de Procesos

#!/bin/bash
# process-monitor.sh - Monitorear procesos específicos

# Monitorear proceso por nombre
monitor_process() {
    local process_name=$1
    local min_instances=${2:-1}
    local max_instances=${3:-10}

    # Contar instancias en ejecución
    local count=$(pgrep -c "$process_name" || echo "0")

    echo "Proceso $process_name: $count instancia(s)"

    if [ "$count" -lt "$min_instances" ]; then
        echo "  ADVERTENCIA: Muy pocas instancias (mínimo: $min_instances)"
        log_message "ERROR: Proceso $process_name tiene solo $count instancias (mínimo: $min_instances)"
        return 1
    elif [ "$count" -gt "$max_instances" ]; then
        echo "  ADVERTENCIA: Demasiadas instancias (máximo: $max_instances)"
        log_message "ADVERTENCIA: Proceso $process_name tiene $count instancias (máximo: $max_instances)"
        return 1
    fi

    # Verificar uso de recursos del proceso
    total_cpu=0
    total_mem=0

    while IFS= read -r pid; do
        cpu=$(ps -p "$pid" -o %cpu= | tr -d ' ')
        mem=$(ps -p "$pid" -o %mem= | tr -d ' ')

        total_cpu=$(echo "$total_cpu + $cpu" | bc)
        total_mem=$(echo "$total_mem + $mem" | bc)
    done < <(pgrep "$process_name")

    echo "  CPU Total: ${total_cpu}%"
    echo "  Memoria Total: ${total_mem}%"

    return 0
}

# Monitorear proceso por archivo PID
monitor_pid_file() {
    local pid_file=$1
    local process_name=$2

    if [ ! -f "$pid_file" ]; then
        echo "Proceso $process_name: Archivo PID no encontrado"
        log_message "ERROR: Archivo PID $pid_file no encontrado para $process_name"
        return 1
    fi

    pid=$(cat "$pid_file")

    if kill -0 "$pid" 2>/dev/null; then
        echo "Proceso $process_name (PID: $pid): EJECUTÁNDOSE"
        return 0
    else
        echo "Proceso $process_name (PID: $pid): NO EJECUTÁNDOSE"
        log_message "ERROR: Proceso $process_name con PID $pid no ejecutándose"
        return 1
    fi
}

# Ejemplo de uso
monitor_process "nginx" 1 10
monitor_process "php-fpm" 5 50
monitor_pid_file "/var/run/nginx.pid" "nginx"

Dashboard de Monitoreo Completo

Script de Monitoreo Todo-en-Uno

#!/bin/bash
# comprehensive-monitor.sh - Dashboard completo de monitoreo del sistema

# Configuración
CONFIG_FILE="/etc/monitoring/config.conf"
LOG_DIR="/var/log/monitoring"
ALERT_EMAIL="[email protected]"
HOSTNAME=$(hostname)

# Asegurar que existe el directorio de logs
mkdir -p "$LOG_DIR"

# Cargar configuración si existe
if [ -f "$CONFIG_FILE" ]; then
    source "$CONFIG_FILE"
else
    # Umbrales predeterminados
    CPU_THRESHOLD=80
    MEMORY_THRESHOLD=85
    DISK_THRESHOLD=90
    LOAD_THRESHOLD=2.0
fi

# Códigos de color para salida de terminal
RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
NC='\033[0m' # Sin Color

# Funciones de registro
log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_DIR/monitoring.log"
}

# Seguimiento de estado
declare -a ALERTS=()
declare -a WARNINGS=()
declare -a INFO=()

add_alert() {
    ALERTS+=("$1")
    log_message "ALERTA: $1"
}

add_warning() {
    WARNINGS+=("$1")
    log_message "ADVERTENCIA: $1"
}

add_info() {
    INFO+=("$1")
}

# Verificaciones del sistema
check_cpu() {
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    local cpu_int=${cpu_usage%.*}

    add_info "Uso de CPU: ${cpu_usage}%"

    if [ "$cpu_int" -gt "$CPU_THRESHOLD" ]; then
        add_alert "Uso alto de CPU: ${cpu_usage}%"
        return 1
    fi

    return 0
}

check_memory() {
    local total=$(free -m | grep Mem: | awk '{print $2}')
    local used=$(free -m | grep Mem: | awk '{print $3}')
    local percent=$(echo "scale=2; ($used / $total) * 100" | bc)
    local percent_int=${percent%.*}

    add_info "Uso de Memoria: ${percent}% (${used}MB / ${total}MB)"

    if [ "$percent_int" -gt "$MEMORY_THRESHOLD" ]; then
        add_alert "Uso alto de memoria: ${percent}%"
        return 1
    fi

    return 0
}

check_disk() {
    local failed=0

    while IFS= read -r line; do
        local mountpoint=$(echo "$line" | awk '{print $6}')
        local percent=$(echo "$line" | awk '{print $5}' | tr -d '%')

        add_info "Disco $mountpoint: ${percent}% usado"

        if [ "$percent" -gt "$DISK_THRESHOLD" ]; then
            add_alert "Uso alto de disco en $mountpoint: ${percent}%"
            failed=1
        fi
    done < <(df -h | tail -n +2 | grep -v "tmpfs")

    return $failed
}

check_load() {
    local load_1min=$(cat /proc/loadavg | awk '{print $1}')
    local cpu_cores=$(nproc)
    local load_per_core=$(echo "scale=2; $load_1min / $cpu_cores" | bc)

    add_info "Promedio de Carga: $load_1min (núcleos: $cpu_cores, por-núcleo: $load_per_core)"

    if (( $(echo "$load_per_core > $LOAD_THRESHOLD" | bc -l) )); then
        add_warning "Promedio de carga alto: $load_per_core por núcleo"
        return 1
    fi

    return 0
}

check_services() {
    local services=("nginx" "mysql" "redis" "ssh")
    local failed=0

    for service in "${services[@]}"; do
        if systemctl is-active --quiet "$service" 2>/dev/null; then
            add_info "Servicio $service: ejecutándose"
        else
            if systemctl list-unit-files | grep -q "^${service}.service"; then
                add_alert "Servicio $service: no ejecutándose"
                failed=1
            fi
        fi
    done

    return $failed
}

check_network() {
    local interface=${1:-eth0}

    if [ ! -d "/sys/class/net/$interface" ]; then
        add_warning "Interfaz de red $interface no encontrada"
        return 1
    fi

    local rx_errors=$(cat /sys/class/net/"$interface"/statistics/rx_errors)
    local tx_errors=$(cat /sys/class/net/"$interface"/statistics/tx_errors)

    add_info "Red $interface - Errores RX: $rx_errors, Errores TX: $tx_errors"

    if [ "$rx_errors" -gt 1000 ] || [ "$tx_errors" -gt 1000 ]; then
        add_warning "Alto número de errores de red en $interface"
        return 1
    fi

    return 0
}

# Generar reporte
generate_report() {
    local report=""

    report+="========================================\n"
    report+="Reporte de Monitoreo del Sistema\n"
    report+="========================================\n"
    report+="Hostname: $HOSTNAME\n"
    report+="Marca de tiempo: $(date)\n"
    report+="========================================\n\n"

    # Alertas
    if [ ${#ALERTS[@]} -gt 0 ]; then
        report+="ALERTAS (${#ALERTS[@]}):\n"
        for alert in "${ALERTS[@]}"; do
            report+="  [!] $alert\n"
        done
        report+="\n"
    fi

    # Advertencias
    if [ ${#WARNINGS[@]} -gt 0 ]; then
        report+="ADVERTENCIAS (${#WARNINGS[@]}):\n"
        for warning in "${WARNINGS[@]}"; do
            report+="  [*] $warning\n"
        done
        report+="\n"
    fi

    # Info
    if [ ${#INFO[@]} -gt 0 ]; then
        report+="ESTADO DEL SISTEMA:\n"
        for info in "${INFO[@]}"; do
            report+="  $info\n"
        done
        report+="\n"
    fi

    report+="========================================\n"

    echo -e "$report"
}

# Enviar alertas por email
send_alerts() {
    if [ ${#ALERTS[@]} -gt 0 ]; then
        local subject="ALERTA: $HOSTNAME - ${#ALERTS[@]} alerta(s)"
        generate_report | mail -s "$subject" "$ALERT_EMAIL"
        log_message "Email de alerta enviado a $ALERT_EMAIL"
    fi
}

# Mostrar en terminal con colores
display_colored_report() {
    echo "========================================"
    echo "Reporte de Monitoreo del Sistema"
    echo "========================================"
    echo "Hostname: $HOSTNAME"
    echo "Marca de tiempo: $(date)"
    echo "========================================"
    echo ""

    if [ ${#ALERTS[@]} -gt 0 ]; then
        echo -e "${RED}ALERTAS (${#ALERTS[@]}):${NC}"
        for alert in "${ALERTS[@]}"; do
            echo -e "  ${RED}[!] $alert${NC}"
        done
        echo ""
    fi

    if [ ${#WARNINGS[@]} -gt 0 ]; then
        echo -e "${YELLOW}ADVERTENCIAS (${#WARNINGS[@]}):${NC}"
        for warning in "${WARNINGS[@]}"; do
            echo -e "  ${YELLOW}[*] $warning${NC}"
        done
        echo ""
    fi

    if [ ${#INFO[@]} -gt 0 ]; then
        echo -e "${GREEN}ESTADO DEL SISTEMA:${NC}"
        for info in "${INFO[@]}"; do
            echo "  $info"
        done
        echo ""
    fi

    echo "========================================"
}

# Ejecución principal
main() {
    log_message "Iniciando monitoreo completo del sistema"

    # Ejecutar todas las verificaciones
    check_cpu
    check_memory
    check_disk
    check_load
    check_services
    check_network

    # Mostrar reporte
    if [ -t 1 ]; then
        # Salida de terminal - usar colores
        display_colored_report
    else
        # No-terminal (cron, etc.) - texto plano
        generate_report
    fi

    # Enviar alertas si hay
    send_alerts

    # Guardar reporte en archivo
    generate_report > "$LOG_DIR/latest-report.txt"

    # Retornar código de salida apropiado
    if [ ${#ALERTS[@]} -gt 0 ]; then
        log_message "Monitoreo completado con ${#ALERTS[@]} alertas"
        exit 2
    elif [ ${#WARNINGS[@]} -gt 0 ]; then
        log_message "Monitoreo completado con ${#WARNINGS[@]} advertencias"
        exit 1
    else
        log_message "Monitoreo completado exitosamente"
        exit 0
    fi
}

# Ejecutar
main "$@"

Conclusión

Los scripts de monitoreo Bash personalizados proporcionan un enfoque flexible, ligero y potente para el monitoreo del sistema. Ofrecen despliegue inmediato, personalización completa e integración sin problemas con la infraestructura existente sin la sobrecarga de frameworks de monitoreo complejos.

Ventajas clave del monitoreo Bash personalizado:

  1. Simplicidad - Fácil de entender, modificar y mantener
  2. Portabilidad - Funciona en cualquier distribución Linux sin dependencias
  3. Personalización - Adaptado exactamente a tus necesidades específicas de monitoreo
  4. Integración - Se integra fácilmente con sistemas de alertas y registro existentes
  5. Eficiencia de recursos - Sobrecarga mínima comparado con monitoreo basado en agentes
  6. Despliegue rápido - Sin instalación o configuración de software complejo

Mejores prácticas para scripts de monitoreo en producción:

  • Implementar manejo de errores y registro completo
  • Usar códigos de salida significativos para integración con sistemas de monitoreo
  • Agregar archivos de configuración para gestión fácil de umbrales
  • Incluir comentarios detallados y documentación
  • Probar scripts exhaustivamente antes del despliegue en producción
  • Implementar limitación de tasa para alertas para prevenir fatiga de notificaciones
  • Usar bloqueos para prevenir ejecuciones concurrentes
  • Monitorear los monitores - asegurar que los scripts de monitoreo se estén ejecutando

Si bien los scripts personalizados destacan para casos de uso específicos y monitoreo ligero, considera complementarlos con soluciones de monitoreo empresarial como Prometheus, Grafana o Nagios para entornos complejos que requieren datos históricos, visualización avanzada y capacidades de monitoreo distribuido.

Los scripts de monitoreo presentados en esta guía proporcionan una base sólida que puedes extender y adaptar a tus requisitos específicos, creando una solución de monitoreo robusta que crece con las necesidades de tu infraestructura.