Kill CPU Killers

Script para matar procesos que consumen demasiada CPU

Internet es una selva, dejas un servidor web y las peticiones extrañas no tardan en llegar. Por ejemplo, en el servidor de la escuela nos piden cosas de drupal, wordpress y demás aplicaciones que no tenemos instaladas. Hay bots que se dedican a buscar vulnerabilidades por Internet. Ok, normal.

El problema es que algunas de esas máquinas molestan mucho. Me he encontrado con que algunas peticiones dejan a nuestros procesos http consumiendo mucha CPU. Además, mientras hacen el tonto no atienden a nadie mas que al atacante. Y el ancho de banda que consumen no es despreciable.

Problema: en cuanto recibimos unas cuantas de estas peticiones, todos nuestros procesos http están haciendo el tonto y no atienden a nadie. Se ha producido una denegación de servicio.

Solución: Después de buscar en la documentación de Apache, no encuentro cómo evitar el abuso. Así que tomo medidas para abortar el abuso lo antes posible.

  1. Utilizando el módulo por defecto de Apache MPM Prefork:
    • Desactivamos la conexiones KeepAlive
    • Ajustamos el Timeout a un valor más reducido
    • Limitamos MaxClients a un número razonable, que evite saturar nuestra máquina con procesos http
    • MaxRequestPerChild 1, para que cada petición http sea atendida por un proceso que muere al terminar la petición. Bajo rendimiento pero aseguramos el mayor aislamiento entre peticiones.
  2. El servicio Apache tiene un proceso maestro que pertenece a root (no atiende directamente a los clientes) y varios procesos hijos (que atienden a los clientes) que pertenecen al usuario apache. Utilizamos el fichero limits.conf para limitar el tiempo de CPU consumido por los procesos del usuario apache. El tiempo mínimo permitido es un minuto, si por ejecumplo hubiese 50 procesos apache desbocados, se tardará, al menos, 50 minutos de reloj en que alguno de ellos acumule 1 minuto de atención de CPU. Este límite no se puede hacer más restrictivo sin reducir el MaxClients de apache, pero hacer eso nos expone a que se puedan atender muy pocas peticiones concurrentes en la red.
  3. Hay que escribir un script que mate procesos desbocados. Cuando un proceso hijo de apache muere, el servidor no se inmuta, simplemente lanza uno nuevo para atender la próxima petición.  Así nace kck un script que utiliza top para obtener información sobre los procesos que más CPU consumen, y si el UID corresponde con lo que indicado y el tiempo de CPU acumulado del proceso excede el límite lo mata. Además cuando se mata a algún proceso se notifica en un fichero de registro.
  4. Se programa una tarea en el cron para que cada minuto se busquen procesos http despendolados. Nota: como no podía ser de otra manera el primer intento falla. Hay que recordar que cron tiene su propio entorno, definimos la variable de entorno TERM para que nadie se queje.
    [root@proxy root]# crontab -l
    # DO NOT EDIT THIS FILE - edit the master and reinstall.
    # (/tmp/crontab.30508 installed on Fri Feb 24 22:16:08 2006)
    # (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
    TERM = xterm
    0-59 * * * * /root/bin/kck apache 5 20 /root/kck.log 2>>/root/kck_error.log


Script kck

#!/bin/bash

# kck - Kill CPU Killers by Victor Carceler
#
# This script is under GNU General Public License
#
# Abstract:
#
# Looks for most CPU consuming processes, and if they
# are from specified user and his CPU time is
# over the limit, kck kills them.
#
# kck <user> <cputime> <n>
#
# <user> Username of the processes to kill (apache)
# <cputime> Time limit in seconds
# <n> Number of most CPU consuming processes to kill
# <logfile> Where to log killed PIDs

if [ $# -ne 4 ]
then
echo "USAGE: $0 <user> <cputime> <n>";
echo "<user> Username of the processes to kill (apache)"
echo "<cputime> Time limit in seconds"
echo "<n> Number of most CPU consuming processes to kill"
echo "<logfile>" Where to log killed PIDs
exit 1
fi


n=`expr 7 + $3`
for linea in `top -b -n 1 | head -n $n | tail -n $3 | sed "s/^ *//" | sed 's/ */:/g' | sed 's/\./:/g' | grep -E "^[0-9]+:$1"`
do
pid=`echo -n $linea | cut -d ':' -f 1`
minutos=`echo -n $linea | cut -d ':' -f 13`
segundos=`echo -n $linea | cut -d ':' -f 14`
segundos=`expr $minutos \* 60 + $segundos`
if [ $segundos -gt $2 ]
then
timestamp=`date`
kill $pid
echo "$timestamp -> PID: $pid TIEMPO CPU: $segundos" >>$4
fi
done


Más información

kck nos ha resuelto el problema, pero antes de usarlo conviene asegurarse de que allí donde se va a utilizar funciona correctamente. En especial hay que comprobar que la versión de top que se utiliza muestra 7 líneas de cabecera antes de listar los procesos. Y evidentemente que las columnas de información de los procesos están en el mismo orden que en el top standard.

Cuidado: Cuando root utiliza kck puede matar cualquier proceso.

Desde que ayer por la noche dejé funcionando kck, ya se han matado a los primeros procesos de apache.

[root@proxy root]# tail kck.log
Fri Feb 24 23:49:00 CET 2006 -> PID: 23911 TIEMPO CPU: 9
Sat Feb 25 01:16:00 CET 2006 -> PID: 32467 TIEMPO CPU: 6
Sat Feb 25 05:37:01 CET 2006 -> PID: 7423 TIEMPO CPU: 34
Sat Feb 25 10:18:00 CET 2006 -> PID: 11638 TIEMPO CPU: 12
Sat Feb 25 10:38:00 CET 2006 -> PID: 11961 TIEMPO CPU: 45


Navegació