DEV Community

minnogit
minnogit

Posted on

Watchdog Apache con Bash, Cron e Systemd: monitoraggio automatico di memoria, PID e socket close-wait

In alcuni scenari ad alto carico o in presenza di problemi lato backend, Apache può degradare progressivamente fino a diventare lento, saturare le risorse o smettere di rispondere correttamente.

Per mitigare questo tipo di problemi è possibile implementare un watchdog leggero in Bash che:

  • monitora periodicamente lo stato del sistema;
  • verifica alcune metriche critiche;
  • esegue automaticamente un reload o un restart di Apache;
  • registra gli eventi tramite syslog e journalctl.

Questo approccio è particolarmente utile in ambienti:

  • con reverse proxy Apache;
  • con backend applicativi instabili;
  • con leak di connessioni;
  • dove è necessario un meccanismo di self-healing semplice e trasparente.

Obiettivo dello script

Lo script controlla:

  1. RAM disponibile;
  2. numero di PID usati dal servizio Apache;
  3. connessioni TCP in stato close-wait verso un backend specifico;
  4. health check HTTP locale;

In base alle soglie configurate:

  • esegue un reload di Apache;
  • oppure un restart completo;
  • scrive i dettagli nel journal di systemd.

Script completo

#!/bin/bash

# --- PARAMETRI DI SOGLIA ---
SOGLIA_RAM_FREE_MB=100
SOGLIA_PIDS_PERCENT=60
SOGLIA_close-wait=300

# --- RACCOLTA DATI ---
# Usiamo percorsi assoluti per cron e variabili pulite
RAM_DISPONIBILE_MB=$(/usr/bin/free -m | grep Mem | awk '{print $7}')

# Conteggio in tempo reale delle connessioni orfane in close-wait
close-wait_COUNT=$(/usr/bin/ss -H -tanp state close-wait | /usr/bin/wc -l)

# Gestione PIDs con controllo esistenza file (evita errori se Apache è spento)
PIDS_FILE="/sys/fs/cgroup/system.slice/apache2.service/pids.current"
MAX_FILE="/sys/fs/cgroup/system.slice/apache2.service/pids.max"

if [ -f "$PIDS_FILE" ]; then
    CURRENT_APACHE_PIDS=$(cat "$PIDS_FILE")
    MAX_APACHE_PIDS=$(cat "$MAX_FILE")

    # Se MAX è la stringa "max", o è 0, o è vuoto -> la percentuale è 0 (limite infinito)
    if [ "$MAX_APACHE_PIDS" = "max" ] || [ -z "$MAX_APACHE_PIDS" ] || [ "$MAX_APACHE_PIDS" -eq 0 ] 2>/dev/null; then
        PERCENTUAL_PIDS=0
    else
        PERCENTUAL_PIDS=$(( 100 * CURRENT_APACHE_PIDS / MAX_APACHE_PIDS ))
    fi
else
    PERCENTUAL_PIDS=0
fi

# --- LOGICA DI CONTROLLO ---
AZIONE_NECESSARIA=false
TIPO_AZIONE="reload" # Default

if [ "$RAM_DISPONIBILE_MB" -lt "$SOGLIA_RAM_FREE_MB" ]; then
    MOTIVO="RAM bassa (${RAM_DISPONIBILE_MB}MB)"
    AZIONE_NECESSARIA=true
    TIPO_AZIONE="restart" # Se la RAM è finita, il reload è troppo lento
elif [ "$PERCENTUAL_PIDS" -gt "$SOGLIA_PIDS_PERCENT" ]; then
    MOTIVO="PIDs al ${PERCENTUAL_PIDS}% (${CURRENT_APACHE_PIDS}/${MAX_APACHE_PIDS})"
    AZIONE_NECESSARIA=true
    TIPO_AZIONE="reload"
elif [ "$close-wait_COUNT" -gt "$SOGLIA_close-wait" ]; then
    MOTIVO="Rilevato leak di socket (${close-wait_COUNT} connessioni in close-wait)"
    AZIONE_NECESSARIA=true
    TIPO_AZIONE="reload" # Il reload è sufficiente a liberare i socket orfani
fi

# Check sopravvivenza: se Apache non risponde, forza RESTART a prescindere dalle soglie
if ! /usr/bin/curl -s --max-time 5 http://127.0.0.1/ > /dev/null; then
    MOTIVO="Apache non risponde (Health Check fallito)"
    AZIONE_NECESSARIA=true
    TIPO_AZIONE="restart"
fi

# --- AZIONE ---
if $AZIONE_NECESSARIA; then
    echo "$(date): Allarme: ${MOTIVO}. Eseguo ${TIPO_AZIONE}."
    /usr/bin/logger -t apache_health_watchdog "Allarme: ${MOTIVO}. Eseguo ${TIPO_AZIONE}."

    if [ "$TIPO_AZIONE" == "reload" ]; then
        /usr/bin/systemctl reload apache2

        # Se il reload fallisce (es. kernel già in fork rejected), prova il restart
        if [ $? -ne 0 ]; then
            /usr/bin/logger -t apache_health_watchdog "Reload fallito, forzo restart."
            /usr/bin/systemctl restart apache2
        fi
    else
        /usr/bin/systemctl restart apache2
    fi
fi
Enter fullscreen mode Exit fullscreen mode

Installazione

Salvare lo script in:

/usr/local/sbin/check_system_health.sh
Enter fullscreen mode Exit fullscreen mode

Rendere il file eseguibile:

chmod +x /usr/local/sbin/check_system_health.sh
Enter fullscreen mode Exit fullscreen mode

Configurazione del cron

Aggiungere in /etc/crontab:

*/2 * * * * root flock -n /tmp/apache_watchdog.lock /usr/local/sbin/check_system_health.sh >> /var/log/apache_health_watchdog.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Questo esegue il controllo ogni 2 minuti.

Perché usare flock

flock evita esecuzioni concorrenti dello script.

In condizioni di degrado del sistema:

  • un controllo potrebbe impiegare più tempo del previsto;
  • il cron successivo potrebbe partire mentre il precedente è ancora attivo;
  • si rischiano reload o restart multipli contemporanei.

Con:

flock -n /tmp/apache_watchdog.lock
Enter fullscreen mode Exit fullscreen mode

solo una istanza dello script può essere eseguita alla volta.


Analisi delle metriche monitorate

1. RAM disponibile

free -m
Enter fullscreen mode Exit fullscreen mode

Lo script utilizza:

awk '{print $7}'
Enter fullscreen mode Exit fullscreen mode

che corrisponde alla colonna available.

Se la memoria disponibile scende sotto:

SOGLIA_RAM_FREE_MB=100
Enter fullscreen mode Exit fullscreen mode

viene eseguito un restart completo di Apache.

Questo è utile perché:

  • il reload può richiedere fork aggiuntivi;
  • in condizioni di memoria critica il reload potrebbe peggiorare la situazione;
  • un restart libera immediatamente worker e memoria frammentata.

2. Saturazione PID Apache

Lo script legge direttamente i contatori cgroup di systemd:

/sys/fs/cgroup/system.slice/apache2.service/pids.current
Enter fullscreen mode Exit fullscreen mode

e:

/sys/fs/cgroup/system.slice/apache2.service/pids.max
Enter fullscreen mode Exit fullscreen mode

Questo approccio è molto più affidabile rispetto al parsing di ps.

Se il servizio supera:

SOGLIA_PIDS_PERCENT=60
Enter fullscreen mode Exit fullscreen mode

viene eseguito un reload.

Questo permette di:

  • rigenerare worker;
  • liberare processi stuck;
  • evitare il raggiungimento del limite massimo PID.

3. Socket close-wait

Lo stato close-wait indica:

  • il peer remoto ha chiuso la connessione;
  • il processo locale non ha ancora rilasciato il socket.

Molte connessioni in close-wait possono indicare:

  • leak applicativi;
  • backend instabili;
  • worker Apache bloccati;
  • timeout mal configurati.

Lo script utilizza:

ss -tanp state close-wait
Enter fullscreen mode Exit fullscreen mode

Questo approccio è preferibile rispetto a netstat perché:

  • ss è più moderno;
  • usa meno CPU;
  • legge direttamente dal kernel tramite netlink;
  • è più affidabile sotto alto carico;
  • evita dipendenze da net-tools.

Se il numero supera:

SOGLIA_close-wait=300
Enter fullscreen mode Exit fullscreen mode

viene eseguito un reload.


Logging e monitoraggio

Lo script utilizza:

logger -t apache_health_watchdog
Enter fullscreen mode Exit fullscreen mode

Quindi gli eventi sono consultabili tramite:

journalctl -t apache_health_watchdog
Enter fullscreen mode Exit fullscreen mode

Esempio:

May 18 10:42:01 server apache_health_watchdog: Allarme: Rilevato leak di socket (182 connessioni in close-wait). Eseguo reload.
Enter fullscreen mode Exit fullscreen mode

Per seguire i log in tempo reale:

journalctl -f -t apache_health_watchdog
Enter fullscreen mode Exit fullscreen mode

Quando usare questo approccio

Questo watchdog è particolarmente utile quando:

  • Apache funge da reverse proxy;
  • i backend possono bloccarsi o degradare;
  • non è disponibile un orchestratore avanzato;
  • si vuole una soluzione semplice e immediata;
  • si desidera un meccanismo di self-healing leggero.

Limiti della soluzione

Questo approccio non sostituisce:

  • un monitoraggio completo;
  • metriche Prometheus/Grafana;
  • tracing applicativo;
  • analisi root cause;
  • tuning corretto di Apache e backend.

Il watchdog serve principalmente come:

  • mitigazione automatica;
  • protezione operativa;
  • recovery rapido.

Top comments (0)