Archivo de la etiqueta: nginx

Depurando problemas con nginx

[spanish]La semana pasada he estado pegándome con un problemilla que tenía con el nginx de mi blog: de vez en cuando se quedaba frito, no respondía a ningún request y tenía que reinciarlo. Hace tiempo que monté un script que se ejcutaba cada X minutos, comprobaba si se había quedado tostado y lo reiniciaba, pero esto no es una solución, sólo una ñapa termporal.

Como decía he estado unos días mirándolo con más detalle, y preguntando en la lista de correo de nginx. Un detalle importante que me dijeron allí es que el log de error se puede poner en modo «debug», que saca trazas bastante completas de lo que está haciendo cada proceso (con su PID) en cada paso del procesado de las requests: cabeceras, rewrites, proxies, cgis … todo.

error_log /var/log/nginx/$host-error.log debug;

Con esto, sabiendo el PID del proceso que está chungo, es muy fácil ver qué estaba haciendo en el momento que se ha quedado colgado. Y para juntar una cosa con la otra, el script que usaba para detectar los cuelgues, en el que añadí varias métricas como un netstat -nap (que saca el PID), un ps, vmstat, etc.:

#!/bin/sh

TIMEOUT=20
CHECK=http://localhost/wp-admin/
LOG=/var/log/checkWeb/checkWeb-$(date +%Y%m%d).log
LOGR=/var/log/checkWeb/restart-$(date +%Y%m%d).log
TMP=/tmp/checkWeb-$RANDOM

if ! wget -t 1 -o /dev/null -O /dev/nul -T $TIMEOUT $CHECK
then
echo "ERROR, reiniciando nginx"
echo "** REINICIANDO **" >> $TMP
date >> $TMP
echo "- CLOSE_WAIT:" >> $TMP
netstat -nap | grep -c CLOSE_WAIT >> $TMP
echo "- vmstat" >> $TMP
vmstat 1 5 >> $TMP
echo "- free" >> $TMP
free >> $TMP
echo "- ps" >> $TMP
ps aux >> $TMP
echo "- netstat" >> $TMP
netstat -nap >> $TMP
echo "" >> $TMP
echo "" >> $TMP

#       pkill -9 -f php-cgi
pkill -9 -f nginx
sleep 1s
/etc/init.d/nginx start

cat $TMP
cat $TMP >> $LOG
date >> $LOGR
fi

rm -rf $TMP

De esta forma, cada vez que localhost/wp-admin (estaba debuggeando un WordPress) no respondía, aparte de reiniciar nginx, me guardaba en un log bastante información sobre el sistema. Con el tiempo vi que siempre que se quedaba colgado, había varios procesos nginx con sockets en el estado CLOSE_WAIT. Con ese PID y el error.log de nginx en modo debug, vi que siempre que un proceso se quedaba colgado con sockets en CLOSE_WAIT, lo último que había estado sirviendo era lo mismo: en el blog tengo varios ejemplos de cómo ejecutar servidores con daemontools; daemontools utiliza «named pipes» (FIFO) en disco, que básicamente si no tienen un proceso alimentándolos, para el que los lee son un agujero negro; cuando nginx se ponía a servir uno de estos FIFO es cuando se quedaba frito.

Lo curioso es que no había tenido problemas ni con Apache ni con lighttpd. Aunque desde luego el problema es que esos FIFO no deberían estar ahí. Los quité y llevo más de cinco días sin cuelgues, cuando antes tenía 3-4 al día mínimo.[/spanish]

[english]Last week I’ve been debugging a problem I had with this site’s nginx server: from time to time it hanged and I had to restart the process. Some time ago I wrote a little script that checked if it was running OK and restarted it otherwise, but anyway that wasn’t a real solution.

So I spent some days really looking into it and asking for support and reporting my findings to the nginx mailing list. One useful tip I got there was enabling the «debug» mode on the error log, which shows full traces of the processes (including their PID) as they’re processing the request, the rewrites, upstreams, etc.

error_log /var/log/nginx/$host-error.log debug;

With this extended log and the PID of the process malfunctioning, it’s quite easy finding out what that process was doing right before hanging. In order to find out the PID of the hanged processes, I extended my check-reboot script to log some generic system metrics right before restarting nginx: netstat -nap (which shows the PID), ps, vmstat, etc.

#!/bin/sh

TIMEOUT=20
CHECK=http://localhost/wp-admin/
LOG=/var/log/checkWeb/checkWeb-$(date +%Y%m%d).log
LOGR=/var/log/checkWeb/restart-$(date +%Y%m%d).log
TMP=/tmp/checkWeb-$RANDOM

if ! wget -t 1 -o /dev/null -O /dev/nul -T $TIMEOUT $CHECK
then
echo "ERROR, restarting nginx"
echo "** RESTARTING **" >> $TMP
date >> $TMP
echo "- CLOSE_WAIT:" >> $TMP
netstat -nap | grep -c CLOSE_WAIT >> $TMP
echo "- vmstat" >> $TMP
vmstat 1 5 >> $TMP
echo "- free" >> $TMP
free >> $TMP
echo "- ps" >> $TMP
ps aux >> $TMP
echo "- netstat" >> $TMP
netstat -nap >> $TMP
echo "" >> $TMP
echo "" >> $TMP

#       pkill -9 -f php-cgi
pkill -9 -f nginx
sleep 1s
/etc/init.d/nginx start

cat $TMP
cat $TMP >> $LOG
date >> $LOGR
fi

rm -rf $TMP

This way, each time localhost/wp-admin was unresponsive (I was debugging a WP site), besides restarting nginx I was getting a lot of system info. With time I got to realize that nginx processes were not actually hanging, but some of their sockets got on the CLOSE_WAIT state forever until the process was restarted. Looking for the PID of those processes according to netstat on the error log, the last request they were processing before getting to the CLOSE_WAIT state was always the same: on my blog I have some examples of how running servers with daemontools; daemontools uses named pipes (FIFOs), which can become kind of black holes if there’s no process feeding them; when nginx hit one of these FIFOs, it hanged.

Funny thing is that I never had this problem with either Apache nor lighttpd. But anyway the problem is not nginx but those FIFOs which shouldn’t really be there. I removed them and have had no hanged processes in five days, while before this nginx was restarting 3-4 times a day.[/english]

Pasar URL a minúsculas con nginx

[spanish]

Con nginx y el módulo de Perl embebido es posible reescribir/redirigir todas las visitas a URL en minúsculas añadiendo algo como esto a la configuración:

http {

	...
	perl_modules  perl/lib;

 	perl_set $uri_lowercase 'sub {
   		my $r = shift;
   		my $uri = $r->uri;
   		$uri = lc($uri);
   		return $uri;
 	}';
	...
}

server {
	...

	location / {
               if ( $uri != $uri_lowercase ) {
                       rewrite . http://$host$uri_lowercase;
               }
# or just:
#		rewrite . $uri_lowercase;
	}
	...
}

La primera opción (con el if y el http://$host) genera una redirección HTTP a la URL en minúscula si la URL que pide el cliente no está ya toda en minúscula. Me gusta más esta técnica ya que de alguna forma normaliza todas las URL a una forma canónica en minúsculas. La segunda opción (sin el if ni el http://) simplemente acepta cualquier URL y la reescribe internamente a minúsculas. Si previamente se renombran todos los ficheros y directorios a minúsculas se consigue un efecto «case insensitive» similar al de Windows.

Quiero dar las gracias al siguiente post de la lista de usuarios de nginx en el que me he inspiarado ;D para conseguir hacer esto. En efecto, la documentación y ejemplos del módulo de Perl de nginx son muy escasos. :-(

http://forum.nginx.org/read.php?2,39425,39944

De todas formas esta solución tiene dos problemas:

  • Hace falta el módulo de Perl que según los propios desarrolladores es experimental y puede tener memory leaks.
  • Aunque la redirección se hace en el location que esté el rewrite, el cálculo de la URL en minúscula al estar en la sección «http» de la configuración se hace para todas las peticiones que lleguen al servidor, haya que cambiarles la URL o no. Es decir, si tienes un servidor virtual con 10 dominios y sólo en una parte de uno de ellos es necesario reescribir las URL, aunque sólo se redirija el tráfico en ese caso, la URL en minúscula se calcula para todas las peticiones de todos los dominios.

Supongo que si en lugar de definir la variable en minúscula se escribiera una función Perl si que se podría restringir a un location determinado, tengo que mirarlo con más detenimiento aunque con la poca documentación que hay de esto la cosa está chunga. Otra opción es escribir un módulo en C que lo haga.

¿Y por qué querría alguien reescribir todas las URL a minúsculas? En el trabajo estamos migrando una aplicación Tomcat vieja desarrollada y que hasta ahora corría en un servidor Windows a Linux, y parece ser que los desarrolladores originales no prestaron nada de atención al tema de las mayúsculas y minúsculas en los nombres de ficheros y directorios, enlazando a veces bien los ficheros según mayúsculas y minúsculas pero otras veces no. Esto en Windows no es un problema, pero en Linux si que lo es. Así que lo que hemos hecho ha sido renombrarlo todo a minúsculas y configurar esta redirección a minúsculas en los servidores nginx que tenemos delante de la granja de Tomcats. Aún así todavía quedan algunos casos problemáticos aislados que han tenido que ser tratados uno a uno, como includes en los JSP a ficheros en mayúsculas (estos casos no «salen» al nginx si no que los trata internamente Tomcat, con lo que la redirección no se aplica), pero el grueso de los accesos a imágenes, videos, páginas HTML estáticas, etc. mal enlazados se ha solucionado de un plumazo sin tener que tocar el código.

[/spanish][english]

With nginx and its embedded Perl module it is possible to automatically rewrite/redirect every incoming request to a lowercase URL with something like this:

http {
	...
	perl_modules  perl/lib;

 	perl_set $uri_lowercase 'sub {
   		my $r = shift;
   		my $uri = $r->uri;
   		$uri = lc($uri);
   		return $uri;
 	}';
	...
}

server {
	...

	location / {
               if ( $uri != $uri_lowercase ) {
                       rewrite . http://$host$uri_lowercase;
               }
# or just:
#		rewrite . $uri_lowercase;
	}
	...
}

The first option (with the if and http://$host) sends an HTTP redirect to the lowercase URL if the requested URL is not completely in lowercase. I like this approach better as it kind of normalizes all URL to a canonical form. The second option (without if nor http://) just accepts any URL and internally rewrites them to lowercase: if you rename all your directories and files to lowercase, this will imitate Windows’ case-insensitive behaviour.

Kudos to the following post on the nginx users list were I draw the «inspiration» :D from to get this done. The embedded perl doc and examples are indeed scarce. :-(

http://forum.nginx.org/read.php?2,39425,39944

Anyway I see two problems in this approach:

  • you need the embedded perl module which according to the docs is experimental and can lead to memory leaks.
  • the actual redirection is done with the rewrite, which you can put on the location you need. But the URL lowercase calculation, being on the «http» section of the config, is done for each and every request arriving to your server. Say you have a virtual server with 10 domains and you only need this on one particular location of one of them. The lowercase URL is going to be calculated for every request of every domain.

I guess that by defining a perl function the URL calculation could be restricted to a particular location, have to look into it further. Another option is writing a C module.

And why would anybody want to rewrite every URL to lowercase? We’re migrating a legacy Tomcat-based application from Windows to Linux, and it seems the original developers were not «case-aware» at all, they mixed upper and lowercase in the filenames without regarding how the actual files were named. On Windows this is not a problem but when moving the app to Linux it is. So we have renamed every file and directory to lowercase and used the previous configuration on the nginx servers that load-balance our Tomcat farm. Some corner-cases remain (includes in JSP files, which don’t route back to the nginx server and are dealt internally by Tomcat) and have been dealt with one by one, but the bulk of wrongly addressed images, videos, links to HTML files, etc. now works.

[/english]

Parche para autenticar nginx con Amazon S3

Vuelvo tras el paréntesis del CCNA (4 módulos aprobados, sólo me falta el examen final de certificación) con algo que he estado haciendo esta semana en el curro: un parche poder usar la autenticación de S3 al hacer de proxy con nginx.

En el curro estamos montando una arquitectura de servidores con EC2, S3 y ELB: los EC2 están distribuidos por capas, con servidores nginx (web y proxy) delante de los servidores de aplicaciones Tomcat, y aparte en S3 tenemos todo el material «pesado» (fotos, vídeos, descargas, etc.) Como nginx puede hacer de proxy, en lugar de mandar a los usuarios directamente a S3 hacemos de proxy y mapeamos el contenido en nuestro propio dominio, con lo que además nos ahorramos unas pelillas (S3 aparte de por ancho de banda y espacio ocupado, cobra por número de conexiones).

El problema viene porque tenemos una gran mayoría de archivos que son públicos (imágenes, iconos y otros elementos del diseño y archivos multimedia) y otros que sólo queremos que los puedan descargar los usuarios registrados en la página. Un paso hacia la solución es usar el parche Secure Download, que genera URL que expiran tras unos minutos con lo que los elementos privados estarían asegurados, pero sólo a través de nuestra página. Si alguien descubre el nombre de nuestro bucket y el esquema de URL (y no es muy complicado….) podría descargar cualquier cosa directamente de S3.

Con este parche se puede acabar de securizar las descargas: se hacen privadas en el bucket de S3, de forma que sólo autenticándose puedan descargarse (el usuario final ya no podría descargarlas aunque averigüe la URL), y nginx si que puede acceder autenticándose a la vez que protege en local las URL con el Secure Download.

El parche está disponible aquí: nginx proxy – Amazon S3 autentication

nginx, memcached y HTTP 304

[spanish]Estoy trabajando en la arquitectura de servidores para una nueva versión de un portal web. Va a estar basada en servidores Amazon-EC2, con un front end con nginx y varios tomcat de back end. Los servidores nginx harán de proxy-caché para ciertas páginas dinámicas (resultados de búsquedas, consultas a los web-services internos) y también tendrá una caché de páginas estáticas gestionada por nosotros en código con memcached.

Al empezar a integrar memcached en esta estructura vi que ya no se enviaban respuestas HTTP 304, todo lo que se servía del memcached eran HTTP 200. Después de usar el comodín de Google parece que es el resultado esperado, ya que memcached no almacena la fecha de modificación de las páginas para poder servir la cabecera HTTP «last-modified», con lo que los clientes tampoco envían la «if-modified-since» y no hay 304. Así que le he metido mano al código, el parche está aquí.

Estoy bastante sorprendido de lo sencillo que ha sido y lo simple que es el parche, ya que no he tenido que desarrollar la lógica para ver si la página en memcache es más reciente que la que tiene el cliente en su caché y devolver 304 ó 200. Una vez que desde nginx-memcached devuelvo la cabecera «last-modified», parece que nginx se encarga el sólo de comparar cabeceras y devolver 304 ó 200 según corresponda. ¡Y funciona!

He estado probando esto esta mañana en el trabajo y parece que todo funciona como toca. Mañana haré más pruebas y modificaré el parche si hace falta. Mientras tanto si alguien se anima a probarlo y le funciona (o no), soy todo oídos. :-)[/spanish]

[english]I’m working on the server architecture for a new web portal. It’s going to be Amazon-EC2based and will have a nginx front end to several tomcat back end servers. The nginx servers will proxy-cache some dynamic pages (search results, internal web-services queries) and will also have a static page cache based on memcached.

While starting to roll memcached into the solution I noticed that I stoped receiving HTTP 304 responses, everything served off memcached where 200. After digging a little bit on Google I found out it was the expected behavior as memcached doesn’t store the timestamp (last-modified) of the files. So I’ve given it a try and developed a quick patch. You can find it here.

I’m quite puzzled at the simplicity of the patch, as I didn’t need to code the logic to check if the cached page is more recent than the client’s one and return 302 or 200. After successfully returning the last-modified header from memcached, nginx seems to do the rest of the job somewhere on its own!

I have tested the patch today at work and everything seems to work OK. Will do more extensive testings tomorrow and update the path if needed. In the meantime, if someone gives it a try and works for them (or doesn’t), I’m waiting for your feedback.[/english]