Trucos de procesamiento métrico en Kapacitor

Lo más probable es que hoy nadie tenga una pregunta de por qué necesitan recopilar métricas de servicio. El siguiente paso lógico es configurar la alerta para las métricas recopiladas, que le notificará sobre cualquier desviación en los datos en los canales convenientes para usted (correo, Slack, Telegram). En el servicio de reserva en línea de los hoteles Ostrovok.ru , todas las métricas de nuestros servicios se vierten en InfluxDB y se muestran en Grafana, allí también se establece una alerta básica. Para tareas como "necesita calcular algo y compararlo", utilizamos Kapacitor.


Kapacitor es parte de la pila TICK que puede manejar métricas de InfluxDB. Puede conectar varias dimensiones entre sí (unirse), calcular algo útil a partir de los datos recibidos, escribir el resultado en InfluxDB, enviar una alerta a Slack / Telegram / mail.

Toda la pila tiene documentación interesante y detallada, pero siempre hay cosas útiles que no se especifican explícitamente en los manuales. En este artículo, decidí recopilar una serie de consejos útiles y no obvios ( aquí se describe la sintaxis básica de TICKscipt) y mostrar cómo se pueden aplicar, usando un ejemplo para resolver uno de nuestros problemas.

Vamos!

float & int, errores de cálculo


Problema absolutamente estándar, se resuelve a través de castas:

var alert_float = 5.0 var alert_int = 10 data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int")) 

Usando default ()


Si la etiqueta / campo no está llena, se producirán errores en los cálculos:

 |default() .tag('status', 'empty') .field('value', 0) 

rellenar unirse (interior vs exterior)


De manera predeterminada, join soltará puntos donde no hay datos (internos).
Con fill ('null'), se ejecutará la unión externa, después de lo cual debe hacer default () y completar los valores vacíos:

 var data = res1 |join(res2) .as('res1', 'res2) .fill('null') |default() .field('res1.value', 0.0) .field('res2.value', 100.0) 

Todavía hay un matiz. Si en el ejemplo anterior una de las series (res1 o res2) está vacía, la serie final (datos) también estará vacía. Hay varias entradas sobre este tema en el github ( 1633 , 1871 , 6967 ): estamos esperando soluciones y sufrimos un poco.

Uso de condiciones en los cálculos (si está en lambda)


 |eval(lambda: if("value" > 0, true, false) 

Los últimos cinco minutos desde la tubería durante el período


Por ejemplo, debe comparar los valores de los últimos cinco minutos con la semana anterior. Puede tomar dos paquetes de datos con dos lotes separados o extraer parte de los datos de un período mayor:

  |where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m) 

Una alternativa para los últimos cinco minutos podría ser usar el nodo BarrierNode, que corta los datos antes del tiempo especificado:

 |barrier() .period(5m) 

Ejemplos de uso de patrones de Go'sh en el mensaje


Las plantillas corresponden al formato del paquete text.template , a continuación se presentan algunas tareas comunes.

si más


Ponemos las cosas en orden, no activamos a las personas con el texto una vez más:

 |alert() ... .message( '{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}' ) 

Dos decimales en el mensaje


Mejora de la legibilidad del mensaje:

 |alert() ... .message( 'now value is {{ index .Fields "value" | printf "%0.2f" }}' ) 

Expandiendo variables en mensaje


Mostramos más información en el mensaje para responder la pregunta "¿Por qué está gritando?"

 var warnAlert = 10 |alert() ... .message( 'Today value less then '+string(warnAlert)+'%' ) 

Identificador único de alerta


Lo correcto cuando hay más de un grupo en los datos, de lo contrario solo se generará una alerta:

 |alert() ... .id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}') 

Manejador personalizado


La gran lista de controladores tiene exec, que le permite ejecutar su script con los parámetros pasados ​​(stdin): ¡creatividad y más!

Una de nuestras herramientas personalizadas es un pequeño script de Python para enviar notificaciones a Slack.
Primero, queríamos enviar una imagen de una grafana protegida por autorización en el mensaje. Después: escriba OK en el hilo en la alerta anterior del mismo grupo, y no como un mensaje separado. Un poco más tarde: para agregar el mensaje de error más común en los últimos X minutos.

Un tema separado es la comunicación con otros servicios y cualquier acción iniciada por una alerta (solo si su monitoreo funciona lo suficientemente bien).
Un ejemplo de una descripción de un controlador, donde slack_handler.py es nuestro script auto-escrito:

 topic: slack_graph id: slack_graph.alert match: level() != INFO AND changed() == TRUE kind: exec options: prog: /sbin/slack_handler.py args: ["-c", "CHANNELID", "--graph", "--search"] 

¿Cómo debutar?


Opción de salida de registro


 |log() .level("error") .prefix("something") 

Watch (cli): kapacitor -url host-or-ip : 9092 logs lvl = error

Variante con httpOut


Muestra datos en la tubería actual:

 |httpOut('something') 

Watch (get): host-or-ip : 9092 / kapacitor / v1 / task / task_name / something

Esquema de ejecución


  • Cada tarea devuelve un árbol de ejecución con números útiles en formato graphviz .
  • Toma el bloque de puntos .
  • Nos insertamos en el visor, disfrutamos .


¿Dónde más puedo conseguir un rastrillo?


marca de tiempo de influxdb en reescritura


Por ejemplo, configuramos una alerta para la suma de solicitudes por hora (groupBy (1h)) y queremos registrar el incidente en influxdb (para mostrar maravillosamente el hecho de un problema en el gráfico en grafana).

influxDBOut () escribirá el valor de tiempo de la alerta en la marca de tiempo, respectivamente, el punto en el gráfico se registrará antes / después de la alerta.

Cuando se requiere precisión: evitamos este problema llamando a un controlador personalizado, que escribirá datos para influxdb con la marca de tiempo actual.

acoplador, construir y desplegar


Al inicio, kapacitor puede cargar tareas, plantillas y manejadores desde el directorio especificado en la configuración en el bloque [cargar].

Para crear una tarea correctamente, se necesitan las siguientes cosas:

  1. Nombre de archivo: se expande al nombre de id / script
  2. Tipo - flujo / lote
  3. dbrp: palabra clave para indicar en qué base de datos + política funciona el script (dbrp "proveedor". "autogen")

Si no hay una línea con dbrp en alguna tarea por lotes, todo el servicio se negará a comenzar y honestamente escribirá sobre ello en el registro.

En chronograf, por el contrario, esta línea no debe ser, no se acepta a través de la interfaz y arroja un error.

Hackear al compilar el contenedor: Dockerfile sale con -1 si hay líneas con //.+dbrp, que comprenderá de inmediato el motivo del archivo al compilar la compilación.

une uno a muchos


Tarea de ejemplo: debe tomar el percentil 95 del tiempo de funcionamiento del servicio por semana, comparar cada minuto de los últimos 10 con este valor.

No puede unir uno a muchos, la última / media / mediana por grupo de puntos convierte el nodo en secuencia, el error "no puede agregar bordes secundarios no coincidentes: lote -> secuencia" volverá.

El resultado del lote, como una variable en una expresión lambda, tampoco se sustituye.

Hay una opción para guardar los números necesarios del primer lote en un archivo a través de udf y cargar este archivo a través de la carga lateral.

¿Qué decidimos?


Tenemos alrededor de 100 proveedores de hoteles, cada uno de ellos puede tener varias conexiones, llamémoslo canal. Hay aproximadamente 300 de estos canales; cada uno de los canales puede caerse. De todas las métricas registradas, supervisaremos la tasa de error (solicitudes y errores).

¿Por qué no grafana?


Las alertas de error configuradas en grafan tienen varias desventajas. Algunos críticos, otros pueden cerrar los ojos, dependiendo de la situación.

Grafana no sabe cómo calcular entre dimensiones + alerta, pero necesitamos una tasa (peticiones-errores) / solicitudes.

Los errores parecen viciosos:



Y menos vicioso cuando se ve con solicitudes exitosas:



De acuerdo, podemos calcular previamente la tasa en el servicio antes de grafana, y en algunos casos lo hará. Pero no la nuestra, porque para cada canal, su relación se considera "normal" y las alertas funcionan de acuerdo con valores estáticos (miramos con los ojos, cambiamos, si a menudo estamos alertas).

Estos son ejemplos de "normal" para diferentes canales:





Descuidamos el párrafo anterior y asumimos que todos los proveedores tienen una imagen "normal". Ahora todo está bien, ¿y podemos salir con alertas en grafana?
Podemos, pero realmente no queremos, porque debemos elegir una de las opciones:
a) hacer muchos gráficos para cada canal por separado (y acompañarlos dolorosamente)
b) deje un gráfico con todos los canales (y piérdase en líneas coloridas y alertas sintonizadas)



Como lo hiciste


Una vez más, la documentación tiene un buen ejemplo inicial ( Calcular tasas a través de series unidas ), puede echar un vistazo o tomar como base en tareas similares.

¿Qué hiciste como resultado?

  • une dos episodios en pocas horas, agrupando por canal;
  • complete la serie por grupos, si no hubo datos;
  • compare la mediana de los últimos 10 minutos con los datos anteriores;
  • gritamos si encontramos algo;
  • escribir tasas calculadas y alertas ocurridas en influxdb;
  • envía un mensaje útil a slack.

En mi opinión, logramos todo lo bellamente posible todo lo que nos gustaría obtener en la salida (e incluso un poco más con controladores personalizados).

En github.com puede ver el código de muestra y el diagrama mínimo (graphviz) del script resultante.

Ejemplo del código resultante:
 dbrp "supplier"."autogen" var name = 'requests.rate' var grafana_dash = 'pczpmYZWU/mydashboard' var grafana_panel = '26' var period = 8h var todayPeriod = 10m var every = 1m var warnAlert = 15 var warnReset = 5 var reqQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."requests"' var errQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."errors"' var prevErr = batch |query(errQuery) .period(period) .every(every) .groupBy(1m, 'channel', 'supplier') var prevReq = batch |query(reqQuery) .period(period) .every(every) .groupBy(1m, 'channel', 'supplier') var rates = prevReq |join(prevErr) .as('req', 'err') .tolerance(1m) .fill('null') //   ,     |default() .field('err.value', 0.0) .field('req.value', 0.0) // if  lambda:  ,     |eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0)) .as('rate') //      rates |influxDBOut() .quiet() .create() .database('kapacitor') .retentionPolicy('autogen') .measurement('rates') //     10 ,   var todayRate = rates |where(lambda: duration((unixNano(now()) - unixNano("time")) / 1000, 1u) < todayPeriod) |median('rate') .as('median') var prevRate = rates |median('rate') .as('median') var joined = todayRate |join(prevRate) .as('today', 'prev') |httpOut('join') var trigger = joined |alert() .warn(lambda: ("prev.median" - "today.median") > warnAlert) .warnReset(lambda: ("prev.median" - "today.median") < warnReset) .flapping(0.25, 0.5) .stateChangesOnly() //   message      .message( '{{ .Level }}: {{ index .Tags "channel" }} err/req ratio ({{ index .Tags "supplier" }}) {{ if eq .Level "OK" }}It is ok now{{ else }} '+string(todayPeriod)+' median is {{ index .Fields "today.median" | printf "%0.2f" }}%, by previous '+string(period)+' is {{ index .Fields "prev.median" | printf "%0.2f" }}%{{ end }} http://grafana.ostrovok.in/d/'+string(grafana_dash)+ '?var-supplier={{ index .Tags "supplier" }}&var-channel={{ index .Tags "channel" }}&panelId='+string(grafana_panel)+'&fullscreen&tz=UTC%2B03%3A00' ) .id('{{ index .Tags "name" }}/{{ index .Tags "channel" }}') .levelTag('level') .messageField('message') .durationField('duration') .topic('slack_graph') // "today.median"   "value",        (keep) trigger |eval(lambda: "today.median") .as('value') .keep() |influxDBOut() .quiet() .create() .database('kapacitor') .retentionPolicy('autogen') .measurement('alerts') .tag('alertName', name) 


¿Cuál es la conclusión?


Kapacitor es muy bueno para monitorear alertas con un grupo de grupos, realizar cálculos adicionales en métricas ya registradas, realizar acciones personalizadas y ejecutar scripts (udf).

El umbral de entrada no es muy alto: pruébelo si la grafana u otras herramientas no satisfacen completamente su lista de deseos.

Source: https://habr.com/ru/post/478702/


All Articles