Enviar alertas de Kibana a Telegram
Tags: groovy, grails, kibana, logs
Published: 2020-08-15 19:42:23

---
title: Enviar alertas de Kibana a Telegram
published: true
date: 2020-08-15 00:00:00 UTC
tags: groovy,grails,kibana,logs
canonical_url: https://jorge.aguilera.soy/blog/2020/kibana-telegram.html
---

> 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](logs-metricas.html) pero están muy relacionados. De hecho sin el primerono 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 navegaren el Elastic Stack. Realiza lo que desees, desde rastrear la carga de búsqueda hasta comprenderla forma en que las solicitudes fluyen por tus apps_ ([https://www.elastic.co/es/kibana](https://www.elastic.co/es/kibana))

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

Un uso típico puede ser el ver de forma centralizada todos los logs que va generando un stack de servicios de talforma que podamos dar un contexto único a los mismos, realizar búsquedas complejas e incluso diseñar diagramasexplotando dichos datos. Así en el post [logs-metricas.html](logs-metricas.html) contaba cómo hacer unaanotación para servicios Grails tal que añadieran información en el contexto del log sobre la duración del métodojunto con los parámetros recibidos en el mismo. Esta meta-información puede llegar al Kibana de tal forma que no sólopuedas 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-camposextras 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 enestos 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 porqueincluso 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](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 Telegramque 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 nolo 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 usuariosque puedan estar interesados en las alarmas.

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

`https://web.telegram.org/#/im?p=cXXXXXXX_yyyyyyyyy`

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 paralas alarmas el de **opendistro** ([https://opendistro.github.io/for-elasticsearch/](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 puedesacceder 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](https://jorge.aguilera.soy/images/2020/elk/kibana-monitor.png)

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

### Trigger

Podemos definir tantos triggers como queramos con diferente severidad y a diferentes _destinations_. En nuestro casovamos 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 dispararla 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 aenviar el valor del `aggregation: when`

Para otro tipo de alarmas tienes que buscar qué información puedes extraer del contexto, básicamente inspeccionando lapropiedad `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 programadoy 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 abiertosahí 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 unmensaje de texto, definir varios niveles de criticidad y enviar un sticker diferente para cada nivel