En los últimos días varias personas con servidores propios –fundamentalmente de wordpress– me pidieron ayuda para reducir la carga de sus servidor o mejorar los tiempos de respuesta. Una de las últimas frecuentes era «porqué a veces la carga del servidor subía mucho». Asumiendo que el servidor está bien configurado y balanceado [1], el problema suele ser por los «bots demasiado agresivos».
[1] Ver abajo Diez reglas básicas para servidores web bien ajustados
El aumento del ancho de banda en las conexiones hogareñas, las pruebas «académicas» y de prácticas desde universidades –en España sus líneas suelen superar creces los 100 Mbps– y los bots de los harvesters de spammers hace que debamos también monitorizarlos e impedir que su molestia sea persistente.
Aunque hay métodos en las iptables y Apache para controlar el número de conexiones —connection throttling— a veces es muy difícil encontrar un valor adecuado sin que genere problemas a conexiones válidas. Incluso puede haber problemas de memoria –de las iptables, que las asigna en el espacio del kernel– en servidores que no tienen suficiente.
Por ello a veces es más fácil analizar los logs de Apache en situaciones de alta carga para detectar desde qué direcciones IP se están haciendo tantas conexiones y agregar una regla a las iptables para hacer in drop a paquetes desde esa dirección.
En Menéame es bastante habitual que nos aparezca un bot de estos y que sus conexiones duren bastante tiempo mientras recorre absolutamente todos los posibles URLs. Como estos son centenares de miles, la «agresión» suele durar horas o días, así que me preparé un pequeño script en Python que me permite analizar el número de conexiones desde cada IP.
#! /usr/bin/python
import fileinput
import re
to_ignore = ['/img/', '/js/', '/css/'] # Los cgi o directorios a ignorar
ips = {}; res = []
try:
for s in to_ignore:
res.append(re.compile(re.escape(s)))
for line in fileinput.input():
words = line.split()
for r in res:
if r.search(words[6]): break
else:
try: ips[words[0]] += 1
except KeyError: ips[words[0]] = 1
tuples = ips.items()
tuples.sort(lambda (k1,v1),(k2,v2):cmp(v2,v1))
for ip, counter in tuples:
print '%-8d\t%s' % (counter, ip)
except IOError:
pass
El programa anterior analiza el logs del apache desde la entrada estándar o los ficheros que se indiquen en la línea de comandos e imprime una tabla de números de conexiones de cada IP.
Yo lo suelo usar de la siguiente forma:
tail -1000000 /var/log/apache2/meneame_net.access.log | ./freq-ip.py | less
aunque también se puede hacer:
./freq-ip.py fichero1.log fichero2.log …
Nota: aunque el ejemplo es con las últimas 1.000.000 de líneas, elige un valor adecuado a tu servidor, por ejemplo si te interesa analizar los últimos 5 minutos y tienes unas 100.000 conexiones en ese período, ese es el valor que debes poner como argumento del tail.
El resultado es una tabla ordenada de mayor a menor, por ejemplo:
22759 66.249.72.103
914 62.43.58. xxx
837 84.77.96.xxx
645 89.248.99.xx
522 65.214.44.xx
486 89.17.210.xx
483 72.14.199.xx
...
Las IPs que aparecen primeras son las sospechosas de bot agresivos, pero hay que analizarlas con el whois para asegurarse. En el resultado anterior se puede observar claramente que la dirección 66.249.72.103 es sospechosa, pero en este caso no hay que hacer nada porque es el bot de Google (el de búsquedas y el del AdSense). Pero si se trata de una IP que no pertenece a ningún buscador o directorio conocido, o que viene de países como China o Ucrania, seguramente se trate de un bot incontrolado o mal programado.
En todo caso siempre conviene analizar el historial de los logs para asegurarse:
tail -1000000 /var/log/apache2/access.log | grep ^66.249.72.103 | less
Una vez que estés seguro que quieres evitarle los accesos, basta agregarlo en las iptables. Yo tengo preparado un script que lee las IP «prohibidas» desde un fichero y ejecuta los comandos de las iptables:
#! /bin/sh
iptables -F
# START Drops from ip_forbiden
for ip in `cat /DIRECTORIO/ip_forbiden` ## AQUI el pathname del fichero
do
if [[ "$ip" =~ "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" ]]
then
echo Dropping $ip
iptables -i ethX -A INPUT -s $ip -j DROP ### AQUI la interfaz que corresponda, eth0 o eth1, etc.
fi;
done
# tus reglas propias
...
Diez reglas básicas para servidores web bien ajustados
Al principio mencioné que primero debemos asegurarnos que el servidor web está bien configurado y ajustado para nuestras necesidades. En realidad no es tan difícil ajustar los parámetros básicos del Apache.
- Asegúrate verificando en el error.log si el Apache no llega al número máximos de procesos. Si es así debes incrementar el máximo, por ejemplo: MaxClients 150
- Algunas distribuciones, como Debian, tiene el máximo «absoluto» en 256 procesos, si necesitas más debes cambiarlo: ServerLimit 512
- Habilita el KeepAlive, pero ponle un timeout bajo: KeepAliveTimeout 2
- StartServers debe tener un valor adecuado, que sea ≥ que MinSpareServers y ≤ que MaxSpareServers. Por ejemplo : StartServers 40, MinSpareServers 30, MaxSpareServers 50
- MaxSpareServers debe ser mayor en al menos un 50% el valor de MinSpareServers.
- El PHP puede consumir mucha memoria en algunas conexiones (subida de ficheros, compilación de muchos módulos, etc.) y no las libera hasta que «muera» el proceso. Por eso es mejor que los procesos de Apache tengan una «vida limitada» para permitir liberar esa memoria que no se necesita: MaxRequestsPerChild 10000. Ajusta el valord de 10.000 a tus propias necesidades, si tu servidor tiene muchas conexiones puedes subirlo, por ejemplo a 1.000.000 como en el caso del Menéame.
- Para saber mejor cuál es el problema de tu servidor usa los comando top y vmstat 2. El primero te dará el uso de CPU de los procesos, si es el mysqld el que aparece con casi el 100% de CPU, el problema es de base de datos, por el contrario se trata del PHP. El vmstat te dará más pistas, si los valores de la columna wait (la última, wa) son altos, es porque necesita mucha entrada salida, necesitas más memoria RAM. Si por el contrario las valores de la columna idle (la penúltima, id) son muy bajos necesitas más CPU.
- El WordPress no es un gran consumidor de CPU en base de datos (a menos que ésta sea inmensa), sino de CPU para compilar todos los módulos para cada conexión. Si tu problema es de carga alta de CPU lo más probable es que necesites usar «cache» de PHP compilados, por ejemplo el eaccelerator.
- Si tienes instalado el WP-Cache, éste evita que se tengan que compilar todos los módulos en el caso de encontrarse la página en cache.
- Si el el Mysql del WordPress te consume mucha CPU lo más probable es que sea algún plugin (especialmente esos que guardan estadísticas en la base de datos) o que tengas muy poca memoria RAM disponible para cache de disco. Pero primero analiza o deshabilita los plugins sospechosos.
- Si has desarrollado tu propio programa y el Mysql te consume mucho, tienes problemas de SQLs, índices o problemas de estructura de la base de datos. recuerda la regla de oro en programas web con base de datos (general para cualquier gestor): debes evitar a toda costa los recorridos secuenciales en las tablas. Para eso debes ser muy cuidadoso en la creación de los índices adecuados o de cómo haces las consultas SQL. Si no puedes asegurarte de ello tienes dos opciones primordiales para el caso que las modificaciones a las tablas no sean muy frecuentes:
- Jugar con los valores de cache de queries sql en el propio gestor (query_cache_size y query_cache_type en el Mysql).
- Usar memcached.
No entiendo nada
Si lo que te comento arriba te suena a chino:
- Quizás no deberías haber contratado un servidor dedicado si eres capaz de administrarlo y ajustarlo, o
- deberías estudiar un poco de administración de servidores, especialmente del Apache, MySQL e iptables, o
- podrías pedir ayuda a un amigo, o menor aún para evitar la proliferación de pringaos, contrata a un amigoo conocido de confianza por unos 50-60 euros la hora (tarifa española más o menos adecuada) que sepa del tema y te ajuste el servidor. Si tiene que hacer todo lo que explico aquí, le llevará más o menos una hora para ajustar, y monitorizar para asegurarse que los valores son adecuados.