Migrando de Apache 2 a nginx: Virtual Hosts

nginxLos ficheros de definición de Virtual Hosts en nginx se almacenan en directorios muy similares a los de apache. Tenemos un directorio llamado sites-available y otro que se llama sites-enabled y ambos están bajo el directorio principal de configuración que es /etc/nginx. En el primero se deberían de guardar todos los virtual hosts del servidor (estén disponibles o no) y en el segundo aquellos que están activos. La forma correcta de activar o desactivar un Virtual Host debería de ser crear un enlace en el segundo directorio al archivo correspondiente del primero, al igual que en Apache. Para desactivarlo temporal o definitivamente borraríamos ese enlace. La diferencia aquí con apache es que no contamos con comandos similares a los a2ensite y a2dissite que hacen estas operaciones de forma cómoda, pero tampoco se nos van a caer los anillos por hacer un enlace manualmente en línea de comando a estas alturas ¿verdad? Bueno, por si acaso, el siguiente comando realizaría un enlace del fichero de definición de Virtual Host llamado miweb del directorio sites-availabe en el directorio sites-enabled:

ln -s /etc/nginx/sites-available/miweb /etc/nginx/sites-enabled/

Veamos ahora el “esqueleto” de configuración más básico en Apache:

<VirtualHost *:80>
	ServerName www.miweb.com
	ServerAlias blog.miweb.com *.miweb.es
	DocumentRoot /var/www/miweb/
	DirectoryIndex index.php index.html index.htm;
</VirtualHost>

Y su equivalente en nginx:

server {
        listen 80;
        server_name www.miweb.com blog.miweb.com *.miweb.es;
        root /var/www/miweb/;
        index index.php index.html index.htm;
}

Puesto que partimos de que conocemos la sintaxis en Apache no deberíamos de encontrar problemas en entender la de nginx ¿verdad? Sólo un detalle adicional sobre estas líneas. Si tenemos un servidor con más de un interfaz y/o dirección IP y queremos que el servidor web sólo atienda a una de ellas en Apache sustituiríamos la primera línea del ejemplo anterior por esta:

<VirtualHost 123.45.67.89:80>

En nginx haríamos lo propio modificando así la segunda línea:

listen 123.45.67.89:80;

Cualquier configuración adicional del Virtual Host se realizaría incluyendo las directrices adecuadas en el interior del bloque de definición. Veamos algunas de las más comunes. Por ejemplo, si queremos dar una ubicación concreta y separada del lugar donde se almacenan los logs de acceso y error, en Apache lo hacíamos con estas líneas:

CustomLog /var/log/apache2/miweb-access.log combined
ErrorLog /var/log/apache2/miweb-error.log
LogLevel warn

Y en nginx lo haríamos así:

access_log /var/log/nginx/miweb-access.log;
error_log /var/log/nginx/miweb-error.log warn;

El formato del log de accesos por defecto en nginx es idéntico al modo combined de Apache. No obstante, tenemos un montón de opciones adicionales que puedes consultar aquí. Los niveles permitidos para el log de error son, además de warn, error, crit, alert, y emerg.

¿Cómo redefinimos las páginas de error que nos mostrará el servidor web? En Apache usábamos la directiva ErrorDocument:

ErrorDocument 404 /errores/404.html
ErrorDocument 500 /errores/500.html
ErrorDocument 502 /errores/502.html
ErrorDocument 503 /errores/503.html
ErrorDocument 504 /errores/504.html

En nginx usamos error_page:

error_page 404 /errores/404.html;
error_page 500 /errores/500.html;
error_page 502 /errores/502.html;
error_page 503 /errores/503.html;
error_page 504 /errores/504.html;

Si queremos redireccionar varios errores a una misma página podemos hacerlo de esta forma:

error_page 500 502 503 504 /errores/500bis.html;

Veamos ahora como se restringe el acceso a ciertos directorios. En Apache, para denegar el acceso a todo el mundo a un directorio del servidor web lo hacíamos de esta forma:

<Directory /secreto>
        Deny from all
</Directory>

Mientras que en nginx haríamos lo mismo así:

location /secreto {
        deny all;
}

Para restringir el acceso a determinados clientes, en Apache “jugamos” con las directrices Order, Deny y Allow. En nginx no existe ninguna directiva similar a Order. Debemos de poner una a una todas las instrucciones deny y allow que necesitemos. El servidor las analizará una a una y en orden hasta encontrar la primera que “cuadre” con la petición que está atendiendo de forma similar a como se comportaría un cortafuegos. Las siguientes reglas son todas válidas:

location /secreto2 {
    deny  192.168.1.1;
    allow 192.168.1.0/24;
    allow 10.1.1.0/16;
    allow 2001:0db8::/32;
    deny  all;
}

Si lo que queremos es restringir el acceso mediante usuario y contraseña con un fichero generado con htpasswd como hacíamos habitualmente con Apache, podemos hacerlo de forma fácil. El fichero de contraseña lo generamos de la misma forma que ya sabemos (tienes que tener instalado el paquete apache2-utils para contar con esta utilidad) e incluir las siguientes directivas:

location /secreto3 {
    auth_basic "Control de Acceso";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

La línea auth_basic sólo aporta un mensaje de texto que aparecerá en la ventana en la que se nos pide el usuario y contraseña. La directiva auth_basic_user_file es la que marca el lugar y el nombre del fichero que contiene los usuarios y las contraseñas (las hashes de estas, en realidad) autorizadas para acceder al directorio /secreto3 de nuestro Virtual Host.

Si nuestro Virtual Host necesita interpretar páginas en PHP, debemos de pasar estas peticiones al módulo adecuado y tenemos que expecificarlo aquí, y no como ocurría en Apache que se hacía de forma automática con sólo tener activo el módulo php5. Yo uso php5-fpm (aunque existen otras alternativas) y para que funcione debemos de incluir un bloque como este:

location ~ \.php$ {
    try_files $uri =404;
    fastcgi_pass unix:/var/run/php5-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
ACTUALIZACIÓN MUY IMPORTANTE: A partir de la versión 1.6.2 de nginx en Debian 8 (Jessie) el anterior bloque de configuración para php ya no es válido y habría que usar en su lugar este otro.
ACTUALIZACIÓN: PHP-FPM puede usarse con puertos TCP o con sockets. Las ventajas de usar sockets te las cuenta brevemente aquí Javier Terceiro.

En Debian hay que instalar el paquete correspondiente (apt-get install php5-fpm) y asegurarse de que el módulo aceptará las peticiones en la dirección que hemos indicado con la directiva fastcgi_pass. Esto lo comprobamos en el fichero de configuración /etc/php5/fpm/pool.d/www.conf y buscando la línea siguiente:

listen = /var/run/php5-fpm.sock
IMPORTANTE: Si necesitas usar de forma imprescindible ficheros .htaccess este no es tu servidor web. nginx está diseñado para ser rápido y eficiente y no soporta nada parecido. Los .htaccess son cómodos para permitir pequeñas personalizaciones de configuración en servidores compartidos, pero en cualquier otro caso son absolutamente prescindibles y desde el punto de vista de la eficiencia son tremendamente negativos. Piensa que por cada petición que se hace al servidor web, este tiene que recorrer todos los directorios del Virtual Host uno por uno en busca de ficheros de este tipo. Aquí te lo explican muy bien y en este enlace y en este otro tienes herramientas automáticas para ayudarte a migrar las reglas que tengas en ellos a nginx.

Año nuevo, servidor web nuevo. Migrando a ningx

nginx Por aquello de terminar un año y empezar el siguiente con cambios y nuevos propósitos, ayer comencé a migrar la infraestructura que da soporte a este blog a nginx. Si has pasado por aquí en las últimas horas habrás podido experimentar problemas debido a los cambios. Ahora parece ya casi totalmente estable pero dista aún de estar optimizado y habrá que trabajar un poco mas en ello… Y bueno, ya que estamos, feliz año y tal 😉

Apache Tunning (y II). Ajustando el parámetro MaxClients

apache Como ya habíamos hablado por aquí, uno de los parámetros críticos en un servidor Apache es MaxClients, el número máximo de conexiones que es capaz de manejar simultaneamente. Es crítico porque cada tarea de Apache consume una determinada cantidad de memoria y si la suma de la memoria que consumen todos de forma simultanea es mayor que la memoria RAM que la máquina tiene libre comenzará a hacer swapping a disco y, si esta situación se mantiene, corremos un riesgo importante de que se nos quede virtualmente congelada. Una instalación de Apache por defecto toma un valor de 150 para este parámetro y esto es una verdadera burrada para casi cualquier servidor web con menos de 1 Gbytes (o incluso con bastante mas) que use contenidos dinámicos medianamente pesados.

Hace bien poco, buscando por ahí algún enlace para profundizar un poco más en estos temas, me encontré con este artículo de una empresa de hosting donde te proponen un script para ajustar el valor más adecuado a tus necesidades. Había dos cosas que no me gustaban en él script propuesto. Por un lado, tomaban el valor más conservador, es decir, el resultante de dividir la memoria libre entre lo que ocupa el proceso de Apache mayor. En segundo lugar, hacen el cálculo de la memoria libre sin tener en cuenta la que está ocupada por cache o buffers y que podría liberarse en caso de necesidad. Yo le he hecho algunos cambios para salvar ambos puntos y dejarlo un poco más bonito e informativo y me ha salido esto:

#!/bin/bash
clear

# Primero analizamos los procesos de apache funcionando actualmente
LISTA=`ps -aylC apache2 |grep apache2 |awk '{print $8'} |sort -n`

echo "Lista ordenada de la memoria, en bytes, ocupada por los procesos de Apache corriendo en este momento:"
COUNT=0
SUMA=0
for ITEM in $LISTA
do
        COUNT=`expr $COUNT + 1`
        SUMA=`expr $SUMA + $ITEM`
        echo "$COUNT -> $ITEM"
        if [ $COUNT -eq 1 ]
        then
                MENOR=$ITEM
        fi
done

MAYOR=$ITEM
MEDIA=`expr $SUMA / $COUNT`

# Pasamos las cantidades a Kbytes
MENOR=`expr $MENOR / 1024`
MAYOR=`expr $MAYOR / 1024`
MEDIA=`expr $MEDIA / 1024`

echo "El proceso de Apache más grande ocupa" $MAYOR "Kbytes y el menor" $MENOR "Kbytes"
echo "La media de la memoria ocupada por un proceso es de $MEDIA Kbytes"

echo "Detenemos Apache..."
apache2ctl graceful-stop > /dev/null

# Limpiamos los caches y buffers del servidor
sync
echo 3 > /proc/sys/vm/drop_caches

# Calculamos la memoria libre del sistema
FREEMEM=`free -m |head -n 2 |tail -n 1 |awk '{free=($4); print free}'`
echo "La memoria libre del servidor, después de liberar cache y buffers, es de $FREEMEM Kbytes"

echo "Arrancamos Apache de nuevo..."
apache2ctl start > /dev/null

echo "MaxClients debería de estar entre" `expr $FREEMEM / $MAYOR` "y" `expr $FREEMEM / $MENOR`
echo "Usando el valor medio de la memoria ocupada, un valor recomendable de MaxClients sería de" `expr $FREEMEM / $MEDIA`

El script de aquí arriba imprime una lista de la memoria ocupada por todos procesos de Apache activos en el momento de ejecutarlo. Luego toma el mayor, el menor y la media de todos ellos y calcula la memoria libre del sistema. Para ello, antes detiene Apache (con un graceful-stop para no fastidiar a nadie que esté viendo nuestra web en ese momento) y libera los buffers y caches de memoria. Por último, arranca de nuevo Apache y nos da como salida dos valores: una horquilla entre el valor que debería de tomar MaxClient usando el menor y el mayor valor de los procesos tomados en la instantánea anterior y un valor recomendado que sale de usar el valor medio de todos ellos.

Evidentemente no se trata de algo matemático. Como es lógico, ejecutado en diferentes momentos obtendremos valores distintos ya que ni la RAM que usa nuestro servidor es inmutable ni el tamaño de los procesos de Apache es siempre el mismo ni siguen la misma distribución. Pero para darnos una primera idea del valor con el que tenemos que empezar a jugar creo que es bastante válido. Ya me contáis.

NOTA: En algunas instantáneas el valor de memoria ocupado por el proceso más pequeño de Apache puede ser cero, seguramente porque o está creándose o está destruyéndose en ese momento. En ese caso el valor de la horquilla pierde uno de sus extremos. Sería muy fácil modificar el script para que en ese caso tome el segundo valor y divida por un elemento menos, pero ocurre en tan pocas ocasiones que no creo que merezca la pena hacerlo. Al menos no hoy 😉

Instalando memcached en Debian y habilitando su uso con WordPress

herramientas memcached es uno de los sistemas de caché para servidores web más usado. Usa una zona de la memoria RAM del servidor web de tamaño configurable como caché y funciona bien tanto en sitios pequeños y discretos como en grandes sistemas con múltiples servidores. Facebook, Twitter o Youtube, por ejemplo, usan este sistema de cacheado. El 85% de los 20 sitios con más tráfico de Internet lo usan, en realidad. Nada mejor, pues, para estrenar una pequeña sección acerca de sistemas y extensiones de cacheado para apache ¿no?

Partimos para la instalación de una Debian en la que ya tenemos un servidor web apache y la versión 5 de php (paquetes apache2 y php5). Para instalar memcached en una Debian he tenido que recurrir a usar “paquetería” de wheezy (testing) porque parece que hay algún problema de dependencias en la versión estable (squeeze) y la librería de extensión para trabajar con PHP no compilaba correctamente. Así que manos a la obra. El primer paso es instalar el servidor de memcached:

apt-get install memcached

El servicio se instala, como es habitual, en el directorio /etc/init.d y responde a los argumentos start, stop, restart, force-reload y status. En el directorio /usr/share/memcached/scripts existe un script llamado start-memcached que es el encargado de realizar el inicio automático y que toma la configuración del archivo memcached.conf en el directorio /etc. Dentro de este fichero de configuración una de las opciones más interesantes es la que dice a memcached cuanta RAM va a usar como caché. Por defecto son 64M, pero en sistemas con muy poca RAM tal vez nos convenga bajarlo. Mi VPS tiene sólo 512M de RAM y yo he bajado esos 64M a la mitad. Para ello basta con cambiar la línea -m 64 por -m 32 y reiniciar el servicio.

Otra de las opciones interesantes en ese archivo de configuración es la posibilidad de habilitar un archivo de logs que, por defecto, viene deshabilitado. Para ello basta con quitar el comentario a una de las dos opciones que aparecen el el fichero según el nivel de detalle que queramos: -v o -vv. El log se grabará en el fichero /var/log/memcached.log. Esto también es configurable modificando la línea que aparece en el mismo fichero. Y no olvides que para que la opción tome efecto haz de reiniciar de nuevo el servicio de memcached.

El segundo paso sería instalar la extensión de memcached para PHP. Para ello tenemos que instalar los siguientes paquetes:

apt-get install php-pear php5-dev php5-memcached libmemcached10 libmemcached-dev libmemcachedutil2 libhashkit2 libhashkit-dev

Y, a continuación, ejecutar lo siguiente:

pecl install memcached

Si todo sale bien, al final del proceso se nos mostrará un mensaje similar a este:
Instalación del módulo PHP de memcached

La librería debería de haberse creado en el directorio de extensiones de php que se indica al final del proceso y que puedes ver en la imagen anterior (/usr/lib/php5/20100525+lfs) y ya sólo nos resta hacerle caso al mensaje final y añadir la línea extension=memcached.so al final de nuestro fichero php.ini que debería de estar en el directorio /etc/php5/apache2. Reiniciamos apache y, si ejecutamos la función phpinfo veremos una nueva sección:
phpinfo con información del módulo memcached

Para ver información acerca de como está funcionando nuestra cache tenemos un par de opciones más atractivas que inspeccionar el fichero de log que hemos mencionado antes. Una de ellas es usar el script memcached-tools que encontraremos en el directorio /usr/share/memcached/scripts/.

/usr/share/memcached/scripts/memcached-tool 127.0.0.1:11211

El comando anterior muestra información sobre el estado del servicio memcached que hemos instalado en nuestra máquina. Añadiendo al final el argumento stats nos mostrará estadísticas de uso y con el argumento dump realizará un volcado del contenido de la caché por pantalla.

La segunda opción que tenemos es bajarnos de aquí un script hecho en perl y llamado memcache-top. Tienes distintos argumentos para modificar la salida que puedes consultar en la web desde la que te has bajado el script. En la imagen siguiente se muestra con las opciones --cumulative y --commands
ejecución de memcached-top

Y ya sólo nos queda habilitar el uso de memcached desde wordpress. Para ello usaremos Memcached Object Cache que, aunque aparece como cualquier otro en el repositorio de plugins de wordpress, en realidad precisa de una sencilla instalación manual para que funcione. Bajamos el archivo, lo descomprimimos y copiamos el archivo object-cache.php en el directorio wp-content de nuestra instancia de wordpress.

Apache Tunning. Ajustando recursos en un servidor web con poca memoria

apacheInstalar un servidor Apache en un entorno Linux debe de ser lo más fácil del mundo. Configurarlo para que funcione de forma aceptable sirviendo una o dos instancias web también es sencillo. Ajustarlo para que funcione de forma eficiente en entornos escasos de recursos o para que escale bien ante grandes cargas ya es algo en lo que muy pocos se meten. Si siempre has trabajado con servidores compartidos no te habrás dado cuenta de esto último porque alguien te lo ha dado ya hecho. Pero tanto si has tenido que gestionar servidores propios y/o dedicados o pequeños VPS posiblemente hayas tenido algún problema en este sentido. Y si no, creeme: los vas a tener…

La forma en que Apache gestiona la multitarea a la hora de atender a los clientes del servidor web se configura mediante los módulos MPM. Los dos más comúnmente usados son Prefork y Worker pero existe por ahí alguno más. Al instalar apache2 en Debian (y en todas las distribuciones, me atrevería a decir) se instala por defecto el paquete apache2-mpm-prefork y este es el módulo MPM que funcionará por defecto. Para probar con worker basta con instalarlo (apt-get install apache2-mpm-worker en una Debian) y automáticamente desinstalará el otro mpm (sólo puede haber uno en cada momento) y reiniciará el servicio de apache para que los cambios tomen efecto.

La primera pregunta que se nos plantea es, lógicamente, cual elegir. A grandes rasgos la principal diferencia entre ambos es que Prefork crea siempre nuevos procesos para gestionar las conexiones entrantes mientras que con Worker los procesos hijos, a su vez, crean múltiples hebras cada uno de ellos para esta tarea. Tienes información más precisa de como funcionan en los enlaces de hace unas líneas. Prefork tiene a gala ser más estable y funcionar mejor en máquinas escasas de recursos, mientras que Worker presume de ser más escalable y eficiente para servidores más ambiciosos. Para novatos o instalaciones sin necesidades especiales es mejor quedarse con la opción por defecto (Prefork) puesto que, además, incluso algo tan simple como instalar PHP es trivial y habitual con este módulo mientras que hay que “enredar” bastante más para hacerlo funcionar con Worker.

Si el servidor no lo hemos instalado nosotros y queremos saber que MPM está activo basta con recordar que sólo puede haber uno, así que podemos mirar que paquete está instalado (aptitude search apache2-mpm-* en Debian, de nuevo) o interrogar directamente a apache con la opción -l:

root@invernalia:/etc/apache2# apache2 -l
Compiled in modules:
  core.c
  mod_log_config.c
  mod_logio.c
  prefork.c
  http_core.c
  mod_so.c

Pero independientemente del módulo que escojamos, el comportamiento final del servidor web será muy parecido. Pensemos que se trata de un servicio diseñado para atender múltiples peticiones simultaneas de decenas (o centenares, si tenemos una web con éxito) de clientes y hacerlo de forma eficiente y en el menor tiempo posible. Nuestro servidor permanecerá a la espera de estas peticiones y, cuando vayan llegando, destinará a un proceso (o a una hebra de un proceso) a atenderlas. Cuantos más peticiones se cursen de forma simultanea, más procesos/hebras necesitaremos y consumiremos más recursos de la máquina. Sobre todo memoria. Un servidor web se comporta como un insaciable devorador de RAM. Este va a ser el recurso que debemos de aprender a gestionar: la RAM que no se usa cuando se necesita se desperdicia y nos hace perder visitantes o que estos sean atendidos más lentamente. Pero si no controlamos el consumo y el servidor la agota comenzará a hacer swapping a disco y evitar esto es la regla de oro: Si llegamos a este punto lo habremos perdido y, posiblemente, tendrás que reiniciar el servicio (o la máquina, si no tienes tiempo o paciencia) para volver a tomar el control.

Los principales ajustes que necesitamos hacer se encuentran en nueve líneas del archivo /etc/apache2/apache2.conf. A continuación tienes los valores por defecto en una instalación básica de Debian 6: Ten cuidado porque las cinco últimas se encontraran duplicadas en el interior de sentencias condicionales segun el módulo MPM que estemos usando. Las que nos interesan hoy son las que están tras la sentencia <IfModule mpm_prefork_module>.

Timeout 		300
KeepAlive 		On
MaxKeepAliveRequests	100
KeepAliveTimeout   	15
StartServers        	5
MinSpareServers     	5
MaxSpareServers     	10
MaxClients		150
MaxRequestsPerChild	0

Timeout, el primero de ellos, define el tiempo en segundos que Apache esperará a determinados eventos antes de cerrar o abortar una conexión. En servidores con pocos recursos 300 segundos puede ser una cantidad bastante elevada. Reducir ese tiempo sustancialmente ayudará a gestionar mejor la memoria del servidor. Valores de 30 o 40 son adecuados en servidores con pocos recursos. Un valor de 10, incluso, podría mejorar el rendimiento en determinados entornos.

Los tres parámetros siguientes (KeepAlive, MaxKeepAliveRequest y KeepAliveTimeout) definen la posibilidad de usar conexiones persistentes y la forma de tratarlas. En concreto, el número de peticiones que se permitirán sobre cada conexión (MaxKeepAliveRequest) y el tiempo de espera sin peticiones antes de abortarlas. Tener habilitadas este tipo de conexiones reduce sustancialmente la carga del servidor y los tiempos de respuesta y el número de peticiones se puede mantener en torno a 100 en casi cualquier entorno. Es, de nuevo, el Timeout por defecto de 15 segundos el que puede resultar demasiado elevado en servidores con poca RAM y podríamos reducirlo a un valor de 5 segundos o incluso menos.

Los cinco parámetros restantes son los que definen el comportamiento del módulo Prefork. StartServers, MinSpareServers y MaxSpareServers ajustan, respectivamente, el número de procesos apache que se crearan inicialmente, y el mínimo y máximo de estos que mantendremos inactivos a la espera de que lleguen peticiones de posibles clientes. Los procesos inactivos definidos por estos parámetros estarán consumiendo memoria pero nos permitirán dar una respuesta más rápida a los clientes (los procesos están ya listos para usarse cuando llegan peticiones y no hay que perder tiempo en crearlos), así que en un servidor con grandes recursos y que espera cargas elevadas querríamos tener valores más elevados de los que vienen por defecto mientras que en un servidor pequeño podríamos reducirlos todos a, por ejemplo, 3 o 4.

MaxClients es, posiblemente, el parámetro más importante. Define el máximo número de procesos simultaneos que nuestro servidor podrá crear para atender peticiones. Un número más pequeño del que podemos permitirnos desperdiciará memoria y ralentizará las peticiones de muchos clientes (que permanecerán a la espera de que uno de estos procesos se libere para atenderle) mientras que un número demasiado elevado agotará la memoria del servidor y lo obligará a hacer swapping a disco. Y aquí no hay fórmulas mágicas. Lo primero que se nos puede ocurrir es que dividiendo la memoria que tenemos disponible entre lo que ocupa cada proceso de Apache obtendríamos el número que necesitamos, pero el problema es que la memoria disponible es difícil de calcular de forma exacta (ya que, posiblemente, tendremos también un servidor mysql, servicios de correo, etc). Además, los procesos de apache no consumen todos la misma cantidad de memoria. Si tenemos distintas instancias de apache con características muy diferentes la diversidad será aún mayor. Podemos verlo en el siguiente pantallazo de htop tomado de un servidor apache funcionando con varias instancias web diferentes:
Vista de htop en un servidor apache que estamos monitorizando

La columna MEM% nos da el porcentaje de memoria que está ocupando el proceso de apache y, como vemos, tenemos cifras entre 0.5% y 4.2%, es decir, entre 4 y 32 Mbytes (la máquina es un VPS con 768Mbytes). Si aproximamos que disponemos de 600Mbytes disponibles para que apache los use nos saldrían cifras de entre 150 y 18 procesos simultaneos. Una horquilla demasiado grande. Si somos muy, muy conservadores podemos irnos al borde inferior de la horquilla y asegurar que nunca entraremos en zona de riesgo. Podemos también dedicarle tiempo a estudiar el comportamiento medio de nuestros clientes y hacer medias de la utilización de memoria por los procesos de Apache. Lo más rápido para empezar es tomar un valor medio, por ejemplo de un 2.5% o un 3% y ajustar por ahí el número máximo de procesos. Entre 25 y 40 en este caso. El hecho de que hayamos bajado mucho los Timeouts ayudará a que los procesos se liberen rápidamente y se turnen atendiendo a los clientes que están a la espera con lo que no deberíamos de notar demasiado la espera.

Tenemos aún otro parámetro más. MaxRequestsPerChild marca el número de peticiones que atenderá cada proceso antes de reciclarse. El valor por defecto es cero que indica un número indefinido. Poner un número elevado pero no ilimitado ayudará también a que nuestro servidor libere y sanee su memoria de forma regular. Un valor de entre 500 o 1000 podría ser adecuado para nuestra pequeña máquina. Nuestra configuración final podría quedar así:

Timeout 		10
KeepAlive 		On
MaxKeepAliveRequests	100
KeepAliveTimeout   	5
StartServers        	3
MinSpareServers     	3
MaxSpareServers     	4
MaxClients		30
MaxRequestsPerChild	600

No olvides que hay que reiniciar el servicio de apache (service apache2 restart) después de realizar cualquier modificación en este fichero de configuración para que los cambios tomen efecto.

AWStats revisitado

estadisticas Desde hace unos meses he dejado de lado a Piwik, el maravilloso sistema de estadísticas que he venido usando durante los tres últimos años. Piwik ha evolucionado muchísimo desde que empecé a usarlo: ya no usa gráficos flash, mantiene muy controlado el tamaño de su base de datos, es más rápido y tiene nuevos plugins que lo convierten en un perfecto competidor libre de google analytic y lo están haciendo escalar puestos poco a poco en el competitivo ranking de los sistemas de estadísticas. Pero… tiene un enorme problema: el consumo de memoria resulta del todo inasumible para una máquina con RAM escasa que es el gran cuello de botella de un servidor web con Apache, así que cuando me pasé a un servidor VPS en Linode, con la limitación de RAM que eso supone, fue lo primero que tuve que sacrificar. Algún día me pararé a probar lighttpd o a ver si realmente el módulo MPM worker mejora tanto como cuentan la gestión de memoria, pero por el momento los alrededor de 128 Megas que consumen los scripts de Piwik me resultan del todo inasumibles.

AWStats tiene un aspecto realmente “viejuno” de web sacada de Geocities, pero hace bien lo que tiene que hacer, apenas consume RAM y obtiene sus resultados leyendo directamente de los logs de Apache con lo que no pierde ni una sóla visita. Cualquier otro método (usando javascript en cliente, PHP en el servidor, etc.) es susceptible de perder información en determinadas circunstancias.
Pantalla principal de Awstats

El hecho de que no ofrezca datos en tiempo real se puede mitigar haciendo que los logs se procesen en intervalos más pequeños o, incluso, añadiendo un enlace para que estos se procesen bajo demanda. El filtro de estadísticas diario que por defecto no es posible también puede conseguirse mediante la extensión Day by Day que también veremos en esta entrada.

Los únicos requisitos previos son tener instalados perl (necesario para ejecutar los scripts de awstats) y nuestro servidor web que, en este ejemplo, será Apache. La instalación en una debian es tan fácil como siempre (apt-get install awstats) pero la versión en el repositorio estable es la 6.9.5 que tiene más de dos años. Si quieres trabajar con la última versión (la 7.0 o la beta 7.1 en estos momentos) lo más fácil es bajarte el paquete desde aquí y descomprimirlo en tu servidor en el directorio /usr/local. Todo lo que vamos a ver a continuación supone que estamos instalando por este segundo método.

A continuación creamos un virtual host en nuestro Apache para realizar el acceso a las estadísticas o incluimos las siguientes líneas (sacadas del modelo que tendremos en el fichero /usr/local/awstats/tools/httpd_conf) en, por ejemplo, la instancia por defecto del mismo:

Alias /awstatsclasses "/usr/local/awstats/wwwroot/classes/"
Alias /awstatscss "/usr/local/awstats/wwwroot/css/"
Alias /awstatsicons "/usr/local/awstats/wwwroot/icon/"
Alias /awstatsjs "/usr/local/awstats/wwwroot/js/"
ScriptAlias /awstats/ "/usr/local/awstats/wwwroot/cgi-bin/"


     Options None
     AllowOverride None
     Order allow,deny
     Allow from all

Una vez editado debemos de pedir a apache que vuelva a leer la configuración para que tengan efecto estas líneas (service apache2 reload).

Vamos ahora a crear un fichero de configuración para awstats. Partimos del modelo que habrá en /usr/local/awstats/wwwroot/cgi-bin/awstats.model.conf. Lo copiamos en el directorio /etc/awstats (que debemos de haber creado antes) con un nombre distintivo (por ejemplo awstats.miweb001.conf) y lo editamos para adecuarlo a nuestra instalación. Mucha antención a la partícula miweb001 que debería de identificar a la web cuyas estadísticas queremos ver y que tendremos que usar en otras instrucciones más adelante. Las líneas que deberías de modificar sobre la configuración por defecto (y que no aparecen consecutivamente como aquí ¿eh?) son estas:

LogFile="/var/log/apache2/apache2-myweb001-access.log"
SiteDomain="www.miweb001.es"
HostAliases="www.miweb0001.com www.miweb001.net"
DNSLookup=1
DirData="/var/lib/awstats"
DirCgi="/awstats"
DirIcons="/awstatsicons"
AllowToUpdateStatsFromBrowser=1
AllowFullYearView=3

Las tres primeras definen, respectivamente, el emplazamiento del fichero de logs de la instancia de apache, el nombre de la web y los posibles alias a través de los cuales podamos acceder a la misma (separados estos por espacios). La cuarta línea habilita la resolución completa por DNS de las IP’s de acceso. En la quinta línea decimos donde queremos que deje los ficheros resultantes de procesar los ficheros de log (¡no olvides crearlo!). En la sexta y la séptima se define donde están los diferentes componentes de awstats según los hemos definido anteriormente en el fichero de configuración de la instancia de apache. Las dos últimas líneas hacen que nos aparezca un enlace que permita refrescar los datos en cualquier momento y habilitan la posibilidad de realizar informes estadísticos de todo un año.

Existen muchos otros parámetros interesantes o útiles para activar plugins, funcionalidades extras, personalización, etc, pero yo te recomiendo que lo eches a funcionar sólo con esto que es lo mínimo y luego ya te metas en experimentar otras cosas. Sobre todo si no tienes mucha experiencia en estas lides.

Vamos ahora a programar el cron de nuestra máquina para que procese los logs de apache, por ejemplo, cada 15 minutos. Creamos un fichero llamado /etc/awstats/cron-awstats.sh, le damos permisos de ejecución y copiamos lo siguiente en él:

#!/bin/sh
perl /usr/local/awstats/wwwroot/cgi-bin/awstats.pl -config=miweb001 -update
perl /usr/local/awstats/wwwroot/cgi-bin/awstats.pl -config=miweb001 -databasebreak=day -update

Fijate bien en que la partícula que aparece a continuación de config en ambas líneas (miweb001) debe de ser exactamente la misma que aparece en el nombre del fichero de configuración que hemos creado anteriormente (awstats.miweb001.conf). Bien, ahora editamos el fichero /etc/crontab y añadimos al final del mismo lo siguiente:

*/15 * * * * root /etc/awstats/cron-awstats.sh > /dev/null

Y listo. Nos quedan unos pequeños retoques pero ahora ya cada 15 minutos se analizarán los logs del servidor de apache y podremos consultar la información, ya procesada, en la siguiente URL. Atención, de nuevo, a la partícula miweb001:

http://ip-del-servidor/awstats/awstats.pl?config=miweb001

Si, como es habitual, nuestro sistema rota los ficheros de log tenemos que evitar perder el procesado de los últimos minutos antes de una rotación. Para ello editamos el fichero /etc/logrotate.d/apache2 y bajo la línea donde pone prerotate volvemos a ejecutar el procedimiento que llama a los scripts de awstats:

prerotate
     /etc/awstats/cron-awstats.sh > /dev/null

Lo último que nos resta por hacer es habilitar la posibilidad de mostrar informes diarios. Para ello usaremos la extensión Day by Day. Para instalarla y configurarla tenemos que descargarnos la última versión, descomprimirla y copiar los dos archivos javascript (day-by-day-head.js y day-by-day-end.js) en el directorio /usr/local/awstats/wwwroot/js/. A continuación editamos el fichero de configuración de awstats (recuerda que en nuestro ejemplo se llama awstats.miweb001.conf y está en el directorio /etc/awstats) y reemplazamos los valores de los dos parámetros HTMLHeadSection y HTMLEndSection (casi al final del fichero) por las siguientes líneas:

HTMLHeadSection=""
HTMLEndSection=""

Una vez hecho esto, la pantalla principal de nuestro awstats aparecerá con un cintillo en la parte superior donde podemos escoger un día concreto y, al hacerlo, en la gráfica principal aparecerá un corte de detalle por horas. El resto de los datos también serán referidos al día escogido. Para volver a la vista mensual pulsamos el enlace “Back to monthly report” del cintillo superior.
Pantalla principal de Awstats con la extensión Day by Day

Si tenemos otras instancias web separadas en nuestro servidor y también queremos obtener sus estadísticas sólo tenemos que repetir, por cada una de ellas, dos de los pasos anteriores:

  • Crear un fichero de configuración separado en el directorio /etc/awstats con los datos pertinentes. No olvides incluir las líneas finales que hemos visto para la extensión Day by Day.
  • Añadir un par de líneas adicionales correspondientes a la nueva instancia en el fichero /etc/awstats/cron-awstats.sh

La URL para consultar las estadísticas de estas nuevas instancias será también diferente, claro. Y recuerda que la partícula distintiva que usamos en el nombre del fichero de configuración será la que nos permitirá construirla.

ACTUALIZACI?N: Hace unos meses apareció Apache2Piwik un script que importa los logs de Apache a la base de datos de Piwik. Habrá que echarle un vistazo y si el consumo de memoria es bajo lo mismo volvemos a hablar de ello por aquí…

Autenticación en Apache (y II): digest y con mySQL

apache Continuamos hoy con otros dos métodos de autenticación en Apache. El primero que vamos a ver recupera los problemas de gestión de usuarios y contraseñas pero soluciona el problema de la transferencia de contraseñas en claro sin necesidad de usar SSL. Consiste en usar la autenticación mediante digest. El procedimiento, como veréis, es muy similar al visto el otro día con la autenticación básica pero cambiando algunas de las directivas y usando la utilidad htdigest en lugar de htpassword para crear el fichero de contraseñas. El módulo de autenticación necesario suele venir con Apache pero no habilitado por defecto. Para activarlo usamos la utilidad a2enmod y, a continuación reiniciamos el servidor Apache:

$ sudo a2enmod auth_digest
$ sudo /etc/init.d/apache2 restart

Luego incluimos una sección como esta en el fichero de configuración de nuestro Virtual Host:

<Directory "/var/www/miweb/privado">
     Order deny,allow
     AuthType Digest
     AuthName "dominio"
     AuthUserFile "/etc/claves/digest.txt"
     <Limit GET POST>
          Require valid-user
     </Limit>
</Directory>

Como vemos, es muy similar a la configuración necesaria en la autenticación básica. Sólo dos notas: el fichero donde se dejan las contraseñas se indicaba con la directiva AuthDigestFile hasta la versión 2.2 de apache. Ahora, como veis en el ejemplo, es AuthUserFile. Y dos: la directiva AuthName que en la autenticación básica se usaba para mostrar un mensaje en la ventana que pide el usuario y contraseña, ahora se usa también para identificar un nombre de dominio (realm) que debe de coincidir con el que aparezca después en el fichero de contraseñas. Dicho esto, vamos a generar dicho fichero con la utilidad htdigest:

# htdigest -c /etc/claves/digest.txt dominio josemaria
Adding password for josemaria in realm dominio.
New password:
Re-type new password:

Al igual que ocurría con htpassword, la opción -c (create) sólo debemos de usarla al crear el fichero con el primer usuario. Luego añadiremos los restantes usuarios prescindiendo de ella. A continuación vemos el fichero que se genera después de añadir un segundo usuario:

josemaria:dominio:8d6af4e11e38ee8b51bb775895e11e0f
gemma:dominio:dbd98f4294e2a49f62a486ec070b9b8c

El último método que vamos a ver usa una base de datos de mySQL como repositorio de contraseñas. Esto nos permitirá preparar de forma fácil unas páginas para gestionarlas por personal no experto o, incluso, permitir al propio usuario final que haga cambios por si mismo, crear algún método de recuperación automático por email, etc. Esto, combinado con un cifrado SSL, nos proporciona un método cómodo, flexible y suficientemente seguro para la mayoría de los casos. Lo primero que debemos de hacer es instalar el módulo que nos proporciona este modelo de autenticación, activarlo y reiniciar nuestro apache:

$ sudo apt-get install libapache2-mod-auth-mysql
$ sudo a2enmod auth_mysql
$ sudo /etc/init.d/apache2 restart

A continuación necesitamos crear una base de datos adecuada en el servidor mysql. Los únicos campos imprescindibles son los dos correspondientes al usuario y contraseña, tal y como se muestran en la siguiente imagen. El resto, a nuestro gusto y dependiendo de si planeamos hacer alguna página para gestionarlos y las funcionalidades que queremos que tenga (nombre completo, dirección de email, etc.)

base de datos para autenticación con Apache

Necesitaremos, además, un usuario de mySQL con acceso a estas tablas para que Apache pueda usarlo. Con concederle privilegios de lectura (SELECT) nos basta:

usuario para la autenticación

Añadimos un par de registros a nuestra base de datos para hacer pruebas:
usuarios para las pruebas de acceso

Y, por último, editamos la sección correspondiente en la configuración de nuestro Virtual Host y pedimos a apache que haga un reload de la misma:

<Directory "/var/www/miweb/privado">
     AuthType Basic
     AuthName "Zona Privada"
     AuthBasicAuthoritative Off
     AuthMYSQL on
     AuthMySQL_Authoritative on
     Auth_MySQL_Host localhost
     AuthMySQL_DB httpdauthmysql
     AuthMySQL_Password_Table usuarios
     Auth_MySQL_User apacheauth
     Auth_MySQL_Password 4pache3Sql
     AuthMySQL_Username_Field login
     AuthMySQL_Password_Field pwd
     AuthMySQL_Empty_Passwords off
     AuthMySQL_Encryption_Types Plaintext
     Require valid-user
</Directory>

El significado de los campos a personalizar para nuestra configuración es fácilmente distinguible:

  • AuthMySQL_DB y Auth_MySQL_Host determinan los nombres de la base de datos y el servidor donde esta reside, que usaremos para la autenticación de usuarios.
  • Auth_MySQL_User y Auth_MySQL_Password son los datos del usuario que apache usará para leer de la base de datos anterior.
  • AuthMySQL_Password_Table, AuthMySQL_Username_Field y AuthMySQL_Password_Field describen, respectivamente, la tabla donde se guardan las credenciales de los usuarios con permiso de acceso y los campos que usaremos para almacenar sus usuarios y contraseñas.

El campo Auth_MySQL_Encryption_Types, por último, nos permite definir la forma en que la contraseña se guarda en nuestra base de datos y admite múltiples formas. Tal y como está en el ejemplo anterior, indicamos que la contraseña se guardará en texto plano. Si queremos hacerlo un poco más serio y no guardar la contraseña así podríamos, por ejemplo, almacenar un hash de la misma. Existen diversos métodos para esto, pero lo más sencillo es variar el valor de este campo por lo siguiente:

AuthMySQL_Encryption_Types Crypt

Esto nos permitirá usar las mismas firmas generadas por la utilidad htpasswd que vimos aquí o, en general, las creadas a partir de la llamada a la función crypt(). Copiamos el hash al campo pwd de la base de datos antes generada, volvemos a hacer un reload a nuestro apache y listo.

Autenticación en Apache: básica y PAM

apache El servidor web Apache puede acompañarse de distintos módulos para proporcionar diferentes modelos de autenticación. Hoy vamos a echarle un vistazo a la configuración básica de dos de los más utilizados y sencillotes. Vamos allá.

La primera forma que veremos es la más simple. Usamos para ello el módulo de autenticación básica que viene instalada “de serie” con cualquier Apache. La configuración que tenemos que añadir en el fichero de definición del Virtual Host a proteger podría ser algo así:

<Directory "/var/www/miweb/privado">
     Order deny,allow
     AuthUserFile "/etc/apache2/claves/passwd.txt"
     AuthName "Palabra de paso"
     AuthType Basic
     <Limit GET POST>
          Require valid-user
     </Limit>
</Directory>

La información a personalizar está marcada en negritas. En Directory escribimos el directorio a proteger, que puede ser el raíz de nuestro Virtual Host o un directorio interior a este. En AuthUserFile ponemos el fichero que guardará la información de usuarios y contraseñas que debería de estar, como en este ejemplo, en un directorio que no sea visitable desde nuestro Apache. Ahora comentaremos la forma de generarlo. Por último, en AutnName personalizamos el mensaje que aparecerá en la ventana del navegador que nos pedirá la contraseña. Existen otras directivas adicionales o modificaciones a estas útiles para personalizar el acceso: AuthGroupFile, Require user, Require group, etc. Ya sabéis: Google es vuestro amigo 😉

El fichero de contraseñas se genera mediante la utilidad htpasswd. Su sintaxis es bien sencilla. Para añadir un nuevo usuario al fichero operamos así:

# htpasswd /etc/apache2/claves/passwd.txt carolina
New password:
Re-type new password:
Adding password for user carolina

Para crear el fichero de contraseñas con la introducción del primer usuario tenemos que añadir la opción -c (create) al comando anterior. Si por error la seguimos usando al incorporar nuevos usuarios borraremos todos los anteriores, así que cuidado con esto. Las contraseñas, como podemos ver a continuación, no se guardan en claro. Lo que se almacena es el resultado de aplicar una función hash.

josemaria:rOUetcAKYaliE
carolina:hmO6V4bM8KLdw
alberto:9RjyKKYK.xyhk

Para denegar el acceso a algún usuario basta con que borremos la línea correspondiente al mismo. No es necesario que le pidamos a Apache que vuelva a leer su configuración (/etc/init.d/apache2 reload) cada vez que hagamos algún cambio en este fichero de contraseñas, pero si lo es después de hacer los cambios en el fichero de definición del Virtual Host.

autenticación en apache La principal ventaja de este método es su sencillez. Sus inconvenientes: lo incómodo de delegar la generación de nuevos usuarios en alguien que no sea un administrador de sistemas o de hacer un front-end para que sea el propio usuario quien cambie su contraseña. Y, por supuesto, que dichas contraseñas viajan en claro a través de la red. Si queremos evitar esto último podemos crear una instancia Apache con SSL.

El segundo método que vamos a tocar usa PAM, el propio sistema de autenticación de la máquina donde está instalado nuestro servidor de Apache. Si ya tenemos alguna herramienta de gestión de usuarios y contraseñas en la misma este sistema nos ahora el engorro de tener que generar otro tipo de usuario. En caso contrario no ganamos mucho en este sentido y seguimos teniendo el problema de que las contraseñas viajan en claro con el agravante adicional de que ahora se trata de usuarios de la máquina… así que ya sabéis: o configuráis ssl o usáis el truco que comentamos el otro día por aquí para deshabilitar la conexión remota.

Usaremos para ello el módulo mod_auth_pam y lo primero que tenemos que hacer es instalarlo:

# sudo apt-get install libapache2-mod-auth-pam

En el fichero de configuración del Virtual Host tendríamos que incluir algo así:

<Directory "/var/www/privado">
     AuthType Basic
     AuthName "Palabra de paso"
     AuthPAM_Enabled On
     AuthBasicAuthoritative Off
     AuthUserFile /dev/null
     Require user josemaria gemma fernando araceli
</code>

Si en este caso la directiva usada fuese Require valid-user se permitiría el acceso a cualquier usuario con cuenta en la máquina. Ah, y no olvides hacer un reload de apache después de este cambio.

Sólo nos quedan un par de pasos. Primero, añadir el usuario www-data (el que usa apache) al grupo shadow para que pueda verificar las contraseñas:

usermod -a -G shadow www-data

Y dos, hacer un enlace como el que sigue. Ignoro la causa, pero el motivo de que esto sea necesario es debido a que apache (en los binarios de Debian y Ubuntu, al menos) pretende leer del archivo /etc/pam.d/http mientras que el módulo de autenticación ha creado el archvo /etc/pam.d/apache2. El módulo auth_pam hace años que no está soportado e imagino que esto tiene algo que ver, pero no lo se a ciencia cierta. Simplemente lo leí por ahí...

ln -s /etc/pam.d/apache2 /etc/pam.d/httpd

Y con esto está todo listo. No lo he probado nunca pero también he leído que la autenticación se hace contra el directorio activo si Apache está instalado en una máquina windows configurada en un dominio. Existe también un módulo para autenticar contra un ldap (mod_auth_ldap) que si que he probado y que ya veremos otro día 😉