Clusters de Asterisk con el foneBRIDGE2

[spanish]

En el trabajo tenemos un cluster Asterisk formado por dos servidores Proliant y un foneBRIDGE2 de Redfone que controla las líneas RDSI. El servicio heartbeat está instalado en ambos servidores, y se encarga de monitorizarlos y alternar el servicio en caso de que el nodo principal caiga, migrando la IP principal y levantando todos los procesos necesarios. Voy a explicar brevemente nuestra configuración como referencia para otros usuarios que se estén empezando a pelear ahora con el foneBRIDGE.

Visión general

Como he comentado tenemos dos servidores Asterisk, llamados asterisk00 y asterisk01.example.com, siendo el primero de ellos el master del servicio. Cada uno tiene su dirección IP (10.10.10.1 y .2) y aparte hay otra IP “virtual” (.3) que va a “saltar” de un nodo al otro si el principal se queda colgado.

Nuestro foneBRIDGE2 es un modelo “quad” para 4 líneas RDSI, pero sólo usamos dos: una al proveedor de telefonía y la otra a una centralita antigua. Aparte de los conectores para las RDSI, el foneBRIDGE tiene dos puertos ethernet para conectarlo a los servidores, pero sólo admite comandos de configuración para inicializarlo, cambiar de servidor, etc. en uno de ellos (el primero). Normalmente lo que se haría es poner un switch en éste puerto ethernet y conectar ahí todos los servidores del cluster, para que todos puedan configurar el FB en caso de necesidad, pero mi jefe se empeñó en ver éste switch como un punto de fallo y se negó a usarlo, una opinión que no comparto ya que tiene otras desventajas que vamos a ver. Así que nuestra configuración es un tanto peculiar, ya que asterisk01 (el backup) está conectado al puerto principal del FB mientras que asterisk00 está conectado al secundario. La lógica detrás de ésta decisión es: asterisk00 va a estar en marcha el 99% del tiempo, y cuando se cuelgue, será asterisk01 quien se deberá encargar de reconfigurar el FB, así que es asterisk01 quien realmente va a controlar las caídas y reconfiguraciones del FB y necesita acceso al puerto de configuración. Pero por supuesto, ahora quien es un punto de fallo en nuestra configuración es asterisk01: si el servidor de backup se nos cae por cualquier motivo, nos arriesgamos a perder el control del FB dejando el cluster inutilizable.

Usamos el interfaz web FreePBX, que a su vez utiliza una base de datos mySQL para almacenar toda la configuración. Si no usas FreePBX, puedes ignorar cualquier mención tanto al mySQL como al Apache.

Sincronización de mySQL

El clustering nativo ndb de mySQL nos va a resultar muy útil aquí. Se configura, se mantiene el servicio en marcha en todo momento en ambos nodos, y el sistema de BD se encarga de la sincronización de forma automática a través de todos los servidores.

La configuración de un cluster mySQL se sale del ámbito de éste documento. Te recomiendo consultar la documentación oficial aquí o bien buscar algún howto en el Google. :)

Sincronización de ficheros

Tenemos que mantener sincronizados en todos los servidores del cluster todos los ficheros de configuración del Asterisk, librerías, módulos, los buzones del voicemail… Hay varias alternativas.

  • Una SAN. Cara pero muy cómoda. Nosotros no tenemos una así que queda descartada. :)
  • DRBD. Si no lo conoces, piensa en él como un RAID1 a nivel de partición pero por red. Funciona de maravilla, lo usamos en clusters de otros servicios y no nos ha dado ningún problema, pero no aquí. La única pega del DRBD es que la partición a sincronizar sólo puede estar montada en uno de los dos servidores, el activo en un momento dado, y nosotros queríamos la comodidad de tener todos los ficheros accesibles en ambos servidores para poder usar el backup como “banco de pruebas” de nuevas configuraciones o actualizaciones del software.
  • csync2. Es un rsync con esteroides. Similar al unison, pero capaz de sincronizar ficheros entre más de dos servidores. Es la opción que usamos en nuestro cluster Asterisk.

Nuestro fichero csync2.conf tiene éste aspecto:

[code lang=”bash”]

group asterisk
{
host asterisk00.example.com asterisk01.example.com;

key /etc/csync2.key_asterisk;

backup-directory /var/backups/csync2;
backup-generations 10;

auto none;

exclude *~ .* ok lock control;
include /etc/csync2.cfg;

include /etc/hosts;
include /etc/ha.d/ha.cf;
include /etc/ha.d/haresources;

include /etc/asterisk;
include /etc/redfone*;

include /var/www;
include /var/lib/asterisk;
include /var/spool/asterisk;
include /usr/lib/asterisk;
include /etc/amportal.conf;
include /var/log/asterisk;
}

[/code]

Lanzamos una sincronización cada cinco minutos. No hay necesidad de hacerlo tan a menudo, y de hecho podríamos lanzarla cada media hora o una hora, ya que en un sistema estable no va a haber tantos cambios (añadir alguna extensión cada X semanas) y rara vez usamos los buzones de voz. La sincronización se lanza desde un cron en /etc/cron.d/FB-csync2:

[code lang=”bash”]

*/5 * * * * root [ -f /tmp/.FB-master ] && /usr/sbin/csync2 -xv

[/code]

Éste fichero /tmp/.FB-master es un fichero “centinela” que marca el servidor principal en cada momento, de forma que ésta sincronización sólo se lance desde ése nodo. En la sección del heartbeat veremos dónde y cómo se crea.

fonulator

fonulator es la utilidad de Redfone para configurar el foneBRIDGE. Como ya he comentado antes, en nuestra configuración sólo asterisk01 (el servidor de backup) tiene acceso al puerto de configuración del FB, y cada uno de los servidores está conectado a un puerto ethernet distinto. Así que cuando un servidor cae, tenemos que cambiar el servidor destino de las tramas TDMoE y TAMBIÉN el interfaz ethernet a través del cual las manda el FB.

Para ello tenemos preparados dos ficheros de configuración independientes, redfone_asterisk00.conf y redfone_asterisk01.conf. Son iguales salvo por los comandos “serverX” y “fbX” de cada “span”:

[code lang=”bash”]

[globals]
fb1=00:50:C2:65:D0:68
fb2=00:50:C2:65:D0:69

# asterisk00.example.com
server1=00:80:5A:61:E7:FF
# asterisk01.example.com
server2=00:04:76:11:A3:EC

card=eth1,fb1

# Telco
[span1]
span=1,0,0,ccs,hdb3,crc4
server1
fb2
pri

# Legacy PBX
[span2]
span=2,0,0,ccs,hdb3,crc4
server1
fb2
pri

[/code]

Eso era redfone_asterisk00.conf. Configura el FB para mandar el tráfico RDSI a asterisk00 (server1) a través del segundo interfaz ethernet (fb2). El fichero redfone_asterisk01.conf usa server2 y fb1.

heartbeat

Sólo nos queda la pieza final que une al resto: heartbeat. Nuestro fichero haresources es así:

[code lang=”bash”]

asterisk00.example.com MailTo::asterisk@example.com::Asterisk 10.10.10.3 FB_fonulator FB_master FB_asterisk apache2

[/code]

Que significa que:

  • asterisk00.example.com es el master
  • cada vez que se intercambie el servicio, se mandará un mail a asterisk@example.com
  • la IP virtual del servicio es la 10.10.10.3
  • arrancar (parar) los servicios FB_fonulator, FB_master, FB_asterisk y apache2 (recuerda eliminar el enlace al apache2 en /etc/rc2.d, no queremos que se lance en el inicio del sistema si no que heartbeat se encargará de manejarlo)

Veamos ahora los scripts. FB_fonulator ejecuta el programa fonulator para configurar el foneBRIDGE y dirigir el tráfico TDMoE al servidor que toque. Un detalle importante es que, aunque el script se lance en ambos servidores, sólo tendrá efecto cuando se lanza desde asterisk01 que es quien está conectado al puerto de configuración del FB:

[code lang=”bash”]

#!/bin/sh

# Chech who I am and who the other host is
THISHOST=”`hostname|cut -d. -f1`”
if [ “$THISHOST” == “asterisk00″ ]
then
OTHERHOST=”asterisk01″
else
OTHERHOST=”asterisk00″
fi

# Bail out if there is no config file
F=”/etc/redfone_$THISHOST.conf”
[ ! -f “$F” ] && exit 0
# Guess the appropiate interface card
export ETH=`grep -E “^card=” “$F” | cut -d= -f2 | cut -d, -f1`

case “$1” in
start)
echo “Fonulating…”
/usr/local/bin/fonulator -s -t 1 “/etc/redfone_$THISHOST.conf”
;;
stop)
/usr/local/bin/fonulator -s -t 1 “/etc/redfone_$OTHERHOST.conf”
;;
restart|status)
echo “Fonulator $1”
exit 0
;;
esac
exit 0

[/code]

FB_master crea el fichero “centinela” /tmp/.FB-master que ya habíamos mencionado, y fuerza una sincronización tanto en el arranque (para asegurarnos que ambos servidores están sincronizados en el inicio) como en la parada (para devolver al master cualquier cambio que haya podido haber mientras el servicio estaba en el backup):

[code lang=”bash”]

#!/bin/sh

F=/tmp/.FB-master

case “$1” in
start)
touch “$F”
# Activate log rotation
ln -sf /etc/asterisk/asterisk.logrotate /etc/logrotate.d/asterisk
# Force sync of these dirs
csync2 -fr /var/
csync2 -fr /etc/asterisk/
csync2 -xv
;;
stop)
if [ -f “$F” ]
then
# De-activate log rotation
rm -f /etc/logrotate.d/asterisk
# Force a last minute sync to the new master
csync2 -fr /var/
csync2 -fr /etc/asterisk/
csync2 -xv
rm -f “$F”
fi
;;
esac
exit 0

[/code]

Por último, FB_asterisk lanza el servicio Asterisk en sí. Usamos mis scripts para manejar Asterisk con daemontools, así que básicamente lo único que tiene que hacer éste script es un “svc -u/-d /service/asterisk”:

[code lang=”bash”]

#!/bin/sh

case “$1” in
start)
echo “Starting Asterisk…”
# Check if Asterisk is already running
if /usr/sbin/asterisk -r -x “quit”
then
echo “Already running”
exit 0
fi
# Just in case…
rm -f /service/*
# Link services and start them up
ln -sf /etc/asterisk/services/asterisk/ /service/asterisk
ln -sf /etc/asterisk/services/fopserver/ /service/fopserver
svc -u /service/*
;;
stop)
echo “Stopping Asterisk …”
svc -d /service/*
rm -f /service/*
;;
restart)
echo “Restarting Astarisk …”
svc -t /service/*
;;
reload)
echo “Reloading Asterisk …”
/usr/sbin/asterisk -r -x “reload”
;;
status)
echo “Checking Asterisk’s status …”
/usr/sbin/asterisk -r -x “quit” && exit 0 || exit 1
;;
esac
exit 0

[/code]

Descargas

Todos los scripts y ficheros de configuración comentados están disponibles aquí. Piensa en ellos como una base sobre la que empezar a hacer pruebas y construir tu propia configuración. Y agradeceré si alguien me envía mejoras, errores que hayáis podido encontrar, etc. :)

[/spanish]

[english]

At work we have an Asterisk cluster comprised of two Proliant servers and a Redfone‘s foneBRIDGE2 that handles the ISDN lines. The heartbeat daemon is installed on both servers, monitors them and, in the event of a system failure on the master, switches the service to the backup server, migrating the main IP and activating all the needed daemons. I’ll briefly explain the whole setup here as a reference.

Overview

As I’ve said we have two Asterisk servers, named asterisk00 and asterisk01.example.com, the former being the master. Each one of them has its IP address (say, 10.10.10.1 and .2) and there’s an additional “virtual” address (.3) that will “jump” from one server to the other if the primary crashes.

Our foneBRIDGE2 is a quad model, but we only use two ISDN lines: one to our telco, and the other to a legacy PBX. Besides the ISDN interfaces, the foneBRIDGE has two ethernet sockets to connect it to the servers, but only one of them (the first one) accepts configuration commands to set up the FB, switch servers, etc. You’d usually use a switch on that interface so that every server has access to it and can configure the FB, but my boss saw this switch as a single point of failure and refused to use one, a opinion I don’t share as it also has its drawbacks as we’ll see. So our setup is a little bit funny in that asterisk01 is connected to the primary FB interface and asterisk00 to the secondary one. The logic here is: asterisk00 is going to be running 99% of the time, and if it crashes, asterisk01 would have to re-configure the FB, so asterisk01 needs to have access to the config port. Of course, now asterisk01 is a SPOF: if our backup server goes down for any reason, we risk losing control of the FB rendering our cluster unusable!

We use the FreePBX web GUI, which in turn uses a mySQL DB to store all the settings. If you don’t use it, you can skip all instructions referring to mySQL and Apache.

mySQL synchronization

mySQL’s native ndb clustering is quite useful here. Set it up, have the service up at all times on both nodes, and the DB system automatically handles the synchronization across the cluster.

Setting up a mySQL cluster is out of the scope of this document, check the official docs here or look for a howto on Google. :)

Filesystem synchronization

All of Asterisk’s config files, libraries, modules, the users’ voicemail dirs… need to be synchronized over the cluster’s nodes. There are several alternatives here:

  • A SAN. Expensive but convenient. We don’t have one so it’s out of the question. :)
  • DRBD. If you don’t know it, think of it as a partition-level RAID1 system over the network. Works great, we use it on several other clusters, but not here. DRBD’s only drawback is that the synchronized partition can’t be mounted on both servers at once, so you can access the files only on the active node. We wanted to have everything accessible on both servers so that we could use the backup one as a testing ground for new configurations, software upgrades, etc., do DRBD wasn’t and option.
  • csync2. It’s like rsync on steroids. Similar to unison, but can synchronize files over more than two nodes. We’re using it for our Asterisk cluster.

Our csync2.conf file looks like this:

[code lang=”bash”]

group asterisk
{
host asterisk00.example.com asterisk01.example.com;

key /etc/csync2.key_asterisk;

backup-directory /var/backups/csync2;
backup-generations 10;

auto none;

exclude *~ .* ok lock control;
include /etc/csync2.cfg;

include /etc/hosts;
include /etc/ha.d/ha.cf;
include /etc/ha.d/haresources;

include /etc/asterisk;
include /etc/redfone*;

include /var/www;
include /var/lib/asterisk;
include /var/spool/asterisk;
include /usr/lib/asterisk;
include /etc/amportal.conf;
include /var/log/asterisk;
}

[/code]

We run the synchronization every five minutes. There’s no need to sync more frequently, as there won’t be that many changes in the configuration (it’s a stable system, maybe a new phone added every X weeks) and we seldom use the voicemail. The synchronization is launched from /etc/cron.d/FB-csync2:

[code lang=”bash”]

*/5 * * * * root [ -f /tmp/.FB-master ] && /usr/sbin/csync2 -xv

[/code]

This /tmp/.FB-master file is just a “flag” that marks the master server, so that the synchronization is only run there. On the section about heartbeat we’ll see how and when this file is created.

fonulator

fonulator is Redfone’s utility to configure the foneBRIDGE. As I’ve explained before, only asterisk01 (the backup system) can configure the FB in our setup, and each server is connected to a different ethernet port on the FB. So in the event of a crash, we need to change the destination server AND the interface used to send it the TDMoE frames.

To this end, we have two different redfone.conf files (redfone_asterisk00.conf and redfone_asterisk01.conf). They look the same except for the “serverX” and “fbX” directives on the spans:

[code lang=”bash”]

[globals]
fb1=00:50:C2:65:D0:68
fb2=00:50:C2:65:D0:69

# asterisk00.example.com
server1=00:80:5A:61:E7:FF
# asterisk01.example.com
server2=00:04:76:11:A3:EC

card=eth1,fb1

# Telco
[span1]
span=1,0,0,ccs,hdb3,crc4
server1
fb2
pri

# Legacy PBX
[span2]
span=2,0,0,ccs,hdb3,crc4
server1
fb2
pri

[/code]

That was redfone_asterisk00.conf. It instructs the FB to send the ISDN traffic to asterisk00 (server1 here) over the second ethernet interface (fb2). The redfone_asterisk01.conf file uses server2 and fb1.

heartbeat

And now, the final piece that ties the rest together: heartbeat. Our haresources file looks like this:

[code lang=”bash”]

asterisk00.example.com MailTo::asterisk@example.com::Asterisk 10.10.10.3 FB_fonulator FB_master FB_asterisk apache2

[/code]

Meaning that:

  • asterisk00.example.com is the master server
  • in the event of a service takeover, send a mail to asterisk@example.com
  • the service’s virtual IP is 10.10.10.3
  • start (stop) the FB_fonulator, FB_master, FB_asterisk and apache2 services (remember to unlink the apache2 link from /etc/rc2.d, we don’t want it to be started at system bootup as heartbeat will handle it)

Now, the scripts. FB_fonulator runs fonulator in order to configure the FB and send the TDMoE traffic to the appropriate server. One important thing here is that, although this script will be run on both servers, it will only have an effect when run from asterisk01 as this is the server on the FB’s config interface:

[code lang=”bash”]

#!/bin/sh

# Chech who I am and who the other host is
THISHOST=”`hostname|cut -d. -f1`”
if [ “$THISHOST” == “asterisk00″ ]
then
OTHERHOST=”asterisk01″
else
OTHERHOST=”asterisk00″
fi

# Bail out if there is no config file
F=”/etc/redfone_$THISHOST.conf”
[ ! -f “$F” ] && exit 0
# Guess the appropiate interface card
export ETH=`grep -E “^card=” “$F” | cut -d= -f2 | cut -d, -f1`

case “$1” in
start)
echo “Fonulating…”
/usr/local/bin/fonulator -s -t 1 “/etc/redfone_$THISHOST.conf”
;;
stop)
/usr/local/bin/fonulator -s -t 1 “/etc/redfone_$OTHERHOST.conf”
;;
restart|status)
echo “Fonulator $1”
exit 0
;;
esac
exit 0

[/code]

FB_master creates the /tmp/.FB-master “flag” file we talked about before, and forces a sync both on the start (to make sure both servers have the same data) and on the stop (to sync back to the primary server any changes after a takeover-and-back):

[code lang=”bash”]

#!/bin/sh

F=/tmp/.FB-master

case “$1” in
start)
touch “$F”
# Activate log rotation
ln -sf /etc/asterisk/asterisk.logrotate /etc/logrotate.d/asterisk
# Force sync of these dirs
csync2 -fr /var/
csync2 -fr /etc/asterisk/
csync2 -xv
;;
stop)
if [ -f “$F” ]
then
# De-activate log rotation
rm -f /etc/logrotate.d/asterisk
# Force a last minute sync to the new master
csync2 -fr /var/
csync2 -fr /etc/asterisk/
csync2 -xv
rm -f “$F”
fi
;;
esac
exit 0

[/code]

Finally, FB_asterisk starts the Asterisk service. We run Asterisk via daemontools using my scripts available here, so basically what this FB_asterisk script has to do is “svc -u/-d /service/asterisk”:

[code lang=”bash”]

#!/bin/sh

case “$1” in
start)
echo “Starting Asterisk…”
# Check if Asterisk is already running
if /usr/sbin/asterisk -r -x “quit”
then
echo “Already running”
exit 0
fi
# Just in case…
rm -f /service/*
# Link services and start them up
ln -sf /etc/asterisk/services/asterisk/ /service/asterisk
ln -sf /etc/asterisk/services/fopserver/ /service/fopserver
svc -u /service/*
;;
stop)
echo “Stopping Asterisk …”
svc -d /service/*
rm -f /service/*
;;
restart)
echo “Restarting Astarisk …”
svc -t /service/*
;;
reload)
echo “Reloading Asterisk …”
/usr/sbin/asterisk -r -x “reload”
;;
status)
echo “Checking Asterisk’s status …”
/usr/sbin/asterisk -r -x “quit” && exit 0 || exit 1
;;
esac
exit 0

[/code]

Download

All the aforementioned scripts and config files are available here. Think of them as a base to make your own Asterisk/foneBRIDGE setup. And feel free to mail me back any improvements, errors you may find, etc.

[/english]

6 comentarios sobre “Clusters de Asterisk con el foneBRIDGE2”

  1. Hola,

    yo estoy intentando hacer una configuración similar pero con un ISDNGuard en lugar de con RedFone. Estoy utilizando csync2 para la sincronización de ficheros. A pesar de seguir la guía de configuración del manual de csync2, y los consejos que das tu en el post, tengo un problema:

    Cuando ejecuto csync2 en uno de los nodos obtengo lo siguiente:

    produccion:/etc# csync2 -Tvv
    My hostname is produccion.
    Database-File: /var/lib/csync2/produccion.db
    Config-File: /etc/csync2.cfg
    Running in-sync check for produccion madridcluster.
    Connecting to host madridcluster (SSL) …
    Can’t connect to remote host.
    ERROR: Connection to remote host failed.
    SQL: SELECT command, logfile FROM action GROUP BY command, logfile
    SQL Query finished.
    Finished with 1 errors.

    A pesar de que el otro host si es alcanzable:

    MadridCluster:/etc# tcpdump -i eth0 port 30865
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
    18:39:30.758298 IP produccion.33378 > madridcluster.csync2: S 3701654960:3701654960(0) win 5840
    18:39:30.773641 IP madridcluster.csync2 > produccion.33378: R 0:0(0) ack 3701654961 win 0

    2 packets captured
    2 packets received by filter
    0 packets dropped by kernel

    ¿Sabrías a que puede ser debido?

    Gracias y un saludo,

    Mariña

  2. Hola Mariña

    Si el puerto está abierto y el servicio en marcha, sólo se me ocurre que sea por el SSL. csync2 usa cifrado Y AUTENTICACIÓN vía SSL, tienes que crear certificados para ambas máquinas y configurarlos. Es bastante coñazo así que en el artículo me lo salté a la torera, porque si no hubiera dedicado más tiempo a ésto que al resto.

    Pégale un vistazo a la documentación del csync al respecto. De todas formas había una directiva para desactivar por completo el SSL, no tengo ahora los docs a mano pero creo que era:

    nossl host1 host2
    nossl host2 host1

    Si no recuerdo mal había que hacerlo así, indicando que no se iba a usar SSL en ambos sentidos, o si no en uno lo desactivabas pero en el otro no, con lo que seguía fallando.

    Prueba así, y si con ésto te va, ya es cuestión de que analices si necesitas realmente ese punto extra de seguridad que daría el cifrado y autenticación con certificados SSL. Cuanta más azucar, más dulce.

    Saludos

  3. Al final el problema era que no había ejecutado el csync2 -i en el servidor esclavo.

    Ahora ya intenta sincronizar pero me da errores:

    Updating /etc/asterisk/.svn/entries on madridcluster …
    While syncing file /etc/asterisk/.svn/entries:
    ERROR from peer madridcluster:
    ERROR: Auto-resolving failed. Giving up.
    File stays in dirty state. Try again later…
    Match (+): /etc/asterisk on /etc/asterisk/.svn/format
    Updating /etc/asterisk/.svn/format on madridcluster …
    File is different on peer (cktxt char #0).
    >>> PEER: OK (data_follows).
    >>> LOCAL: v1:mtime=0:mode=33060:uid=5060:gid=1001:type=reg:size=2
    Format-error while receiving data.
    SQL: COMMIT TRANSACTION

    ¿Sabes a que puede ser debido, o donde puedo encontrar información al respecto? Es que no encuentro nada…

    Saludos,

    Mariña

  4. Si el fichero ha sido modificado en ambos equipos, csync2 por defecto se lava las manos y te deja a tí que decidas cuál de los dos es “el bueno”.

    Desde un servidor puedes forzar que su copia sea la que se sincronice la próxima vez con “csync2 -f FICHERO”. También hay unas directivas para indicar en el fichero de configuración qué servidor tiene prioridad en caso de que haya un conflicto de éste tipo: si el de más a la izquierda en la lista, a la drcha., el que tenga el fichero más reciente, etc.

    La verdad es que del csync2 hay poca documentación. Yo lo único que he encontrado es éste PDF que está en su página web. A partir de ahí, todo es pelearse con él, probar, probar y probar:

    http://oss.linbit.com/csync2/paper.pdf

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.