Soluciones VozIP con Integración de bus de mensajería AMQP – Kamailio

Muy buenas!

Seguimos con la línea de comentar las grandes posibilidades de integración de nuestro querido Kamailio 😉

Esta vez, en lugar de hablar de flujos SIP, de arquitecturas integradas con Asterisk, de estrategias de gestión de RTP … nos queremos centrar en AMQP (advanced message queuing protocol)

Necesidades cada vez mayores de comunicación inter-plataforma/sistema

De cara a exponer las bondades de los buses de mensajes, y de AMQP en particular, creemos que lo lo mejor es centrarse en el proceso de diseño y escalado de los sistemas que se desarrollan, no siendo nada centrado específicamente en VozIP. En muchos casos, se opta por unir los componentes en base a la necesidad de información puntual que tengan, de un acontecimiento concreto que les es importante saber.

Es decir, por ejemplarizarlo: Si queremos que un sistema de grabación de llamadas «se entere» de que hay una nueva grabación, optaremos, generalmente por:

  • gearmanRealizar triggering en el colgado, de lo que haya que hacer.
  • Optar por desacoplar la secuencia y lanzar Jobs, con algún mecanismo «reliable» que garantice que se hace (GEARMAN es nuestro gran amor en este área).
  • Técnicas de polling para ver si hay algo nuevo y hay que hacer algo con ello.

Todos los que trabajamos en estas galaxias hemos optado seguramente por las tres opciones, en función de criticidad/importancia, tiempo invertible, experiencia que tengamos.

Sin embargo, en nuestra mas humilde opinión, cuando se  trabaja con entornos con múltiples componentes que consumen información entre ellos (Aplicaciones Web, Sistemas de Seguridad, Mecanismos de control de capacidad, lógicas de negocio con visión global de status, …), es altamente probable que la información que necesita un componente sea también necesaria por otro – siendo perfectamente posible que no se conozca dicha necesidad en las primeras fases del análisis. Con ello, lo que acaba sucediendo es es un full-mesh (o quasi) de información cruzada, y modificaciones en todos los componentes cada vez que uno necesita saber de lo que hace otro.full_mesh

Ya que hemos empezado con él: Continuando con el ejemplo de las grabaciones, es perfectamente posible que una grabación haya que transcodificarla, moverla o copiarla a otro lado, relacionarla; y en, paralelo, si alguien está consultando la sección de Grabaciones de un portal web, que bonito sería que le haga «hey! nueva grabación» al usuario, y sin que la web tenga que estar todo el rato haciendo polling para saberlo.

Ilustrándolo como los temas de Presencia, el mecanismo de subscripción («Hey, avísame si pasa algo con Bob«) encaja muy bien en la necesidad: Avísame si pasa algo con Bob, avísame si pasa algo con Alice, y, para terminar: Avísame si pasa algo que tenga que ver con la seguridad, porque soy un firewall perimetral y me interesa saberlo para poder hacer mi trabajo. De esta forma, cuando se haga el Publish del cambio de Bob, todo el mundo que esté subscrito a su status recibirá la información. Y, lo interesante de esto es que quien haga dicho Publish no tiene necesariamente porque saber quien estará subscrito, él simplemente lo lanza, y el protocolo se encarga 😉

Llegados a este punto, lo que nos interesa es un protocolo / mecanismo / X de comunicación que nos permita intercambiar todo tipo de mensajes, con quien o quienes estén interesados en recibirlo, y, si encima podemos clasificarlos de alguna forma, la guinda ! De ahí que nos guste tanto AMQP:

AMQP

AMQP es el protocolo de mensajes que creemos por excelencia ya hoy por hoy. Al menos, en nuestro caso, el que nos permite construir 🙂

En sí, es el protocolo (como SMTP, HTTP, SIP), existiendo múltiples implementaciones de broker completo:

Así como librerías/componentes disponibles para casi todos los entornos, y una especificación poco difusa. Es decir, al contrario que en los mundos de SOAP y derivados dónde no siempre es todo tan fácil (versiones, enveloppings, encodings, …), si un componente habla AMQP, no se esperan problemas de integración. Es decir, al igual que con SMTP por ejemplo, donde Postfix’s, Exims, Exchange y derivados conviven en armonía, con AMQP creemos estar en la misma situación.

En lo que respecta los conceptos y entidades de AMQP, en la propia web de Rabbit lo explican de forma excelente

De forma muy resumida, tenemos:

  • Publisher: Quien envía un mensaje (y no sabe quien lo recibirá 😉 que será a quien le interese!)
  • Consumer:  Quien recibe el mensaje.hello_world_amqp

Los mensajes son publicados por el Publisher en un Exchange y FIN, él se olvida, es como un apartado de correos, él no sabe nada ya. Lo que publica es el mensaje en sí, así como una routing key, que sería por así decirlo «el tema».

Los consumidores que estén interesados en recibir mensajes, se conectan al broker (Server AMQP), definen o reutilizan una cola y la vinculan con un exchange en base a un routing key. Algo así como decirle al señor de Correos: «Oye, todas las cartas que lleguen a Bilbao, y sean de publicidad de deportes, vete dándomelas en este buzón mío, que ya las voy cogiendo«.

Y claro, todo esto lo podemos complicar mucho con exchanges de tipo fanout, el routing topic que tanto nos gusta, colas con/sin persistencia, o incluso colas con TTL – por así decirlo: «Me interesan este tipo de cartas, pero si tienen mas de 2 días, ya no tanto, que se borren solas«.

RabbitAMQP

RabbitMQLogoSe trata del broker AMQP que mas  conocemos por aquí, y con el que sinceramente estamos encantados.

Sin querer entrar en los detalles de su instalación, con paquetes para prácticamente todas las distribuciones, lo interesante como primer paso para poder realizar pruebas de concepto es habilitar su consola web de maangement, distribuida en forma de plugin:

rabbitmq-plugins enable rabbitmq_management

 

Con este comando, se nos habilita un servidor HTTP, que escucha en el puerto 15672 (Debian Jessie) o 55672 (Debian Wheezy), y se puede realizar todo tipo de operaciones de forma gráfica sin necesidad de programarse un productor o consumidor.consola_rac

Dicha consola, no sólo nos viene bien para probar los conceptos, e ir creando exchanges, colas, publicando desde la propia web mensajes y luego consumirlos en otra sesión; sino que también  es una herramienta excelente para debugging puntual en producción, ya que podemos conectarnos como consumidores y confirmar que recibimos la información que se debe, o simplemente ver que los mensajes / segundo siguen fluyendo y que todo va bien!

Kamailio Kazoo Module – AMQP

h0us3s-Signs-Hazard-Warning-17-300pxAntes de nada, un mega warning ! El módulo Kazoo, que soporta AMQP correctamente, es posible que cambie / que haya uno nuevo, y su futuro es incierto, en la última reunión en el IRC de los desarrolladores de Kamailio, se habló de dicho módulo. En concreto, interesante destacar al gran Daniel-Constantin, sentenciando:

miconda_: kazoo can still be split/renamed — it just needs someone between chair and keyboard to do it

A pesar de su nombre, el módulo en sí, no sólo está orientado a Kazoo, es perfectamente compatible con AMQP Standard 0.9.1. Por otra parte, no sólo está planteado para Publicar/Consumir mensajes, sino que soporta funcionalidades PUA (Presence User Agent).

Sea como fuere, en la documentación habitual se puede consultar más información.

El módulo está perfactamente disponible en los paquetes preparados por Victor Seva (linuxmaniac), siendo en Debian Jessie, para la rama 4.4:kamailio-from-world-logo (1)

  • Sources: deb     http://deb.kamailio.org/kamailio44 jessie  main
  • Paquete:  kamailio-kazoo-modules

En lo que respecta la la inicialización del módulo:

[...]
loadmodule "kazoo.so"
[..]
modparam("kazoo", "node_hostname", "kamailiotesting")
modparam("kazoo", "amqp_connection", "amqp://guest:guest@localhost:5672")
modparam("kazoo", "pua_mode", 0)

Sobre dicha configuración base inicial, cabe destacar:

  • amqp_connection: Nos conectamos, en este caso a localhost, pero puede ser cualquier otro server. Por defecto guest/guest es creado por Rabbit AMQP, así que sin hacer nada, se podrá conectar bien.
  • pua_mode: Como bien comentan en la lista, si no queremos integrarlo con presencia (que no es el caso), con desactivarlo debería bastar.

Tras reiniciar Kamailio, en la consola web de RabbitAMQP vemos perfectamente como aparece la nueva conexión:

rabbitAMQ_connections
Hellow World! I’m Kamailio on AMQP

Las funciones que exporta el módulo, usables en nuestro script de configuración, son relativas a la publicación y recepción de mensajes. En nuestro caso nos centraremos en la publicación, que es lo que nos interes, por tanto, la que nos interesa es:

kazoo_publish(exchange, routing_key, json_payload)

En cuanto a los parámetros: el exchange es el punto de intercambio en el broker, todo mensaje publicado debe ir a un exchange (dónde acabe es otra cosa, si acaba en algún lado), la routing_key se utiliza para encaminar, en función del tipo de exchange (direct, por topic – siendo este último el que mas nos interesa).

Por suerte, RabbitAMQP lo pone fácil, si no se especifica exchange, va al default exchange, y el routing key especifica la cola donde acabará, sin tener que hacer binding en el consumidor.

Así que declaramos tranquilamente vía web la cola:

creando_cola_kamailio

 

y desde Kamailio, con la configuración default añadimos en el main routing block:

        if (is_method("REGISTER"))
        {
                kazoo_publish("","kamailio","{ 'hello' : 'world of Register' }");
        }

Lanzamos unos Register contra el Kamailio y vemos rápidamente en la web como aumentan los contadores:

cola_kamailio

Y finalmente, tachánnnnnnnnnnnnn! Desde la misma ventana accedemos a la sección de Get Message, cogemos el último:

cogiendo_mensaje

Y ahí está nuestro Payload !

 

Proof of concepts! Diseñando la estrategia …

Como nos suele gustar mucho, pensando en que queremos hacer sin plantearnos si se puede o no o como tienen que hablar las diferentes partes. Si conseguimos simplemente limitarnos a nivel conceptual, y no como ingenieros que luego lo tendríamos que construir (jeje, muchas veces es difícil mantener dicha perspectiva tech-agnostic) podríamos tener un dibujo de un escenario similar a este:

esquema_global

Preparando el correo postal 😉

Siguiendo esta puesta en escena, lo que nos interesa por tanto es un exchange de tipo: «topic», y que los distintos agentes se subscriban a los temas que les interesan (los firewalls perimetrales, sistemas de antifraude, etc .. a temas de seguridad – las aplicaciones web a lo específico sobre canales/llamadas/status de nuestro device) .

Dicho esto, la parte de Kamailio es bastante sencilla en lo que a decisión como Publisher: publicará siempre en el exchange, llamemoslo «voicelife», y con el tema que considere.

Desde el punto de vista de las queues, la decisión pasará al plano de si queremos persistencia o no, de este modo, los agentes de postprocesado de grabaciones quizás la necesiten (si no están disponibles o no cogen el mensaje – que no se borre – es importante), en cambio el NodeJS o similar que actúa como componente Web para mostrar las llamadas: Si está, que coja el mensaje, sino, que se pierda – a la web no le interesa dibujar como estaban las llamadas hace 10 minutos 😉

Config Samples

Siguiendo la línea de lo que queremos ilustrar, tenemos por ejemplo:

Informar de una request bloqueada por Pike:

if (!pike_check_req()) 
{
  [...]
  kazoo_publish("voicelife","security.pike.blockedrequest","{ 'blockedip' : '$si' }");
  [...]
  exit;
}

Informar de un Registro SIP Expirado:

Es posible que nos interese informar cada vez que expira un registro, para que la web por ejemplo, pueda mágicamente cambiar su icono de verde a rojo 😉

Gracias al módulo usrloc, tenemos evento!

 

...
event_route[usrloc:contact-expired] {
    kazoo_publish("","registros","{ 'aor' : '$ulc(exp=>aor)' }");
}
...

Y con ello, recibimos por ejemplo:

mensaje_expires

Control de  diálogos

Utilizando el módulo dialog, tenemos la suerte de tener estos dos eventos que se lanzan en el start/end. Lo suyo sería completarlo con algo mas que el callid, el tipo, user’s id y tal 😉 Tenerlo en el contexto del diálogo para poder publicarlo y tal de forma ya preparada para quien le interese.

A modo de prueba de concepto:

event_route[dialog:start]{
        xlog("event dialog started");
	kazoo_publish("","dialogos","{ 'startcallid': '$dlg(callid)' }");
}


event_route[dialog:end]{
        xlog("event dialog ended");
	kazoo_publish("","dialogos","{ 'endcallid': '$dlg(callid)' }");
}

(tenemos que haber construido el diálogo con dlg_manage en la initial request invite y tal).

Obtenemos por ejemplo, dos mensajes tales que:

dialog_module_KAM_AMQP

 

Probando la recepción de eventos

De forma independiente a la consola web, que nos permite ir cogiendo mensajes y haciendo requeue (si procede), podemos igualmente consumirlos desde CLI, a modo de ejemplo:

root@zgorkamrtp:~# amqp-consume -u amqp://guest:[email protected]:5672 -q kamailio cat
{ "hello": "world 10.10.1.230", "App-Name": "kamailio", "App-Version": "4.4.0", "Node": "kamailio@proxyusers", "Msg-ID": "2769ca3f-30af-40e0-bce8-6dd6e49439d2", "Server-ID": "kamailio@proxyusers-<1>-targeted-31" }

Algo de benchmarking …

Gracias a nuestro gran amigo SIPP, lanzar una batería simple de tests que ejecuten por ejemplo un register, no es nada complejo.

Así que nos planteamos por un lado, la gestión del Register en Kamailio, a modo de pruebas, nada del otro mundo, pero publicando a AMQP.

Y por otra parte, SIPP preparado para lanzar Register’s de forma agresiva, con un escenario simple, sin autenticación ni nada:

<?xml version="1.0" encoding="ISO-8859-2" ?>

<scenario name="registros_simple_sinauth">
  <send retrans="500">
    <![CDATA[

      REGISTER sip:[remote_ip] SIP/2.0
      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
      From: <sip:test@test>;tag=[call_number]
      To: <sip:test@test>
      Call-ID: [call_id]
      CSeq: [cseq] REGISTER
      Contact: sip:test@[local_ip]:[local_port]
      Max-Forwards: 10
      Expires: 120
      User-Agent: SIPP/AmqpTesting
      Content-Length: 0

    ]]>
  </send>

  <recv response="100" optional="true">
  </recv>

  <recv response="200">
  </recv>

  <!-- response time repartition table (ms)   -->
  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>

  <!-- call length repartition table (ms)     -->
  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>

</scenario>

Lo lanzamos tal que, a modo de ejemplo con nuestro direccionamiento:

sipp -p 5090 10.10.0.160 -sf REGISTER_sinauth.xml -r 10 -rate_max 1000 -rate_interval 30 -rate_increase 10

De forma inicial, pero aumentando de forma progresiva el CPS, para intentar forzar un poco la máquina 😉 Con estos parámetros, iniciará con 10 registros por seg, llegando hasta un máximo de 1000, aumentando cada 30 segundos 10 más. Es decir, al de un minuto serán 10+20 requests/seg, al de 10 minutos serán +200, al de unos 50 minutos abremos llegado a tope. Así es más fácil obtener gráficas con Munin y tal 😉

Lo que obtenemos  como resultados, nos muestra que para llegar a stressar y que empiece a consumir CPU, tenemos que llegar a request’s/seg muy altos:

Resumen SIPP

------------------------------ Scenario Screen -------- [1-9]: Change Screen --
  Call-rate(length)   Port   Total-time  Total-calls  Remote-host
1000.0(0 ms)/1.000s   5090    3000.38 s      1514935  10.10.0.160:5060(UDP)

  0 new calls during 0.000 s period      0 ms scheduler resolution
  0 calls (limit 3000)                   Peak was 176 calls, after 2685 s
  0 Running, 32968 Paused, 0 Woken up
  0 dead call msg (discarded)            0 out-of-call msg (discarded)        
  1 open sockets                        

                                 Messages  Retrans   Timeout   Unexpected-Msg
    REGISTER ---------->         1514935   2         0                  
         100 <----------         0         0         0         0        
         200 <----------         1514935   0         0         0        
------------------------------ Test Terminated --------------------------------


----------------------------- Statistics Screen ------- [1-9]: Change Screen --
  Start Time             | 2016-04-30	12:35:40.335072	1462012540.335072         
  Last Reset Time        | 2016-04-30	13:25:40.717026	1462015540.717026         
  Current Time           | 2016-04-30	13:25:40.717130	1462015540.717130         
-------------------------+---------------------------+--------------------------
  Counter Name           | Periodic value            | Cumulative value
-------------------------+---------------------------+--------------------------
  Elapsed Time           | 00:00:00:000000           | 00:50:00:382000          
  Call Rate              |    0.000 cps              |  504.914 cps             
-------------------------+---------------------------+--------------------------
  Incoming call created  |        0                  |        0                 
  OutGoing call created  |        0                  |  1514935                 
  Total Call created     |                          |  1514935                 
  Current Call           |        0                  |                          
-------------------------+---------------------------+--------------------------
  Successful call        |        0                  |  1514935                 
  Failed call            |        0                  |        0                 
-------------------------+---------------------------+--------------------------
  Call Length            | 00:00:00:000000           | 00:00:00:000000          
------------------------------ Test Terminated --------------------------------

Consumo CPU

cpu

Mensajes AMQP

A modo de ejemplo, lo que muestra la consola de Rabbit AMQP en esta prueba:

message_rates

Dashboards AMQP con Graylog

Hoy por hoy, con el diseño de arquitecturas complejas con muchos componentes participando en el interés común del servicio, el aseguramiento del servicio no sólo pasa por diseñar arquitecturas HA o sistemas self-healthness en caso de que falle algo, lo obvio es plantear que la monitorización es algo totalmente necesario, hasta aquí lo normal.

Como siguiente step, se suelen diseñar plugins/tests que prueban el ciclo productivo end2end, es decir, lanzar un diálogo con SIPSAK, esperar que se reciba en XXX, y validar que se ha marcado el CDR, que aparece en tal o cual. Pero todo esto no nos cubre todos los casos, puede haber situaciones en las que la plataforma está OK, pero sus métricas de ciertos aspectos difieren de lo esperado acorde a la carga de un día normal, es ahí dónde interesan los dashboards 🙂 A parte de que quedan bastante bien para la foto jiji 😉

Lo interesante de la estrategia AMQP es que Graylog2, el gran analizador de información, principalmente de logs, se conecta nativamente a AMQP y es un source input adicional, como cualquier otro. Podemos crear Extractors en base al JSON del contenido, dashboards o hacer búsquedas.

A modo de ejemplo, para lanzar el input AMQP y configurarlo::

amqp_launcher

Una vez definido, nos podemos ir creando dashboards actualizados prácticamente en tiempo real:

kamailio_graylog_amqp

Simplemente creando las búsquedas que nos interesan.

Despedida y good luck!

Nada más por hoy en este área 🙂 Simplemente comentaros que por aquí nos está funcionando en entornos de alta carga a las mil maravillas este tipo de diseños de arquitectura, así como el broker RabbitAMQP, del cual estamos muy enamorados !

 

 



¿Te gusta este post? Es solo un ejemplo de cómo podemos ayudar a tu empresa...
Sobre Gorka Gorrotxategi

CTO en Irontec, en el frente técnico desde un par de lustros ya, para todo lo que tenga que ver con Networking, VoIP y Sistemas, en ese orden :D) Desde @zetagor escribo algo, pero poco verbose la verdad

1 Comentario

¿Por qué no comentas tú también?

Queremos tu opinión :)