Kamailio como balanceador de carga para Asterisk: guía práctica con el módulo dispatcher

Kamailio como balanceador de carga para Asterisk: guía práctica con el módulo dispatcher

Cuando trabajaba instalando y configurando Asterisk de mis clientes, más tarde o temprano te encuentras con el mismo problema: un solo servidor de Asterisk no escala bien. Esto es algo que llevamos viendo desde siempre. Las llamadas se acumulan, la CPU se dispara con las llamadas (más aún si hace grabaciones o peor aún si hace trascoding), y cualquier reinicio para aplicar un cambio de configuración implica cortar el servicio. La solución clásica es poner varios Asterisk en paralelo y poner algo delante que reparta la carga. Ese «algo» es un SIP proxy: Kamailio, y la herramienta que lo hace posible es el módulo dispatcher.

Llevo varios años trabajando con este módulo en producción para repartir llamadas entre varios Asterisk, o entre varios proveedores (así sean llamadas entrantes o salientes) y este módulo es uno de los que hacen esa «magia» que tanto nos gusta a los que trabajamos a diario con Kamailio. Así que he optado por hacer un tutorial que explique cómo instalar Kamailio en Debian/Ubuntu, configurar el módulo dispatcher y enrutar las llamadas SIP hacia distintos servidores Asterisk en función del usuario que origina la llamada. No es magia, es lógica de enrutamiento SIP bien configurada.

La arquitectura que vamos a montar

El escenario es simple y funcional:

  • Kamailio actúa como proxy SIP. Los terminales (softphones, teléfonos IP, troncales SIP) se registran contra Kamailio y Kamailio decide a qué Asterisk mandar cada llamada.
  • Asterisk A (192.168.1.10) y Asterisk B (192.168.1.20) son los servidores de media. Aquí vive la lógica de negocio: IVRs, colas, extensiones, etc.
  • Importante!: Kamailio no toca el audio. Es un proxy de señalización pura. El RTP va directo entre el terminal y Asterisk.

Esto puede dar problemas con terminales detrás de un router con CG-NAT ya que estos routers suelen cerrar puertos muy rápidamente y nos encontraríamos con problemas de audio en un único sentido, pero por lo general, con este escenario debería funcionar todo correctamente.

El módulo dispatcher de Kamailio mantiene un conjunto de destinos (los Asterisk) y los distribuye según distintos algoritmos: round-robin, hash del usuario, carga activa… En nuestro caso usaremos hash del usuario (algoritmo «3»), lo que garantiza que un mismo usuario siempre va al mismo Asterisk mientras ambos estén disponibles. Esto es importante para que el estado de los canales, los parqueos de llamada y los grupos de captura sean coherentes.

Requisitos previos

  • Un servidor con Debian 12 o Ubuntu 22.04/24.04 limpio para Kamailio
  • Dos servidores Asterisk funcionando (o al menos accesibles por red)
  • Acceso root o sudo en todos los servidores
  • Los puertos UDP 5060 abiertos entre Kamailio y los Asterisk

1. Instalación de Kamailio

Usamos los repositorios oficiales de Kamailio. No instales el paquete del repositorio base de Debian/Ubuntu: suele ir muy atrasado de versión.

# Añadimos el repositorio oficial de Kamailio (versión 5.8)

curl -fsSL https://deb.kamailio.org/kamailiodebkey.gpg | gpg --dearmor -o /usr/share/keyrings/kamailio.gpg

echo "deb [signed-by=/usr/share/keyrings/kamailio.gpg] http://deb.kamailio.org/kamailio58 $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/kamailio.list

apt update
apt install -y kamailio kamailio-mysql-modules kamailio-utils-modules

Además de los módulos base, instalamos kamailio-utils-modules porque incluye el módulo dispatcher que es el protagonista de este tutorial.

Comprobamos que Kamailio arranca:

systemctl enable kamailio
systemctl start kamailio
systemctl status kamailio

2. Configuración básica de Kamailio

El fichero de configuración principal es /etc/kamailio/kamailio.cfg. Es un fichero largo e intimidante si no lo has visto antes, pero no te preocupes: vamos a construirlo desde cero con lo mínimo necesario para que funcione el balanceo.

Antes de editar el cfg, ajustamos los parámetros globales en /etc/default/kamailio:

MEMORY=128
SHM_MEMORY=256
CFGFILE=/etc/kamailio/kamailio.cfg
USER=kamailio
GROUP=kamailio
DUMP_CORE=no

Ahora el fichero principal. Reemplaza el contenido de /etc/kamailio/kamailio.cfg con lo siguiente:

#!KAMAILIO

##############################################
# Parámetros globales
##############################################

debug=2
log_stderror=no
log_facility=LOG_LOCAL0

fork=yes
children=4

port=5060
listen=udp:0.0.0.0:5060

##############################################
# Módulos a cargar
##############################################

loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "dispatcher.so"

##############################################
# Parámetros de módulos
##############################################

# tm - Transaction Manager
modparam("tm", "failure_reply_mode", 3)
modparam("tm", "fr_timer", 30000)
modparam("tm", "fr_inv_timer", 120000)

# rr - Record-Route
modparam("rr", "enable_full_lr", 1)

# dispatcher - el módulo clave
modparam("dispatcher", "list_file", "/etc/kamailio/dispatcher.list")
modparam("dispatcher", "flags", 2)
modparam("dispatcher", "dst_avp", "$avp(ds_dst)")
modparam("dispatcher", "grp_avp", "$avp(ds_grp)")
modparam("dispatcher", "cnt_avp", "$avp(ds_cnt)")
modparam("dispatcher", "sock_avp", "$avp(ds_sock)")

# Comprobación de disponibilidad de los Asterisk cada 30 segundos
modparam("dispatcher", "ds_ping_interval", 30)
modparam("dispatcher", "ds_probing_mode", 1)
modparam("dispatcher", "ds_ping_from", "sip:kamailio@tudominio.com")

##############################################
# Lógica de enrutamiento
##############################################

request_route {

  # Comprobaciones básicas de sanidad SIP
  if (!mf_process_maxfwd_header(10)) {
    sl_send_reply("483", "Too Many Hops");
    exit;
  }

  if (!sanity_check()) {
    exit;
  }

  # Gestionamos los ACK de transacciones existentes
  if (is_method("ACK")) {
    if (t_check_trans()) {
      t_relay();
    }
    exit;
  }

  # Gestionamos los CANCEL
  if (is_method("CANCEL")) {
    if (t_check_trans()) {
      t_relay();
    }
    exit;
  }

  # Añadimos Record-Route para mantener el diálogo
  if (is_method("INVITE|SUBSCRIBE")) {
    record_route();
  }
 
  # Si ya tiene Route header, seguimos la ruta existente
  if (loose_route()) {
    route(RELAY);
    exit;
  }

  # Enrutamiento de INVITE hacia los Asterisk
  if (is_method("INVITE")) {
    route(DISPATCH);
    exit;
  }

  # El resto de métodos: OPTIONS, REGISTER, etc.
  route(RELAY);
}

route[DISPATCH] {
  # Seleccionamos destino usando hash del usuario (algoritmo 4)
  # El grupo 1 es el que definimos en dispatcher.list
  if (!ds_select_dst(1, 4)) {
    xlog("L_ERR", "No hay destinos disponibles en el grupo 1\n");
    sl_send_reply("503", "Service Unavailable");
    exit;
  }

  xlog("L_INFO", "Enviando llamada de $fu hacia $du (dispatcher)\n");

  # Configuramos failover: si falla el primer Asterisk, intenta con el siguiente
  t_on_failure("MANAGE_FAILURE");
  route(RELAY);
}

route[RELAY] {
  if (!t_relay()) {
    sl_reply_error();
  }
  exit;
}

failure_route[MANAGE_FAILURE] {
  if (t_is_canceled()) {
    exit;
  }

  # Si el destino falla (408, 5xx, 6xx), marcamos como inactivo e intentamos otro
  if (t_check_status("408|5[0-9][0-9]|6[0-9][0-9]")) {
    ds_mark_dst("ip"); # marca como inactivo y prueba el siguiente
    if (!ds_next_dst()) {
      xlog("L_ERR", "No hay más destinos disponibles. Fallando la llamada.\n");
      t_reply("503", "All destinations failed");
      exit;
    }
    route(RELAY);
  }
}

3. El fichero dispatcher.list: aquí defines tus Asterisk

Este es el fichero más importante para el balanceo. Cada línea define un destino con el formato:

GRUPO  URI  FLAGS  PRIORIDAD  ATRIBUTOS  DESCRIPCION

Crea el fichero /etc/kamailio/dispatcher.list con tus servidores Asterisk:

# /etc/kamailio/dispatcher.list
# Formato: setid  uri  flags  priority  attrs  description

# Grupo 1: Asterisk de producción
1  sip:192.168.1.10:5060  0  0  weight=50  Asterisk-A
1  sip:192.168.1.20:5060  0  0  weight=50  Asterisk-B

El número de grupo (1 en este caso) es el que usamos en la función ds_select_dst(1, 3) del routing. Puedes tener varios grupos para distintos propósitos: un grupo para llamadas entrantes, otro para llamadas salientes, otro para usuarios premium, etc.

El algoritmo 3 hace hash del Request-URI (el usuario al que se llama). Así, todas las llamadas hacia el mismo destino van siempre al mismo Asterisk. Si prefieres hacer hash del From (el usuario que llama), usa el algoritmo 1, que hashea el Call-ID y es más uniforme. Para round-robin simple usa el algoritmo 4.

Después de modificar el fichero, recarga el dispatcher sin reiniciar Kamailio:

kamctl dispatcher reload

O bien usando el RPC de Kamailio:

kamcmd dispatcher.reload

4. Configuración de Asterisk para aceptar conexiones de Kamailio

Los Asterisk tienen que aceptar las llamadas que vienen de la IP de Kamailio. En PJSIP (/etc/asterisk/pjsip.conf), crea un endpoint para Kamailio:

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

[kamailio]
type=endpoint
context=desde-kamailio
disallow=all
allow=alaw,ulaw
direct_media=no
trust_id_inbound=yes
from_domain=tudominio.com

[kamailio]
type=identify
endpoint=kamailio
match=192.168.1.1  ; IP de tu Kamailio

[kamailio]
type=aor
max_contacts=1

El parámetro direct_media=no es crítico. Le dice a Asterisk que no intente negociar media directo entre terminales, porque Kamailio no va a estar en medio del RTP. Si lo pones a yes y los terminales están detrás de NAT, el audio no funcionará.

En el dialplan (/etc/asterisk/extensions.conf), crea el contexto que recibe las llamadas:

[desde-kamailio]
exten => _X.,1,NoOp(Llamada recibida desde Kamailio para EXTEN_VAR)
 same => n,Dial(PJSIP/EXTEN_VAR,30)
 same => n,Hangup()

(Nota: sustituye EXTEN_VAR por ${EXTEN} en tu fichero real.)

5. Verificar que el dispatcher funciona

Una vez que todo está arrancado, puedes ver el estado de los destinos del dispatcher con:

kamcmd dispatcher.list

La salida te muestra algo así:

DISPATCHER SETS[1]:
SET[0]:
 ID: 1
 DESTS[2]:
  DEST[0]: URI=sip:192.168.1.10:5060 FLAGS=AP PRIORITY=0
  DEST[1]: URI=sip:192.168.1.20:5060 FLAGS=AP PRIORITY=0

El campo FLAGS es el que más información te da:

  • A (Active): el destino está activo y recibiendo llamadas
  • P (Probing): Kamailio está enviando OPTIONS periódicos para verificar disponibilidad
  • I (Inactive): el destino no responde y ha sido marcado como caído
  • D (Disabled): el destino está deshabilitado manualmente

Si quieres poner un Asterisk en mantenimiento sin cortar las llamadas en curso, puedes deshabilitarlo manualmente:

# Deshabilitar el destino sip:192.168.1.20:5060 del grupo 1
kamcmd dispatcher.set_state ip 1 sip:192.168.1.20:5060

Y para volver a habilitarlo:

kamcmd dispatcher.set_state ap 1 sip:192.168.1.20:5060

6. Depuración y logs

Cuando algo no funciona, el primer sitio donde mirar es el syslog de Kamailio:

tail -f /var/log/syslog | grep kamailio

Si quieres ver el SIP en crudo para entender qué está pasando con la señalización, usa sngrep:

apt install -y sngrep
sngrep -d eth0 port 5060

sngrep es una herramienta imprescindible para depurar SIP. Muestra los diálogos SIP de forma visual en la terminal, con colores y filtros, y es infinitamente más cómodo que hacer un tcpdump y analizar el pcap a mano.

Para activar el debug del dispatcher específicamente, puedes bajar el nivel de log de Kamailio temporalmente:

# Bajar el nivel de debug en caliente (sin reiniciar)
kamcmd cfg.set_now_int core debug 4

Y volver al nivel normal cuando termines:

kamcmd cfg.set_now_int core debug 2

Y con esto y un bizcocho…

Con esta configuración tienes un proxy SIP funcional que balancea llamadas entre varios Asterisk usando hash del usuario, con detección automática de caídas y failover transparente. Es la base sobre la que construir una arquitectura de telefonía seria.

Hay varias cosas que quedan fuera del alcance de este tutorial pero que son el siguiente paso natural:

  • Registro de usuarios: para que los terminales se registren contra Kamailio y este sepa dónde está cada usuario, necesitas el módulo registrar y una base de datos (normalmente MySQL con kamailio-mysql-modules).
  • Autenticación: el módulo auth y auth_db para que los terminales autentiquen contra Kamailio.
  • NAT traversal: el módulo nathelper y un servidor STUN o un mediaproxy (RTPengine, rtpproxy) para los clientes detrás de NAT.
  • TLS y SRTP: para cifrar la señalización y el audio.

El dispatcher es solo uno de los cientos de módulos que viene con Kamailio, pero es uno de los que más partido te da en un entorno que trabaje con varios Asterisk. Si lo manejas bien, tendrás la base para construir lo que necesites encima.

 

1 comentario
Abierto
  1. Rosa Atienza

    Que pasó a paso tan completo!