Submitted by admin on
Actualizado 23 Septiembre 2013
En este articulo veremos como configurar dos servidores OpenSIPs con el modulo Load_Balancer in un cluster Redis (una base de datos no relacional de la familia de NoSQL). Esto a través del modulo chachedb_redis. De esta forma los dos servidores compartirán en tiempo real la carga de los servidores presenten en la configuración del balanceamiento de carga. Este tipo de escenario puede ser una optima solución para proveedores de terminación SIP o un general para cualquier tipo de configuración donde se necesiten servidores media redundantes y un Cluster de servidores OpenSIPs.
En los dos servidores se ha instalado la versión minimal de CentOS 6.3
IP local servidor A: 192.168.168.60
IP local servidor B: 192.168.180.70
En ambos servidores:
yum update -y
reboot
Ingresamos nuevamente en ambos servidores y seguimos con la instalación de los paquetes necesarios para todo el proceso:
yum install mysql mysql-devel mysql-server gcc gcc-c++ bison bison-devel flex make expat expat-devel unixODBC-devel net-snmp-devel
yum install subversion libxml2 libxml2-devel openssl-devel xmlrpc-c-devel lynx pcre pcre-devel ncurses-devel ncurses-libs
Para Redis:
yum install tcl git ruby
El modulo CACHEDB_REDIS de OpenSIPs se basa en el cliente Redis “hiredis”. Se descarga, compila e instala:
cd /usr/src
git clone https://github.com/redis/hiredis.git hiredis
cd hiredis
make
make install
Se descarga la versión 1.8.2 de OpenSIPs:
cd /usr/src
svn co https://opensips.svn.sourceforge.net/svnroot/opensips/branches/1.8 opensips_1_8
cd opensips_1_8
make menuconfig
Se entra en el menú “Configure Compile Options” y luego en “Configure Excluded Modules”. Se seleccionan los módulos “cachedb_redis” , “db_mysql” y “regex”.
Se vuelve atrás con la tecla <—, se selecciona “Save Changes” y se presiona dos veces la tecla Envío. Se vuelve atrás con la tecla <— y se selecciona “"Exit & Save All Changes”.
Se presiona la tecla Envío hasta salir del menú. Se compila e instala:
make prefix=/ all
make prefix=/ install
Se instala el script de arranque de OpenSIPs:
cd packaging/fedora
nano opensips.init
Cambiamos esta línea:
oser=/usr/sbin/$prog
para que quede:
oser=/sbin/$prog
Guardamos los cambios y terminamos la configuración:
chmod 755 opensips.init
cp opensips.init /etc/init.d/opensips
chkconfig --add opensips
chkconfig opensips on
Ya podemos pasar a la compilación e instalación de Redis. La versión que soporta el Cluster es la unstable que bajamos de github:
cd /usr/src
git clone https://github.com/antirez/redis.git redisuns
cd redisuns
make
make test
make install
Terminada la instalación pasamos a la configuración del servidor A.
Servidor A
En este servidor crearemos la base de datos para OpenSIPs que luego se compartirá con el segundo servidor OpenSIPs:
chkconfig mysqld on
service mysqld start
mysqladmin -u root password sesamo
Entramos en el cliente:
mysql -u root -psesamo
Creamos la base de datos OpenSIPs:
mysql> create database opensips;
Salimos del cliente y configuramos OpenSIPs para la creación de las tablas:
mv /etc/opensips/opensipsctlrc /etc/opensips/opensipsctlrc.old
nano /etc/opensips/opensipsctlrc
copiamos las líneas que siguen:
SIP_DOMAIN=198.168.168.60
DBENGINE=MYSQL
DBHOST=localhost
DBNAME=opensips
DBRWUSER=opensips
DBRWPW="opensipsrw"
DBROOTUSER="root"
INSTALL_EXTRA_TABLES=ask
INSTALL_PRESENCE_TABLES=ask
OSIPS_FIFO="/tmp/opensips_fifo"
VERIFY_ACL=1
PID_FILE=/var/run/opensips.pid
SIP_DOMAIN es la dirección IP o el nombre de dominio de nuestro servidor Linux.Guardamos los cambios y creamos las tablas:
opensipsdbctl create
Volvemos a entrar en el cliente MySQL y creamos los permisos de acceso para el segundo servidor OpenSIPs:
mysql -u root -psesamo
mysql> grant all privileges on opensips.* to 'opensips'@'192.168.180.70' identified by 'opensipsrw';
Insertamos dos servidores en la tabla load_balancer:
mysql> insert into load_balancer (group_id,dst_uri,resources,probe_mode,description) values ('1','sip:sip.servidor1.org','voip/s=20','2','20 canales SIP'),('1','sip:sip.servidor2.org','voip/s=20','2','20 canales SIP');
mysql> quit
Cuando queremos compartir recursos a traves del Cluster Redis, tenemos que indicar el nombre del recurso con /s al final. En este caso: voip/s
Pasamos a la configuración de OpenSips:
mv /etc/opensips/opensips.cfg /etc/opensips/opensips.cfg.old
nano /etc/opensips/opensips.cfg
Copiamos las líneas que siguen:
####### Global Parameters #########
debug=3
log_stderror=no
log_facility=LOG_LOCAL6
fork=yes
children=4
listen=udp:192.168.168.60:5060
disable_tcp=yes
disable_tls=yes
auto_aliases=no
####### Modules Section ########
#set module path
mpath="/lib/opensips/modules/"
#### SIGNALING module
loadmodule "signaling.so"
#### StateLess module
loadmodule "sl.so"
#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timer", 5)
modparam("tm", "fr_inv_timer", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)
#### Record Route Module
loadmodule "rr.so"
modparam("rr", "append_fromtag", 0)
#### MAX ForWarD module
loadmodule "maxfwd.so"
#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"
#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)
#### URI module
loadmodule "uri.so"
modparam("uri", "use_uri_table", 0)
#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", 10)
modparam("usrloc", "db_mode", 0)
#### REGISTRAR module
loadmodule "registrar.so"
modparam("registrar", "tcp_persistent_flag", 7)
#### ACCounting module
loadmodule "acc.so"
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
modparam("acc", "detect_direction", 0)
modparam("acc", "failed_transaction_flag", 3)
modparam("acc", "log_flag", 1)
modparam("acc", "log_missed_flag", 2)
#### CACHEDB_REDIS module
loadmodule "cachedb_redis.so"
modparam("cachedb_redis","cachedb_url","redis://localhost:6379/")
loadmodule "db_mysql.so"
loadmodule "dialog.so"
loadmodule "load_balancer.so"
modparam("dialog","cachedb_url","redis://localhost:6379/")
modparam("load_balancer","db_url","mysql://opensips:opensipsrw@localhost/opensips")
modparam("load_balancer", "probing_interval", 60)
modparam("load_balancer", "probing_reply_codes", "404")
####### Routing Logic ########
# main request routing logic
route{
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
exit;
}
if (has_totag()) {
# sequential request withing a dialog should
# take the path determined by record-routing
if (loose_route()) {
if (is_method("BYE")) {
setflag(1); # do accounting ...
setflag(3); # ... even if the transaction fails
} else if (is_method("INVITE")) {
# even if in most of the cases is useless, do RR for
# re-INVITEs alos, as some buggy clients do change route set
# during the dialog.
record_route();
}
# route it out to whatever destination was set by loose_route()
# in $du (destination URI).
route(1);
} else {
if ( is_method("ACK") ) {
if ( t_check_trans() ) {
# non loose-route, but stateful ACK; must be an ACK after
# a 487 or e.g. 404 from upstream server
t_relay();
exit;
} else {
# ACK without matching transaction ->
# ignore and discard
exit;
}
}
sl_send_reply("404","Not here");
}
exit;
}
# CANCEL processing
if (is_method("CANCEL"))
{
if (t_check_trans())
t_relay();
exit;
}
t_check_trans();
# preloaded route checking
if (loose_route()) {
xlog("L_ERR",
"Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
if (!is_method("ACK"))
sl_send_reply("403","Preload Route denied");
exit;
}
# record routing
if (!is_method("REGISTER|MESSAGE"))
record_route();
# account only INVITEs
if (is_method("INVITE")) {
setflag(1); # do accounting
}
if ($rU==NULL) {
# request with no Username in RURI
sl_send_reply("484","Address Incomplete");
exit;
}
if (!load_balance("1","voip/s","1")) {
send_reply("500","Failure to route");
exit;
}
route(1);
}
route[1] {
# for INVITEs enable some additional helper routes
if (is_method("INVITE")) {
t_on_branch("2");
t_on_reply("2");
t_on_failure("1");
}
if (!t_relay()) {
send_reply("500","Internal Error");
};
exit;
}
branch_route[2] {
xlog("new branch at $ru\n");
}
onreply_route[2] {
xlog("incoming reply\n");
}
failure_route[1] {
if (t_was_cancelled()) {
exit;
}
}
Las lineas importantes:
- modparam("cachedb_redis","cachedb_url","redis://localhost:6379/") – Para conectarnos al servidor Redis local
- modparam("dialog","cachedb_url","redis://localhost:6379/") – Para compartir los diálogos a través del Cluster Redis
- if (!load_balance("1","voip/s","1")) { – Para llamar los recursos configurados en la tabla load_balancer
Guardamos los cambios y terminamos con Redis:
mkdir /etc/redis
nano /etc/redis/redis.conf
daemonize no
pidfile /var/run/redis.pid
port 6379
timeout 0
loglevel notice
logfile stdout
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
slave-serve-stale-data yes
slave-read-only yes
slave-priority 100
maxclients 10000
maxmemory 3GB
maxmemory-policy noeviction
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
cluster-enabled yes
cluster-config-file /etc/redis/nodes-6379.conf
slowlog-log-slower-than 10000
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
Las líneas importantes:
- cluster-enabled yes – Para Activar el Cluster
- cluster-config-file /etc/redis/nodes-6379.conf – Nombre del archivo que Redis creará in automático donde se guardarán los datos del Cluster y de los nodos
Guardamos los cambios y iniciamos Redis:
redis-server /etc/redis/redis.conf
Redis no encuentra todavía ningún Cluster porque no se ha configurado el segundo servidor todavía. Para salir: CTRL-C
Servidor B
primero configuramos OpenSIPs:
mv /etc/opensips/opensips.cfg /etc/opensips/opensips.cfg.old
nano /etc/opensips/opensips.cfg
Copiamos las líneas que siguen:
####### Global Parameters #########
debug=3
log_stderror=no
log_facility=LOG_LOCAL6
fork=yes
children=4
listen=udp:192.168.180.70:5060
disable_tcp=yes
disable_tls=yes
auto_aliases=no
####### Modules Section ########
#set module path
mpath="/lib/opensips/modules/"
#### SIGNALING module
loadmodule "signaling.so"
#### StateLess module
loadmodule "sl.so"
#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timer", 5)
modparam("tm", "fr_inv_timer", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)
#### Record Route Module
loadmodule "rr.so"
modparam("rr", "append_fromtag", 0)
#### MAX ForWarD module
loadmodule "maxfwd.so"
#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"
#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)
#### URI module
loadmodule "uri.so"
modparam("uri", "use_uri_table", 0)
#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", 10)
modparam("usrloc", "db_mode", 0)
#### REGISTRAR module
loadmodule "registrar.so"
modparam("registrar", "tcp_persistent_flag", 7)
#### ACCounting module
loadmodule "acc.so"
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
modparam("acc", "detect_direction", 0)
modparam("acc", "failed_transaction_flag", 3)
modparam("acc", "log_flag", 1)
modparam("acc", "log_missed_flag", 2)
#### CACHEDB_REDIS module
loadmodule "cachedb_redis.so"
modparam("cachedb_redis","cachedb_url","redis://localhost:6380/")
loadmodule "db_mysql.so"
loadmodule "dialog.so"
loadmodule "load_balancer.so"
modparam("dialog","cachedb_url","redis://localhost:6380/")
modparam("load_balancer","db_url","mysql://opensips:opensipsrw@192.168.168.60/opensips")
modparam("load_balancer", "probing_interval", 60)
modparam("load_balancer", "probing_reply_codes", "404")
####### Routing Logic ########
# main request routing logic
route{
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
exit;
}
if (has_totag()) {
# sequential request withing a dialog should
# take the path determined by record-routing
if (loose_route()) {
if (is_method("BYE")) {
setflag(1); # do accounting ...
setflag(3); # ... even if the transaction fails
} else if (is_method("INVITE")) {
# even if in most of the cases is useless, do RR for
# re-INVITEs alos, as some buggy clients do change route set
# during the dialog.
record_route();
}
# route it out to whatever destination was set by loose_route()
# in $du (destination URI).
route(1);
} else {
if ( is_method("ACK") ) {
if ( t_check_trans() ) {
# non loose-route, but stateful ACK; must be an ACK after
# a 487 or e.g. 404 from upstream server
t_relay();
exit;
} else {
# ACK without matching transaction ->
# ignore and discard
exit;
}
}
sl_send_reply("404","Not here");
}
exit;
}
# CANCEL processing
if (is_method("CANCEL"))
{
if (t_check_trans())
t_relay();
exit;
}
t_check_trans();
# preloaded route checking
if (loose_route()) {
xlog("L_ERR",
"Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
if (!is_method("ACK"))
sl_send_reply("403","Preload Route denied");
exit;
}
# record routing
if (!is_method("REGISTER|MESSAGE"))
record_route();
# account only INVITEs
if (is_method("INVITE")) {
setflag(1); # do accounting
}
if ($rU==NULL) {
# request with no Username in RURI
sl_send_reply("484","Address Incomplete");
exit;
}
if (!load_balance("1","voip/s","1")) {
send_reply("500","Failure to route");
exit;
}
route(1);
}
route[1] {
# for INVITEs enable some additional helper routes
if (is_method("INVITE")) {
t_on_branch("2");
t_on_reply("2");
t_on_failure("1");
}
if (!t_relay()) {
send_reply("500","Internal Error");
};
exit;
}
branch_route[2] {
xlog("new branch at $ru\n");
}
onreply_route[2] {
xlog("incoming reply\n");
}
failure_route[1] {
if (t_was_cancelled()) {
exit;
}
}
Guardamos los cambios y configuramos redis:
mkdir /etc/redis
nano /etc/redis/redis.conf
Copiamos la configuración para el segundo servidor:
daemonize no
pidfile /var/run/redis.pid
port 6380
timeout 0
loglevel notice
logfile stdout
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
slave-serve-stale-data yes
slave-read-only yes
slave-priority 100
maxclients 10000
maxmemory 3GB
maxmemory-policy noeviction
appendonly no
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
cluster-enabled yes
cluster-config-file /etc/redis/nodes-6380.conf
slowlog-log-slower-than 10000
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
Guardamos los cambios y iniciamos Redis en ambos servidores:
Servidor A:
redis-server /etc/redis/redis.conf
Servidor B:
redis-server /etc/redis/redis.conf
Para que cada nodo del Cluster sepa del otro hay que “presentárselo". En el servidor A entramos en la consola de Redis (en otra ventana terminal):
redis-cli
Miramos la situación del Cluster:
redis 127.0.0.1:6379> cluster nodes
28cc1625f86f777065a057fd19cc0cb8d49cf4b3 :0 myself - 0 0 connected
Todavía hay solo uno (el local). Escribimos:
redis 127.0.0.1:6379> cluster meet 192.168.180.70 6380
OK
Miramos nuevamente:
redis 127.0.0.1:6379> cluster nodes
a862b68040167034f20a1ec535769fc6e2e175e5 192.168.180.70:6380 master - 1352482127 1352482127 connected
28cc1625f86f777065a057fd19cc0cb8d49cf4b3 :0 myself - 0 0 connected
Ya el nodo local sabe del otro y viceversa.
Salimos de la consola:
redis 127.0.0.1:6380> quit
En la consola de los dos servidores Redis aparecerá:
A:
[15170] 09 Nov 17:28:40.440 * Connecting with Node d376333ff55910a6d54c9c7dace20d7d92a5ef9a at 192.168.180.70:16380
B:
[13763] 09 Nov 17:29:03.183 * Connecting with Node 86d0dc855d790c8ec42f63486e08b87613971d05 at 192.168.168.60:16379
El sistema Cluster de Redis pone a disposición de los nodos un total de 16384 Hash Slots. Como los nodos son dos, los Hash Slots se reparten de la siguiente manera:
Servidor A
Abrimos otra ventana terminal y escribimos:
echo '(0..8192).each{|x| puts "CLUSTER ADDSLOTS "+x.to_s}' | ruby | redis-cli -p 6379 > /dev/null
Servidor B
Abrimos otra ventana terminal y escribimos:
echo '(8193..16384).each{|x| puts "CLUSTER ADDSLOTS "+x.to_s}' | ruby | redis-cli -p 6380 > /dev/null
Salimos de ambos servidores Redis con CTRL-C y los volvemos a iniciar:
redis-server /etc/redis/redis.conf
Entramos en la consola Redis del servidor A:
redis-cli
redis 127.0.0.1:6379> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:2
Los dos Cluster están funcionando perfectamente.
Salimos de la consola:
redis 127.0.0.1:6379> quit
OpenSIPs busca la librería libhiredis.so.0.10 en la carpeta /usr/lib mientras se encuentra en /usr/local/lib. En ambos servidores creamos un enlace simbólico:
cd /usr/lib
ln -s /usr/local/lib/libhiredis.so.0.10 libhiredis.so.0.10
Ya podemos iniciar ambos servidores OpenSIPs:
service opensips start
La prueba
En otro servidor (AsteriskA) en la misma red local, con instalado Asterisk, enviamos todas las llamadas hacia los servidores OpenSIPs:
nano /etc/asterisk/sip.conf
al final de archivo añadimos los dos servidores OpenSIPs:
[opensipsA]
type=peer
context=from-opensips
host=192.168.180.70
disallow=all
allow=alaw
qualify=yes
[opensipB]
type=peer
context=from-opensips
host=192.168.168.60
disallow=all
allow=alaw
qualify=yes
Guardamos los cambios y recargamos la configuración SIP
asterisk -rx "sip reload"
Configuramos oportunamente el dialplan para enviar la llamadas al servidor OpenSIPsA y si falla al servidor OpenSIPsB
Configuramos los dos servidores Asterisk presentes en la configuración del balanceamiento de carga para que acepten INVITE que lleguen desde los dos servidores OpenSIPs:
[opensipA]
type=peer
context=opensipscluster
host=192.168.180.70
disallow=all
allow=alaw
dtmfmode = rfc2833
nat=no
[opensipB]
type=peer
context=opensipcluster
host=192.168.160.60
disallow=all
allow=alaw
dtmfmode = rfc2833
nat=no
Guardamos los cambios y recargamos la configuración SIP:
asterisk -rx "sip reload"
Ahora creamos el contexto “opensipscluster” nel dialplan:
nano /etc/asterisk/extensions.conf
[opensipscluster]
exten => _X.,1,Answer
same => n,Playback(tt-monkeys)
same => n,hangup
Guardamos los cambios y recargamos el dialplan:
asterisk -rx "dialplan reload"
Ahora desde un softphone conectado al servidor AsteriskA, marcamos cualquier numero que salga por una de las troncales OpeSIPs configuradas:
El resultado en el Asterisk donde llega la llamada:
OpenSIPsA:
OpenSIPsB:
El Cluster Redis y el modulo chachedb_redis, funcionan correctamente.
Nota final: no es muy aconsejable dejar el servidor MySQL en uno de los dos servidores donde está instalado OpenSIPs. Mejor tenerlo en un tercer servidor.
Fuentes:
OpenSIPs CACHEDB_REDIS module
Presentación “Distributed SIP clusters with OpenSIPS” by Bogdan
Articulo “Backporting into Redis 2.4 and other news” by Antirez
Recent comments