viernes, 13 de noviembre de 2015

Instalación del cliente de DB2 9.7 en Debian 7 (wheezy) y 8 (jessie)

Es muy común que las aplicaciones que solicitan cumplir ciertos requerimientos no sean muy específicas con cuales deben cumplir. Un ejemplo es el cliente de DB2.

Al ejecutar el script de comprobación aparece en primer lugar el siguiente mensaje de error:

root@srv----:/tmp/client# ./db2prereqcheck 
WARNING:
   Can't use string to find the version of libstdc++.
   Check the following web site for the up-to-date system requirements
   of IBM DB2 9.7
   http://www.ibm.com/software/data/db2/udb/sysreqs.html
   http://www.software.ibm.com/data/db2/linux/validate

Traté de hacer una búsqueda de ese archivo en /usr/lib y allí estaba... instalé la versión de 32 bits, las librerías de desarrollo, etc y seguía sin superar ese requerimiento... hasta que se me encendió la bombilla e instalé, siguiendo la ruta de instalar librerías de desarrollo, el compilador "g++":

root@srv----:/tmp/client# apt-get install g++
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias       
Leyendo la información de estado... Hecho
El paquete indicado a continuación se instaló de forma automática y ya no es necesario.
  php5-ldap
Utilice «apt-get autoremove» para eliminarlo.
Se instalarán los siguientes paquetes extras:
  binutils cpp cpp-4.9 g++-4.9 gcc gcc-4.9 libasan1 libatomic1 libc-dev-bin libc6-dev
  libcilkrts5 libcloog-isl4 libgcc-4.9-dev libgomp1 libisl10 libitm1 liblsan0 libmpc3
  libmpfr4 libquadmath0 libstdc++-4.9-dev libtsan0 libubsan0 linux-libc-dev manpages-dev
Paquetes sugeridos:
  binutils-doc cpp-doc gcc-4.9-locales g++-multilib g++-4.9-multilib gcc-4.9-doc
  libstdc++6-4.9-dbg gcc-multilib make autoconf automake libtool flex bison gdb gcc-doc
  gcc-4.9-multilib libgcc1-dbg libgomp1-dbg libitm1-dbg libatomic1-dbg libasan1-dbg
  liblsan0-dbg libtsan0-dbg libubsan0-dbg libcilkrts5-dbg libquadmath0-dbg glibc-doc
  libstdc++-4.9-doc
Se instalarán los siguientes paquetes NUEVOS:
  binutils cpp cpp-4.9 g++ g++-4.9 gcc gcc-4.9 libasan1 libatomic1 libc-dev-bin libc6-dev
  libcilkrts5 libcloog-isl4 libgcc-4.9-dev libgomp1 libisl10 libitm1 liblsan0 libmpc3
  libmpfr4 libquadmath0 libstdc++-4.9-dev libtsan0 libubsan0 linux-libc-dev manpages-dev
0 actualizados, 26 nuevos se instalarán, 0 para eliminar y 0 no actualizados.
Se necesita descargar 45,6 MB de archivos.
Se utilizarán 136 MB de espacio de disco adicional después de esta operación.
¿Desea continuar? [S/n] s

Por fin había superado el primer tropezón, ahora faltaba el segundo:

root@srv----:/tmp/client# ./db2prereqcheck 
WARNING:
   The 32 bit library file libstdc++.so.6 is not found on the system. 
   32-bit applications may be affected.

Éste ya era obvio, hay que empezar activando el soporte multiplataforma y agregar la arquitectura i386:

root@srv----:/tmp/client# dpkg --add-architecture i386 && apt-get update

Por último instalar libstdc++ para la arquitectura i386:

root@srv----:/tmp/client# apt-get install libstdc++6:i386
Leyendo lista de paquetes... Hecho
Creando árbol de dependencias       
Leyendo la información de estado... Hecho
El paquete indicado a continuación se instaló de forma automática y ya no es necesario.
  php5-ldap
Utilice «apt-get autoremove» para eliminarlo.
Se instalarán los siguientes paquetes extras:
  gcc-4.9-base:i386 libc6:i386 libc6-i686:i386 libgcc1:i386
Paquetes sugeridos:
  glibc-doc:i386 locales:i386
Se instalarán los siguientes paquetes NUEVOS:
  gcc-4.9-base:i386 libc6:i386 libc6-i686:i386 libgcc1:i386 libstdc++6:i386
0 actualizados, 5 nuevos se instalarán, 0 para eliminar y 83 no actualizados.
Se necesita descargar 5.639 kB de archivos.
Se utilizarán 13,9 MB de espacio de disco adicional después de esta operación.
¿Desea continuar? [S/n] s

Finalmente los requerimientos fueron satisfechos y la instalación pudo comenzar. Espero que le sirva de ayuda a quien lo necesite.

viernes, 20 de marzo de 2015

Error Courier IMAP y Outlook Express en Windows XP: actualizando courier-imap usando repositorios oneiric y wheezy "fijados" (pinned)

Aunque parezca mentira no sólo me quejo en este apartado de que aún haya clientes que sigan usando Windows XP (que de por sí es una irresponsabilidad). El problema en sí es que el sistema operativo del servidor también es anticuado.

Aunque estoy en proceso de migrar a Debian 7 (y en la mayoría de los casos a una configuración basada en ISPConfig) todos los viejos ubuntu server que tenía instalados (hace un par de años migré una ubuntu 10.04, el año pasado un par de ubuntus 12.04, etc), aún tengo un servidor que se resiste debido a que una de las páginas principales del cliente no funciona en versiones actuales de PHP, por lo que pasar de PHP 5.3 a 5.4 supone perder la funcionalidad del negocio y se considera una catástrofe.

Dejando a parte que actualizar dicha aplicación no sería demasiado costoso, tuve que actualizar el hardware y aproveché para hacerlo sobre una instalación limpia (y obsoleta, sin soporte ni actualizaciones de seguridad) de Ubuntu 11.10 Server (oneiric).

Hecha la locura (una espina que tengo clavada y que me costará tiempo quitarla) fui a migrar el servidor de correo y todo iba de maravilla hasta que uno de los clientes empezó a llamar alertado porque su cliente Outlook Express de Windows XP le decía que no podía descargar nuevos mensajes.

Hasta donde me alcanzaba la memoria recordé que ese fue un problema que ya pasamos y que tras buscar mucho por Internet solucioné instando una versión ligeramente superior de courier-imap. Se trataba de un bug conocido que habían arreglado en una versión posterior, por lo que de una instalación originalmente con estos paquetes:

ii  courier-base             0.66.1-1ubuntu3                 Courier mail server - base system
ii  courier-imap             4.9.1-1ubuntu3                  Courier mail server - IMAP server
ii  courier-imap-ssl         4.9.1-1ubuntu3                  Courier mail server - IMAP over SSL
ii  courier-ssl              0.66.1-1ubuntu3                 Courier mail server - SSL/TLS Support

Terminé dejando esto instalado en el servidor, sin recordar de dónde saqué los paquetes.

ii  courier-base             0.66.3-2                        Courier mail server - base system
ii  courier-imap             4.9.3-2                         Courier mail server - IMAP server
ii  courier-imap-ssl         4.9.3-2                         Courier mail server - IMAP over SSL
ii  courier-ssl              0.66.3-2                        Courier mail server - SSL/TLS Support

Mucho me temo, basándome en que ya no aparecen las palabras "ubuntu" en los paquetes, que generé los paquetes actualizados desde el código fuente y sólo instalé los paquetes DEB necesarios en el servidor. El problema es que de ello hace ya tanto tiempo que ya no dispongo de esos paquetes, ¡ni quiero tenerlos!, prefiero que se actualicen junto con el "sistema operativo" durante un apt-get update, aunque obviamente no voy a tener demasiadas actualizaciones.

¿Cómo hice la actualización en el nuevo servidor?: usar repositorios de oneiric y wheezy (debian 7) de manera simultánea.

Como he dicho, quería evitar usar paquetes generados desde las fuentes para evitar "tragarme" un agujero de seguridad que hubiera sido subsanado por una actualización. Con esta solución al menos el servidor de imap estará actualizado y al día.

¿Cómo se hace esta proeza? Fácil si seguimos las instrucciones que aparecen en la propia página de ubuntu: Pinning Howto

Para empezar definimos la distribución objetivo por defecto ("oneiric") en un archivo llamado, por ejemplo, /etc/apt/apt.conf.d/00default:

APT::Default-Release "oneiric";

Posteriormente configuramos el repositorio de Debian Wheezy (obtenido a través de este generador, cambiando "stable" por "wheezy" para evitar sorpresas en un futuro cercano) en /etc/apt/sources.list.d/wheezy.list:

deb http://ftp.es.debian.org/debian wheezy main

Creamos el archivo /etc/apt/preferences.d/wheezy.pref con el siguiente contenido:

Package: courier-imap courier-imap-ssl courier-base courier-ssl
Pin: release n=wheezy
Pin-Priority: 1000

Package: *
Pin: release n=wheezy
Pin-Priority: -1

Y posteriormente creamos el archivo /etc/apt/preferences.d/oneiric.pref con el siguiente contenido:

Package: *
Pin: release n=oneiric
Pin-Priority: 990

Hay que notar que en otros "howtos" de debian y apt-preferences fija las prioridades mediante la "suite" ("archive") a=stable, mientras que en la guía de ubuntu y mi solución usamos "codename" n=wheezy para ser homogéneos.

Bien, ¿qué son esas prioridades?

  • 1000: Toma preferencia sobre cualquier otra versión disponible de igual o superior versión. Sólo la prioridad 1001 permite bajar de versión un paquete previamente instalado. De este modo nos aseguraremos que la versión de los paquetes requeridos será la disponible en los repositorios de wheezy.
  • 990: Prioridad normal, con especial cuidado de que no será actualizado por ningún paquete que no sea de la distribución objetivo configurada. Sólo se instalarán paquetes nuevos y se actualizarán los existentes con aquellos pertenecientes a "oneiric".
  • -1: Sólo puedes instalar un paquete de este repositorio si manualmente lo indicas. Por ejemplo, se podría hacer un apt-get install traceroute/wheezy para forzar la instalación del traceroute disponible en wheezy, y no en oneiric.

Ahora basta con hacer un apt-get update para que se actualicen sólo los paquetes deseados a la versión disponible en los repositorios wheezy.

Solución alternativa 1: poniendo una prioridad a wheezy inferior a oneiric (por ejemplo 500 o 100) todo funcionaría igual con apt-get update (no se metería de manera automática un paquete más actual de wheezy). Sin embargo si hacemos un apt-get dist-upgrade podremos "liarla parda" ya que deja de hacer uso la distribución objetivo y usa las versiones de paquete más actuales, por lo que prefiero dejarle una prioridad -1.

Solución alternativa 2: dejando únicamente una prioridad -1 para wheezy y no configurando prioridad 1000 para los paquetes de courier deseados podríamos hacer una instalación "forzada" de los paquetes de wheezy usando el siguiente comando: apt-get install courier-ssl/wheezy courier-imap/wheezy courier-imap-ssl/wheezy courier-base/wheezy.

Al final todo ha quedado de la siguiente manera:

ii  courier-base             0.68.2-1                        Courier mail server - base system
ii  courier-imap             4.10.0-20120615-1               Courier mail server - IMAP server
ii  courier-imap-ssl         4.10.0-20120615-1               Courier mail server - IMAP over SSL
ii  courier-ssl              0.68.2-1                        Courier mail server - SSL/TLS Support

jueves, 19 de marzo de 2015

Courier maildrop: Cómo enviar los mensajes marcados como SPAM a la carpeta de correo no deseado

Gracias a ISPConfig estoy empezando a jugar con Dovecot y su filtrado gestionado por ManageSieve. Este servicio permite a los usuarios gestionar sus scripts de filtrado sin necesidad de tener acceso físico (FTP, SCP, WebDAV o similar) al sistema de archivos mediante un estándar que puede ser integrado en diferentes clientes (como roundcube, horde, thunderbird, etc).

Como en la actualidad mantengo algunos servidores con postfix+courier hasta ahora usaba un script de maildrop genérico que detecta el flag de SPAM que deja Spamassassin en los mensajes para enviarlos a una carpeta IMAP en particular (SPAM hasta hace poco). Como la mayoría de clientes de correo usan la carpeta "Junk" como destino de los correos marcados como SPAM, decidí cambiar dicho directorio de destino y, de paso, publicar el script para que otras personas puedan usarlo.

Este es el archivo /etc/maildroprc genérico que uso en esos servidores:

# Global maildrop filter file

# Uncomment this line to make maildrop default to ~/Maildir for
# delivery- this is where courier-imap (amongst others) will look.
#DEFAULT="$HOME/Maildir"

# Compruebo si es un correo con puntuación mayor de 25. En ese caso lo mando a
# una carpeta que periódicamente usa "spamassassin -r" para reportar a Pyzor,
# Razor, DCC e incluso Spamcop (luego los valido manualmente en su web) y no
# permito que llegue al destinatario.
if (/^X-Spam-Level: *\*{25,}/)
{
    DESTINO="/var/mail/cron.SPAM"
    # Compruebo si la carpeta existe y en caso contrario la creo
    exception {
        `test -d "$DESTINO"`
            if( $RETURNCODE == 1 )
            {
                `/usr/bin/maildirmake "$DESTINO"`
            }
    }
    to "$DESTINO"
}

# Si está marcado como SPAM (y tiene una puntuación inferior a 25) lo mando a
# la carpeta "Junk" del usuario.
if (/^X-Spam-Flag: *YES/)
{
    # Compruebo si la carpeta existe y en caso contrario la creo
    exception {
        `test -d "$DEFAULT/.Junk"`
            if( $RETURNCODE == 1 )
            {
                `/usr/bin/maildirmake "$DEFAULT/.Junk"`
            }
    }
    to "$DEFAULT/.Junk"
}

# Si no es ninguno de los dos casos anteriores lo tramito normalmente.
to "$DEFAULT"

Por hacer: tener un patrón genérico de correo electrónico y línea de comandos para probar el correcto funcionamiento de maildrop.

miércoles, 27 de febrero de 2013

Instalando un sintetizador de voz en castellano en Debian/Ubuntu (TTS - Text To Speech)

Me ha surgido la necesidad de un sintetizador de voz en castellano para generar voces con el contenido de un mensaje de alerta en nuestros servidores Ubuntu Server (en proceso de migración a Debian).

He decidido usar "festival", haciendo uso del paquete de voz en castellano "festvox-ellpc11k". El proceso de instalación es muy sencillo:

apt-get install festival festvox-ellpc11k

He visitado bastantes páginas de internet que recomiendan no usar "text2wave" para generar la voz por no poder cambiar de lenguaje (!) por lo que se complican la vida agregando un archivo ".festivalrc" que contiene el siguiente texto:

(Parameter.set 'Audio_Method 'Audio_Command)
(Parameter.set 'Audio_Command "cat $FILE > foo.raw")

Para generar un archivo de sonido necesitamos dos pasos:

  • Generar el archivo RAW:
    iconv -f utf-8 -t iso-8859-1 < archivo.txt | festival --language spanish --tts
  • Convertir el archivo RAW en WAV:
    sox -c 1 -r 11025 -s -2 foo.raw salida.wav

No digo que esté mal hacerlo así, pero tiene el inconveniente de no funcionar de manera correcta si realizamos dos conversiones paralelas. Ambas trabajarán en el archivo foo.raw, perdiéndose una de las dos conversiones de texto a voz.

Finalmente, investigando un poco, he preferido "forzar" la configuración de idioma pasándosela como parámentro a "text2wave" como comando LISP que se evaluará previo a la conversión.

Creando un script (llamado "texto_a_voz.sh", por ejemplo) con el siguiente contenido nos facilitará la tarea enormemente:

#!/bin/bash
/usr/bin/iconv -f utf-8 -t iso-8859-1 | /usr/bin/text2wave -eval "(language_castillian_spanish)"

Bastará con ejecutar la siguiente línea para, a partir de un archivo de texto de entrada en formato UTF-8, generar un archivo WAV de salida:

texto_a_voz.sh < archivo.txt > salida.wav

Si deseáramos pasar el archivo a MP3, por ejemplo, bastaría con agregar a la cadena "lame", con la opción "-S" si no queremos mensajes (modo silencioso), a la línea del comando:

texto_a_voz.sh < archivo.txt | lame -S - salida.mp3

Espero que os sea de utilidad.

jueves, 14 de febrero de 2013

Particionando tablas en Zabbix 2.0

Tras usar durante varios años en producción Zabbix 1.8 estamos migrando la plataforma a Zabbix 2.0 para realizar una limpieza de equipos, refinado de plantillas, etc.

Una de las cosas que deben hacerse para mejorar el rendimiento de la base de datos es deshabilitar el housekeeping integrado en Zabbix y realizar el borrado de datos usando particiones en las tablas históricas.

Si usamos una guía genérica de particionamiento de tablas basado en Zabbix 1.8 no nos funcionará debido al uso de claves foráneas en algunas tablas. Por desgracia, a día de hoy, MySQL (ni Percona) soporta claves foráneas al mismo tiempo que particionamiento de tablas, por lo que la solución que propondremos aquí eliminará de forma permanente dichas relaciones.

Primer paso: Eliminar claves foráneas

# Tabla "acknowledges"
ALTER TABLE `acknowledges`
 DROP FOREIGN KEY `c_acknowledges_2`,
 DROP FOREIGN KEY `c_acknowledges_1`;

# Tabla "alerts"
ALTER TABLE `alerts`
 DROP FOREIGN KEY `c_alerts_4`,
 DROP FOREIGN KEY `c_alerts_1`,
 DROP FOREIGN KEY `c_alerts_2`,
 DROP FOREIGN KEY `c_alerts_3`;

# Tabla "auditlog"
ALTER TABLE `auditlog`
 DROP FOREIGN KEY `c_auditlog_1`;

# Tabla "auditlog_details"
ALTER TABLE `auditlog_details`
 DROP FOREIGN KEY `c_auditlog_details_1`;

# Tabla "service_alarms"
ALTER TABLE `service_alarms`
 DROP FOREIGN KEY `c_service_alarms_1`;

Segundo paso: Cambio de claves primarias

ALTER TABLE `acknowledges` DROP PRIMARY KEY, ADD KEY `acknowledgedid` (`acknowledgeid`);
ALTER TABLE `alerts` DROP PRIMARY KEY, ADD KEY `alertid` (`alertid`);
ALTER TABLE `auditlog` DROP PRIMARY KEY, ADD KEY `auditid` (`auditid`);
ALTER TABLE `events` DROP PRIMARY KEY, ADD KEY `eventid` (`eventid`);
ALTER TABLE `service_alarms` DROP PRIMARY KEY, ADD KEY `servicealarmid` (`servicealarmid`);
ALTER TABLE `history_log` DROP PRIMARY KEY, ADD PRIMARY KEY (`itemid`,`id`,`clock`);
ALTER TABLE `history_log` DROP KEY `history_log_2`;
ALTER TABLE `history_text` DROP PRIMARY KEY, ADD PRIMARY KEY (`itemid`,`id`,`clock`);
ALTER TABLE `history_text` DROP KEY `history_text_2`;

Tercer paso: Obtener fechas iniciales para particiones

SET @SEMANA= DATE_ADD(NOW(), INTERVAL 1 WEEK);
SET @F_SEMANA = DATE_FORMAT(@SEMANA, '%Y-%m-%d');
SET @P_SEMANA= CONCAT('p', DATE_FORMAT(@SEMANA, '%x%v'));
SELECT @F_SEMANA, @P_SEMANA;
SET @DIA= DATE_ADD(NOW(), INTERVAL 1 DAY);
SET @F_DIA = DATE_FORMAT(@DIA, '%Y-%m-%d');
SET @P_DIA= CONCAT('p', DATE_FORMAT(@DIA, '%Y%m%d'));
SELECT @F_DIA, @P_DIA;

Cuarto paso: Crear particiones semanales

# Formato de fecha PHP "oW"/'%x%v'
ALTER TABLE `acknowledges` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `alerts` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `events` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `auditlog` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `events` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `service_alarms` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `trends` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `trends_uint` PARTITION BY RANGE( clock ) ( PARTITION pYYYYss VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );

ALTER TABLE `acknowledges` PARTITION BY RANGE( clock ) ( PARTITION p201308 VALUES LESS THAN (UNIX_TIMESTAMP("2013-02-21 00:00:00")) );

Quinto paso: Crear particiones diarias

ALTER TABLE `history` PARTITION BY RANGE( clock ) ( PARTITION pYYYYmmdd VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `history_log` PARTITION BY RANGE( clock ) ( PARTITION pYYYYmmdd VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `history_str` PARTITION BY RANGE( clock ) ( PARTITION pYYYYmmdd VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `history_text` PARTITION BY RANGE( clock ) ( PARTITION pYYYYmmdd VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );
ALTER TABLE `history_uint` PARTITION BY RANGE( clock ) ( PARTITION pYYYYmmdd VALUES LESS THAN (UNIX_TIMESTAMP("XXXXXXXXXX 00:00:00")) );

ALTER TABLE `history` PARTITION BY RANGE( clock ) ( PARTITION p20130215 VALUES LESS THAN (UNIX_TIMESTAMP("2013-02-15 00:00:00")) );

lunes, 18 de junio de 2012

Notas sobre DRBD y el cluster proyectado

La idea original de usar DRBD y OCFS2 funciona muy bien bajo una carga que no sea excesiva y sin muchos "syncs" (las bases de datos sqlite suelen hacerlos de manera frecuente) en el sistema de archivos.

El cluster de pruebas estuvo activo durante más de 6 meses funcionando muy bien, pero observé fallos de rendimiento que he terminado por subsanar pasando el sistema de archivos a una tercera máquina con OpenNAS. No me ha sido posible convencer a los usuarios para que dejen de usar bases de datos basados en archivos (sqlite), por lo que el cambio fue casi obligado.

Voy a retomar el artículo para terminar con el proceso de instalación original usando DRBD y posteriormente haré un artículo para mejorar el rendimiento usando un almacenamiento iSCSI externo.

Por último, el cluster lo monté originalmente en Ubuntu Server 10.04. Estoy planteándome migrar a Debian 6 o a Ubuntu Server 12.04. Cuando confirme mi decisión postearé igualmente cualquier modificación que se deba aplicar a lo ya escrito.

Siento haberme demorado en la documentación de la instalación :) entre mi reciente paternidad, mi creciente carga de trabajo y los exámenes de la universidad no he tenido apenas tiempo. Ahora en verano tendré más tiempo libre para retomar estos temas.

domingo, 20 de noviembre de 2011

Cluster de alta disponibilidad y balanceo de carga Apache + PHP + MySQL con sólo dos máquinas (Primera parte)

Introducción

Tras poner en producción un cluster de estas características para mi empresa, decido publicar una serie de artículos que ilustrarán cómo montar una arquitectura similar y para que todo el mundo que lo desee pueda disfrutar de un aumento de rendimiento en sus aplicaciones LAMP.

Lo ideal sería separar las máquinas que ejecutan PHP y Apache de las máquinas que almacenan las páginas web y el servidor de bases de datos. Posiblemente haciendo esto se solucionen la mayoría de problemas de rendimiento que tienen la gran mayoría de las instalaciones LAMP.

¿Cómo mejoro el rendimiento en mi instalación LAMP actual?

Antes de decidir montar un cluster de alta disponibilidad y balanceo de carga tenemos que tener muy claro que no podemos sacarle mayor rendimiento a la arquitectura actual. Para ello debemos hacernos las siguientes preguntas:

¿Dónde tenemos el cuello de botella en nuestra instalación hardware?

Dependiendo de dónde esté el cuello de botella (aquél elemento que ralentiza en mayor grado la ejecución de nuestros scripts) debemos aumentar el hardware de nuestros servidores para mejorar lo máximo posible el problema.

Podemos destacar los siguientes cuellos de botella hardware:

  • CPU: Que se esté usando la CPU al 100% (nice + idle + user + system) en nuestra máquina es una buena señal ya que significa que tenemos bien dimensionada nuestra máquina en memoria y disco duro y que si no es capaz de procesar un mayor número de peticiones es por falta de potencia de procesamiento. También puede ser un indicador de que nuestros scripts PHP no están optimizados todo lo que debieran. En algunos casos es posible aumentar la CPU sin tener que cambiar el resto del hardware (placa madre, memoria, etc), pero no siempre es así. El balanceo de carga que propondremos en esta arquitectura solucionará este problema drásticamente permitiendo, a su vez, escalar tanto como deseemos la potencia de cálculo del cluster cada vez que sea necesario.
  • Memoria: Cuando los procesos agotan la memoria RAM y empiezan a hacer uso del área de intercambio el rendimiento general del servidor decae radicalmente debido a que cualquier tipo de entrada salida (búsqueda de una imagen desde el servidor web, búsqueda de datos desde el servidor MySQL, acceso a datos en disco desde un script PHP, etc) se ve ralentizado por las constantes entradas y salidas de datos de memoria a intercambio y viceversa. Generalmente podemos saber cuándo está un servidor perdiendo rendimiento debido a este motivo observando que la memoria RAM libre y la caché de disco es muy baja y, sin embargo, tanto el uso de memoria de intercambio ha aumentado como el número de procesos esperando la finalización de un proceso de entrada/salida (iowait). Aumentar la memoria RAM de un servidor suele ser mucho más sencillo y barato que aumentar la RAM y deberemos hacerlo tanto como podamos antes de montar el clustar. Usar un cluster de balanceo de carga permitirá repartir el consumo de RAM de algunos procesos PHP mientras que otros usos de memoria RAM no se podrán reducir o evitar (caché de índices, consultas, etc del servidor MySQL, por ejemplo).
  • Disco duro: Hay que tener en cuenta que una aplicación que haga uso intensivo del almacenamiento (cálculo de la firma MD5, creación o lectura de un archivo muy grande, etc) podría provocar la ralentización de todo el sistema de manera similar a cuando entramos en paginación. La diferencia radica en que un proceso que haga uso intensivo de disco tendrá un tiempo de ejecución acotado (si nuestra aplicación está bien diseñada) mientras que un estado de paginación por falta de memoria RAM se producirá durante largos periodos de tiempo. Hay que procurar que se use lo mínimo posible de manera intensiva el almacenamiento compartido (afectará al rendimiento del cluster). Como cada servidor MySQL funciona con su réplica de datos local, un uso intensivo puntual de una de las máquinas no afectará a la otra. Para mejorar el rendimiento de E/S de datos se recomienda el uso de discos de estado sólido (SSD) o el uso de RAID 5 para mejorar sensiblemente la tasa de transferencia a la vez que se agrega redundancia (es importante mantener a salvo los datos en caso de fallo de uno de los discos).

¿Qué arquitectura de cluster vamos a montar?

El objetivo de este pequeño proyecto es ofrecer los servicios de procesamiento, almacenamiento, bases de datos, etc de manera autónoma en sólo dos máquinas proporcionando alta disponibilidad sin hacer uso de servicios externos:
  • No disponemos de almacenamiento SAN (Network Attached Storage).
  • No podemos tener máquinas separadas para el servidor MySQL, servidor HTTP, servidor de archivos, etc.
Esto nos deja la siguiente propuesta de arquitectura:
  • Gestor de Recursos del Cluster (CRM): Usaremos heartbeat y pacemaker. Nos proporcionará monitorización de los recursos para mover la IP virtual de servicio a aquella máquina que esté funcional.
  • Servidor web: Usaremos un servidor apache en cada máquina sirviendo páginas web con soporte para ejecutar scripts PHP.
  • Acelerador web: Usaremos squid como acelerador web para descargar la carga del servidor web real y, a su vez, realizar la función de balanceador de carga solicitando una petición a cada servidor web real mediante un esquema de peticiones round-robin.
  • Almacenamiento compartido: Usaremos DRBD para replicar la información entre dos volúmenes lógicos, proporcionando redundancia en caso de caída de un nodo o bien fallo catastrófico de los discos duros de una de las máquinas, y OCFS2 para proporcionar un acceso concurrente a los datos de dicho dispositivo replicado.
  • Servidor de bases de datos: Usaremos MySQL por ser su uso el más extendido entre aplicaciones PHP. A su vez usaremos un esquema de replicación circular para proporcionar una mayor disponibilidad de los datos y minimizar el tiempo de recuperación de servicio en caso de caída de un nodo (aunque ello no implica consistencia como veremos más adelante en los "contras").
En la próxima entrega comenzaremos montando el sistema de archivos compartido donde alojaremos las páginas web de los usuarios.