Archivo de la categoría: daemontools

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]

exec

[spanish]exec es un comando interno de la shell que fuerza a que un binario se ejecute sobre el propio proceso de la shell en vez de sobre un hijo.

Cuando en shell-script se invoca un comando, normalmente se crea un proceso hijo que lo ejecute. Esto a nivel de llamadas al sistema sería el típico:


if( (pid=fork()) == 0) {
    exec(command);
    exit();
}
wait();

En general ésto es lo que queremos, para poder seguir ejecutando comandos en la shell. Sin embargo puede haber situaciones en las que no, casos típicos:

  • tenemos un programa que va a monitorizar la ejecución de otro, y si hay un proceso shell por en medio no funciona bien, pero necesitamos esa shell para lo que sea (p.ej. preparar variables de entorno, ejecutar el comando con nice, etc.)
  • lanzamos un programa vía un shell-script en MacOS y en el dock salen dos iconos

Con exec en lugar de hacer un fork que ejecute el comando, se ejecuta directamente sobre el proceso actual. Importante darse cuenta que, por lo tanto, cualquier comando que hubiera detrás del exec en la shell nunca se llega a ejecutar, pues el comando digamos que «suplanta» a la shell.


#!/bin/sh

#inicializar variables, parsear parámetros, etc.
export IP=$1

exec nice comando $*

[/spanish]
[english]
exec is a built-in shell command that forces a binary to be executed by the currently running shell process instead of forking the process and running the binary on that child process.

When you run a command on a shell-script, it forks a child process and runs the command there. On a syscall level this is the classic:


if( (pid=fork()) == 0) {
    exec(command);
    exit();
}
wait();

And this is usually what we want, because we will keep running commands after that one. Nevertheless, sometimes this is a problem, like when:

  • we have a program that’s going to monitor a given process, and it doesn’t run properly if there’s an intermediate shell process but we need to run this second process via a shel-script for whatever reasons (to initialize some variables, run the program with nice, whatever)
  • on MacOS we’re running a program via a shell-script and get two icons on the dock, one for the shell and another one for the program

Running a command with exec forces the shell not to fork, but to run the command directly over the shell process. An important thing to note here is that the shell-script will end there, no further commands of the shell script will be executed as the shell process will be substituted by the command process, so to speak.


#!/bin/sh

# initialize variables, parse command-line parameters, etc.
export IP=$1

exec nice command $*

[/english]

Mi nuevo servidor

[spanish]Señores, señoras, les presento a mi nuevo servidor del que he hablado estos días:[/spanish]
[english]Ladies and gentleman, let me please introduce you to my new server, the one I’ve been blogging about lately:[/english]

dscf0042.JPG dscf0044.JPG

[spanish]¿Qué? ¿Que no lo veis? ¡Si hombre! La caja ésta gris encima del disco iomega, ligeramente más grande que la Fonera

Para el que no conozca éste chisme ya, se trata de un Linksys NSLU2, un pequeño cacharro que cuesta unos 80€ y trae dos puertos USB2 y uno ethernet, permitiendo pincharle discos externos USB y hacerlos accesibles por la red en plan NAS. Y lo mejor de todo: se le puede flashear el firmware cambiándolo por ¡¡una Debian!! :-D

No es muy potente, lleva un procesador XScale (ARM) a 266Mhz y sólo 32Mb de RAM, aunque hay páginas que explican cómo ampliarle la memoria hasta 256Mb. Pero bueno… hace su papel, y sobre todo es pequeño, no ocupa lugar, no hace ruido y apenas consume electricidad.

Yo por ahora lo tengo con todo ésto y aguanta (tira de swap bastante, eso si):

[/spanish][english]

What? You don’t see it? Yes! The small grey box on top of the iomega disk, slightly bigger than the Fonera

In case you don’t know it yet, it’s a Linksys NSLU2, a small device around $100 that comes with two USB2 ports and an ethernet connection. Plug an external USB hard drive to it and it’ll become available over the network like a NAS share. And the best part is: you can flash its firmware and install Debian!! :-D

It’s not that powerful, it has an XScale (ARM) processor at 266Mhz and only 32Mb of RAM. There are pages explaining how to install up to 256Mb. Nevertheless, it works and is small, doesn’t make noise, and has a small electrical consumption.

Up to now I’m running the following on it and it works quite well:

[/english]

top-nslu2.png


# cat /proc/cpuinfo
Processor	: XScale-IXP42x Family rev 2 (v5l)
BogoMIPS	: 266.24
Features	: swp half fastmult edsp
CPU implementer	: 0x69
CPU architecture: 5TE
CPU variant	: 0x0
CPU part	: 0x41f
CPU revision	: 2
Cache type	: undefined 5
Cache clean	: undefined 5
Cache lockdown	: undefined 5
Cache format	: Harvard
I size		: 32768
I assoc		: 32
I line length	: 32
I sets		: 32
D size		: 32768
D assoc		: 32
D line length	: 32
D sets		: 32

Hardware	: Linksys NSLU2
Revision	: 0000
Serial		: 0000000000000000

# free
             total       used       free     shared    buffers     cached
Mem:         29988      28988       1000          0        404       4808
-/+ buffers/cache:      23776       6212
Swap:       979924      41164     938760

# uname -a
Linux eliza 2.6.18-6-ixp4xx #1 Tue Feb 12 00:57:53 UTC 2008 armv5tel GNU/Linux

# pstree
init-+-afpd---afpd
     |-atalkd
     |-atd
     |-avahi-daemon---avahi-daemon
     |-cnid_metad
     |-cron
     |-dbus-daemon
     |-events/0
     |-getty
     |-khelper
     |-klogd
     |-ksoftirqd/0
     |-kthread-+-aio/0
     |         |-kblockd/0
     |         |-khubd
     |         |-3*[kjournald]
     |         |-kmirrord
     |         |-kpsmoused
     |         |-kseriod
     |         |-kswapd0
     |         |-2*[pdflush]
     |         |-scsi_eh_0
     |         `-usb-storage
     |-mtdblockd
     |-nmbd
     |-papd
     |-portmap
     |-rpc.statd
     |-slpd
     |-smbd---smbd
     |-sshd---sshd---sshd---bash---su---bash---pstree
     |-svscanboot-+-readproctitle
     |            `-svscan-+-supervise---dnscache
     |                     |-3*[supervise---multilog]
     |                     |-supervise---tinydns
     |                     `-supervise---mlnet---mlnet---mlnet
     |-syslogd
     `-udevd

Scripts daemontools para lighttpd y PHP

[spanish]

He preparado unos scripts para controlar con daemontools el lighttpd y los procesos PHP con spawn-fcgi. Aquí está el README, el tar con los scripts, y aquí se puede navegar por los directorios.

PD: si, me gusta daemontools. Me ayuda a que un montón de servicios no caigan aunque un servidor pueda tener un fallo puntual y algún proceso muera, evitando que me despierten a las tantas de la noche. Es un gran invento. :)

[/spanish]

[english]

I’ve prepared a set of daemontools scripts to launch and monitor lighttpd and its PHP processes spawned with spawn-fcgi. Here is the README, a tar file with the scripts, and here you can browse the directories with all the scripts.

PS: yes, I like daemontools. It helps me achieving high availability with many services, keeping them up even when a server misbehaves and some process dies. This avoids a lot of late night calls. It’s a great invention. :)

[/english]