Archivi tag: for

clean_zombies: script bash per ripulire il sistema dai processi zombie

Tenere sotto controllo il numero di processi attivi sulla nostra macchina è certamente una buona pratica, soprattutto quando si parla di sistemi in produzione. Tale monitoraggio può essere realizzato mediante il buon vecchio Nagios, il quale provvederà ad inviarci un’opportuna notifica nel caso in cui il numero dei processi superi una determinata soglia (definibile a piacere).

bashOccorre precisare, però, che nel computo totale rientrano anche i processi zombie, i quali, per definizione, non possono essere killati (sono già in stato defunct). Ergo, per sbarazzarsene occorrerà “fermare” il processo padre (o parent process). Ora, poichè non esiste pratica più noisa dell’andare a ricercare i padri dei vari processi zombie, ho deciso di realizzare il seguente scrip bash in grado di automatizzare il task in questione (con relativo respawning del parent process):

parents=`ps -ef | grep defunct | grep -v grep | awk '{print $3}' | sort -u | wc -l`

p=1

parentidlist=`ps -ef | grep defunct | grep -v grep | awk '{print $3}' | sort -u`

if [[ ! -z "$parents" ]] && [[ "$parents" -gt 0 ]];then
        while [[ "$p" -le "$parents" ]]
        do
                parentid=`echo $parentidlist | awk -v p="$p" '{print $p}'`
                parentname=`ps -o cmd -p $parentid | grep -v CMD`

                if [[ ! -z "$parentid" ]] && [[ ! -z "$parentname" ]];then
                        echo "Terminating $parentname with PID $parentid..."
                        kill -15 "$parentid"
                        sleep 10
                        running=`ps aux | grep "$parentname" | grep -v grep`
                        if [[ -z "$running" ]];then
                                echo "Starting $parentname..."
                                eval $parentname
                        else
                                echo "Error: the process $parentname is still running, switching to the next one..."
                        fi
                fi

                let p=p+1
        done
else
        echo "No defunct process, exiting..."
        exit 1
fi

Il suddetto codice è abbastanza intuitivo. Per prima cosa individuo il numero totale e la lista dei PPID (parent process id) associati a ciascun processo in stato defunct (se esistono). A questo punto, mediante un banale ciclo while, “termino” i processi padre ricavati in precedenza, con successivo respawning degli stessi (sempre che il kill -15 aka SIGTERM sia andato in buon fine, altrimenti skip e si procede con il PPID successivo). Da notare che ho utilizzato la direttiva eval piuttosto che exec, poichè, in quest’ultimo caso, veniva forzata l’uscita dal while.

Se volete fare qualche test potete utilizzare quest’altro scrip bash (fork_zombies) che ho creato allo scopo:

#!/bin/bash

(sleep 1 & exec /bin/sleep 60)

è sufficiente lanciarne qualche istanza in background mediante il comando:

[root@linuxbox ~]# ./fork_zombies &

Nel prossimo post vedremo come integrare il suddetto scrip con Nagios (sotto forma di event handler).

Alla prossima.

Demone nodejs per CentOS 6

Come già affermato in questo post, nodejs ha rappresentato una vera e propria rivoluzione nella programmazione Web, in quanto ha consentito ad un linguaggio nativamente client-side come javascrip, di essere riadattato ed utilizzato come linguaggio server-side.

nodejs

Il funzionamento del suddetto applicativo è abbastanza banale: si richiama il binario da CLI e gli si da in pasto il file di configurazione del “server javascrip”, ad esempio:

[root@linuxbox ~]# node server.js

Per lasciarlo attivo in background occorre utilizzare un tool apposito come screen oppure un semplice nohup:

[root@linuxbox ~]# nohup node server.js

Va da se che tale soluzione per “demonizzare” nodejs non è proprio pulitissima ed è per questa ragione che ho deciso di creare un demone ad hoc per il suddetto applicativo:

 #!/bin/bash

# chkconfig: 2345 95 20
# description: nodejs service
# processname: nodejs

. /etc/rc.d/init.d/functions

USER="node"

DAEMON="/usr/bin/node"
ROOT_DIR="/home/nodejs"

SERVER="$ROOT_DIR/server.js"

for i in {1..8}

do

        do_start()
        {
                PORT="900$i"
                LOG_FILE="$ROOT_DIR/server$i.js.log"

                result=`ps aux | grep "server.js $PORT" | grep -v grep`
                if [ -z "$result" ] ; then
                echo -n $"Starting $SERVER $i: "
                runuser -l "$USER" -c "$DAEMON $SERVER $PORT >> $LOG_FILE &" && echo_success || echo_failure
                RETVAL=$?
                echo
                [ $RETVAL -eq 0 ]
                else
                echo "$SERVER $i already started."
                RETVAL=1
                fi
        }

        do_status()
        {
                PORT="900$i"

                result=`ps aux | grep "server.js $PORT" | grep -v grep`
                if [ -n "$result" ] ; then
                echo "$SERVER $i is started."
                RETVAL=0
                else
                echo "$SERVER $i is stopped."
                RETVAL=1
                fi
        }

done

do_stop()
{
        echo -n $"Stopping $SERVER:"
        killall -w node
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
}

case "$1" in
start)
        for i in {1..8}
        do
            do_start
        done
        ;;
stop)
        do_stop
        ;;
status)
        for i in {1..8}
        do
            do_status
        done
        ;;
restart)
        do_stop
        for i in {1..8}
        do
            do_start
        done
        ;;
*)

echo "Usage: $0 {start|stop|status|restart}"
        RETVAL=1
esac

exit $RETVAL

Tale demone, ad ogni avvio, esegue 8 istanze di nodejs, ciascuna delle quali è in ascolto su una porta dedicata (dalla 9001 alla 9008). Per non definire 8 funzioni do_start() e do_status() ho utilizzato un banale ciclo for e poichè la funzione do_stop() effettua un kill di tutti i processi il cui nome contiente la stringa “node” (killall -w), essa è stata estromessa dal predetto loop.

Inoltre, per ragioni di sicurezza, è stato definito un utente dedicato per l’esecuzione dell’applicativo (USER=”node”), la cui root directory è la propria home (ROOT_DIR=”/home/nodejs/), mentre il file di configurazione di nodejs è specificato all’interno della variabile SERVER (SERVER=”$ROOT_DIR/server.js”).

Infine, all’interno della funzione do_start(), ho definito il file di log per ciascuna istanza di nodejs, utilizzando la direttiva LOG_FILE=”$ROOT_DIR/server$i.js.log”.

Per completezza, di seguito riporto anche la versione senza cicli for:

#!/bin/bash

# chkconfig: 2345 95 20
# description: nodejs service
# processname: nodejs

. /etc/rc.d/init.d/functions

USER="node"

DAEMON="/usr/bin/node"
ROOT_DIR="/home/nodejs/"

SERVER="$ROOT_DIR/server.js"

PORT1="9001"
PORT2="9002"
PORT3="9003"
PORT4="9004"
PORT5="9005"
PORT6="9006"
PORT7="9007"
PORT8="9008"

LOG_FILE1="$ROOT_DIR/server1.js.log"
LOG_FILE2="$ROOT_DIR/server2.js.log"
LOG_FILE3="$ROOT_DIR/server3.js.log"
LOG_FILE4="$ROOT_DIR/server4.js.log"
LOG_FILE5="$ROOT_DIR/server5.js.log"
LOG_FILE6="$ROOT_DIR/server6.js.log"
LOG_FILE7="$ROOT_DIR/server7.js.log"
LOG_FILE8="$ROOT_DIR/server8.js.log"

do_start1()
{
        result1=`ps aux | grep "server.js $PORT1" | grep -v grep`
        if [ -z "$result1" ] ; then
        echo -n $"Starting $SERVER 1: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT1 >> $LOG_FILE1 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 1 already started."
        RETVAL=1
        fi
}

do_start2()
{
        result2=`ps aux | grep "server.js $PORT2" | grep -v grep`
        if [ -z "$result2" ] ; then
        echo -n $"Starting $SERVER 2: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT2 >> $LOG_FILE2 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 2 already started."
        RETVAL=1
        fi
}

do_start3()
{
        result3=`ps aux | grep "server.js $PORT3" | grep -v grep`
        if [ -z "$result3" ] ; then
        echo -n $"Starting $SERVER 3: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT3 >> $LOG_FILE3 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 3 already started."
        RETVAL=1
        fi
}

do_start4()
{
        result4=`ps aux | grep "server.js $PORT4" | grep -v grep`
        if [ -z "$result4" ] ; then
        echo -n $"Starting $SERVER 4: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT4 >> $LOG_FILE4 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 4 already started."
        RETVAL=1
        fi
}

do_start5()
{
        result5=`ps aux | grep "server.js $PORT5" | grep -v grep`
        if [ -z "$result5" ] ; then
        echo -n $"Starting $SERVER 5: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT5 >> $LOG_FILE5 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 5 already started."
        RETVAL=1
        fi
}

do_start6()
{
        result6=`ps aux | grep "server.js $PORT6" | grep -v grep`
        if [ -z "$result6" ] ; then
        echo -n $"Starting $SERVER 6: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT6 >> $LOG_FILE6 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 6 already started."
        RETVAL=1
        fi
}

do_start7()
{
        result7=`ps aux | grep "server.js $PORT7" | grep -v grep`
        if [ -z "$result7" ] ; then
        echo -n $"Starting $SERVER 7: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT7 >> $LOG_FILE7 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 7 already started."
        RETVAL=1
        fi
}

do_start8()
{
        result8=`ps aux | grep "server.js $PORT8" | grep -v grep`
        if [ -z "$result8" ] ; then
        echo -n $"Starting $SERVER 8: "
        runuser -l "$USER" -c "$DAEMON $SERVER $PORT8 >> $LOG_FILE8 &" && echo_success || echo_failure
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
        else
        echo "$SERVER 8 already started."
        RETVAL=1
        fi
}

do_status1()
{
        result1=`ps aux | grep "server.js $PORT1" | grep -v grep`
        if [ -n "$result1" ] ; then
        echo "$SERVER 1 is started."
        RETVAL=0
        else
        echo "$SERVER 1 is stopped."
        RETVAL=1
        fi
}

do_status2()
{
        result2=`ps aux | grep "server.js $PORT2" | grep -v grep`
        if [ -n "$result2" ] ; then
        echo "$SERVER 2 is started."
        RETVAL=0
        else
        echo "$SERVER 2 is stopped."
        RETVAL=1
        fi
}

do_status3()
{
        result3=`ps aux | grep "server.js $PORT3" | grep -v grep`
        if [ -n "$result3" ] ; then
        echo "$SERVER 3 is started."
        RETVAL=0
        else
        echo "$SERVER 3 is stopped."
        RETVAL=1
        fi
}

do_status4()
{
        result4=`ps aux | grep "server.js $PORT4" | grep -v grep`
        if [ -n "$result4" ] ; then
        echo "$SERVER 4 is started."
        RETVAL=0
        else
        echo "$SERVER 4 is stopped."
        RETVAL=1
        fi
}

do_status5()
{
        result5=`ps aux | grep "server.js $PORT5" | grep -v grep`
        if [ -n "$result5" ] ; then
        echo "$SERVER 5 is started."
        RETVAL=0
        else
        echo "$SERVER 5 is stopped."
        RETVAL=1
        fi
}

do_status6()
{
        result6=`ps aux | grep "server.js $PORT6" | grep -v grep`
        if [ -n "$result6" ] ; then
        echo "$SERVER 6 is started."
        RETVAL=0
        else
        echo "$SERVER 6 is stopped."
        RETVAL=1
        fi
}

do_status7()
{
        result7=`ps aux | grep "server.js $PORT7" | grep -v grep`
        if [ -n "$result7" ] ; then
        echo "$SERVER 7 is started."
        RETVAL=0
        else
        echo "$SERVER 7 is stopped."
        RETVAL=1
        fi
}

do_status8()
{
        result8=`ps aux | grep "server.js $PORT8" | grep -v grep`
        if [ -n "$result8" ] ; then
        echo "$SERVER 8 is started."
        RETVAL=0
        else
        echo "$SERVER 8 is stopped."
        RETVAL=1
        fi
}

do_stop()
{
        echo -n $"Stopping $SERVER:"
        killall -w node
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ]
}

case "$1" in
start)
        do_start1
        do_start2
        do_start3
        do_start4
        do_start5
        do_start6
        do_start7
        do_start8
        ;;
stop)
        do_stop
        ;;
status)
        do_status1
        do_status2
        do_status3
        do_status4
        do_status5
        do_status6
        do_status7
        do_status8
        ;;
restart)
        do_stop
        do_start1
        do_start2
        do_start3
        do_start4
        do_start5
        do_start6
        do_start7
        do_start8
        ;;
*)

echo "Usage: $0 {start|stop|status|restart}"
        RETVAL=1
esac

exit $RETVAL

Basta dare una semplice occhiata e vi renderete subito conto del perchè abbia preferito la prima versione a quest’ultima.

Non ci rimane che rendere il demone eseguibile:

[root@linuxbox ~]# chmod +x nodejs

spostarlo nella directory /etc/init.d:

[root@linuxbox ~]# mv nodejs /etc/init.d

ed avviarlo:

[root@linuxbox ~]# service nodejs start

E’ tutto.