Enviar alertas de Kibana a Telegram

Lo que no se define no se puede medir. Lo que no se mide, no se puede mejorar. Lo que no se mejora, se degrada siempre
— William Thomson Kelvin
Este post no es continuación de logs-metricas.html pero están muy relacionados. De hecho sin el primero no se me habría ocurrido este otro, sin embargo como ya he dicho son independientes.

Kibana (ELK)

Kibana es una interfaz de usuario gratuita y abierta que te permite visualizar los datos de Elasticsearch y navegar en el Elastic Stack. Realiza lo que desees, desde rastrear la carga de búsqueda hasta comprender la forma en que las solicitudes fluyen por tus apps (https://www.elastic.co/es/kibana)

Kibana es el interface que usamos para visualizar los datos (como por ejemplo logs) que Elasticsearch ingesta de diferentes fuentes.

Un uso típico puede ser el ver de forma centralizada todos los logs que va generando un stack de servicios de tal forma que podamos dar un contexto único a los mismos, realizar búsquedas complejas e incluso diseñar diagramas explotando dichos datos. Así en el post logs-metricas.html contaba cómo hacer una anotación para servicios Grails tal que añadieran información en el contexto del log sobre la duración del método junto con los parámetros recibidos en el mismo. Esta meta-información puede llegar al Kibana de tal forma que no sólo puedas realizar búsquedas en el texto sino en esta meta-información.

Visualmente, esto sería un ejemplo de una línea de log en la consola web de Kibana junto con su meta-información:

t @containerId	rm26xpdn3qyx4i0ugphtplq8o/43b08f1b4da4
t @id	35625187335574879357932181753924573842110613149578166277
t @log_group	docker-logs
t @log_stream	service_1.2.rm26xpdn3qyx4i0ugphtplq8o/43b08f1b4da4
t @message	{"timestamp":"2020-08-15T10:50:49.748+0000","level":"INFO","thread":"http-apr-8080-exec-4","logger":"{...}"}
t @owner	602122916959
t @payload.className	com.puravida.service.HelloController
# @payload.duration	1,680
t @payload.methodName	hello
t @replica	2
t @service	service_1
 @timestamp	Aug 15, 2020 @ 10:50:49.748
t _id	35625187335574879357932181753924573842110613149578166277
t _index	cwl-2020.08.15
# _score	 -
t _type	docker-logs-production
t context	default
t correlationId	8f9b465a-d08c-4d95-b65b-82398d3dc127
t level	INFO
t logger	com.puravida.service.HelloController
t message	com.puravida.service.HelloController.hello: duration=1680 ms;
t thread	http-apr-8080-exec-4
 timestamp	Aug 15, 2020 @ 10:50:49.748
t userId	 - not logged -

Como puedes ver en este registro de Elasticsearch no sólo tenemos el message generado sino una serie de meta-campos extras como el @payload generado por nuestra anotación.

Gracias al motor de búsqueda de Elasticsearch desde Kibana puedes filtrar logs que contengan un valor de interés en estos metacampos, por ejemplo puedes filtrar:

@payload.className:HelloController

y obtener todos los logs generados por este controller y extraer de ellos el campo @payload.duration

Realizar gráficas que usen este campo junto, en un intervalo de tiempo definido en @timestamp es cuestión de minutos.

Telegram

A día de hoy asumo que prácticamente todo el mundo conoce Telegram (si estás leyendo este post probablemente es porque incluso eres usuario de este sistema de mensajería).

Una de las características de Telegram sobre otros sistemas es la posibilidad de crear canales y bots de forma simple (y gratuita) incluso privados.

En https://core.telegram.org/bots#6-botfather tienes la documentación oficial sobre cómo crear bots

En primer lugar (y siendo usuarios de esta plataforma) crearemos un bot mediante el BotFather (el bot de Telegram que nos sirve para crear y gestionar nuestros bots):

  • buscar desde la aplicación "botfather" y comenzar un diálogo con él

  • crear un bot siguiendo los pasos (dar un nombre y un id terminado en "bot" básicamente)

  • obtener el token. No hace falta guardarlo en lugar seguro pues podremos consultarlo en cualquier momento, pero no lo compartas más que con gente de confianza y ojo no guardarlo en un repositorio git público.

En segundo lugar crearemos un canal privado, por ejemplo "Puravida Alerts" y añadir al bot como administrador del canal . Puedes restringir sus permisos para que solamente pueda publicar mensajes. Así mismo hay que añadir a otros usuarios que puedan estar interesados en las alarmas.

Como el canal será privado (a no ser que quieras que cualquiera pueda suscribirse y ver las alertas) necesitaremos conocer su ID, que a diferencia del público donde es el @nombre_del_canal. Para ello tendremos que usar el interface web de Telegram (desconozco si se puede con el móvil, yo no lo he conseguido). Simplemente navegaremos hasta el canal creado con el navegador y nos fijaremos en la url que tiene asignada el canal:

El id del canal será "XXXXXXX", es decir el número comprendido entre c y _ en la URL

Alertas

El stack ELK (Kibana+Elasticsearch) cuenta con un ecosistema de plugins bastante extenso del cual vamos a usar para las alarmas el de opendistro (https://opendistro.github.io/for-elasticsearch/)

Si quieres probarlo primero antes de instalarlo en tu stack, puedes usar este docker-compose como punto de partida:

(1)
version: '3'
services:
  odfe-node1:
    image: amazon/opendistro-for-elasticsearch:1.9.0
    container_name: odfe-node1
    environment:
      - cluster.name=odfe-cluster
      - node.name=odfe-node1
      - discovery.seed_hosts=odfe-node1,odfe-node2
      - cluster.initial_master_nodes=odfe-node1,odfe-node2
      - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536 # maximum number of open files for the Elasticsearch user, set to at least 65536 on modern systems
        hard: 65536
    volumes:
      - odfe-data1:/usr/share/elasticsearch/data
    ports:
      - 9200:9200
      - 9600:9600 # required for Performance Analyzer
    networks:
      - odfe-net
  odfe-node2:
    image: amazon/opendistro-for-elasticsearch:1.9.0
    container_name: odfe-node2
    environment:
      - cluster.name=odfe-cluster
      - node.name=odfe-node2
      - discovery.seed_hosts=odfe-node1,odfe-node2
      - cluster.initial_master_nodes=odfe-node1,odfe-node2
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - odfe-data2:/usr/share/elasticsearch/data
    networks:
      - odfe-net
  kibana:
    image: amazon/opendistro-for-elasticsearch-kibana:1.9.0
    container_name: odfe-kibana
    ports:
      - 5601:5601
    expose:
      - "5601"
    environment:
      ELASTICSEARCH_URL: https://odfe-node1:9200
      ELASTICSEARCH_HOSTS: https://odfe-node1:9200
    networks:
      - odfe-net

volumes:
  odfe-data1:
  odfe-data2:

networks:
  odfe-net:
1 Sí, es el mismo que viene en la web de opendistro pero así hago este post más extenso

Básicamente levanta dos nodos de Elasticsearch y uno de Kibana con los plugins configurados. Una vez levantado puedes acceder a localhost:9200 y usar admin:admin para logearte.

La idea principal va a consistir en crear:

  • un monitor que inspeccione los registros cada cierto tiempo

  • un trigger que se ejecute dentro de este monitor cuando los registros cumplan una condicion

  • un destination a donde enviar la alarma

Destination

En primer lugar crearemos un destination (objetivo principal de este post) Puravida Telegram:

  • name: Puravida Telegram

  • type: Custom

  • custom attributes:

    • host: api.telegram.org

    • path: botELTOKENDELBOT-INCLUIDO-LOS-DOS-PUNTOS/sendMessage

  • header information:

    • Content-Type: application/json

Es importante escribir bien la url: - texto fijo "bot" - el token obtenido con BotFather incluidos los dos puntos, tal que XXXXXXX:YYYYYYYYYY - terminar en /sendMessage

Así mismo es necesario añadir un header con el Content-Type como application/json

Monitor

A continuación crearemos un Monitor

  • name: Slow HelloWorld

  • schedule: every 10 mnts

y usando el interface visual definiremos el monitor:

  • index: cwl-* (o el que hayas definido)

  • time field: @timestamp

crearemos una expresión para indicar el filtro a aplicar sobre los documentos:

kibana monitor

En este ejemplo le estamos indicando que seleccione la duración máxima del @payload.duration en los registros de la última hora.

Trigger

Podemos definir tantos triggers como queramos con diferente severidad y a diferentes destinations. En nuestro caso vamos a crear un trigger de severidad 1 al PuraVida Telegram destination creado anteriormente

Lo único que tenemos que hacer es darle un nombre, establecer una severidad e indicar la condición de disparo, por ejemplo:

IS ABOVE 500

lo cual enviará la alarma cuando la ejecución de HelloController sea superior a 500 mills

Añadiremos un action indicando un name a usar y seleccionando el destination PuraVida Telegram. Así mismo nos interesará personalizar el mensaje a enviar incluyendo por ejemplo la duración que ha hecho disparar la alarma por lo que cambiaremos el texto propuesto por defecto:

Message
{
"chat_id":"-100EL_ID_DEL_CHAT",
"text": "{{ctx.monitor.name}} alert\n Some HelloWorld takes {{#ctx.results}}{{#aggregations}}{{when.value}}{{/aggregations}}{{/ctx.results}} millisecs to be completed"
}
Fíjate que el canal se compone de -100 y el id extraído de la URL del mismo

Mediante el uso de {{`y `}} (handlebars) podremos incluir información relativa al evento. En este caso vamos a enviar el valor del aggregation: when

Para otro tipo de alarmas tienes que buscar qué información puedes extraer del contexto, básicamente inspeccionando la propiedad hits del mismo.

Desde esta consola puedes realizar una prueba de envío usando el enlace que aparece debajo Send test message. Si todo está bien configurado recibirás una notificación en el canal indicado.

Conclusión

Si todo lo anterior ha ido correctamente a partir de ahora tendrás un job que se ejecutará según le hayas programado y si se cumple alguna de las condiciones impuestas recibirás un mensaje en el canal.

Obviamente el destination puede ser un canal de #Slack por ejemplo, pero con los cientos de canales que tengo abiertos ahí al final no les presto atención a ninguno.

TODO

Con el API de Telegram es muy fácil enviar stickers así que me anoto como tarea pendiente en lugar de enviar un mensaje de texto, definir varios niveles de criticidad y enviar un sticker diferente para cada nivel

Follow comments at Telegram channel

2019 - 2020 | Mixed with Bootstrap | Baked with JBake v2.6.4