Trucs per processar mètriques a Kapacitor

Molt probablement, avui ningú es pregunta per què és necessari recollir mètriques de servei. El següent pas lògic és configurar una alerta per a les mètriques recollides, que notificarà qualsevol desviació de les dades als canals que us convinguin (correu, Slack, Telegram). Al servei online de reserves d'hotels Ostrovok.ru totes les mètriques dels nostres serveis s'aboquen a InfluxDB i es mostren a Grafana, i també s'hi configuren les alertes bàsiques. Per a tasques com "cal calcular alguna cosa i comparar-la", utilitzem Kapacitor.

Trucs per processar mètriques a Kapacitor
Kapacitor forma part de la pila TICK que pot processar mètriques d'InfluxDB. Pot connectar diverses mesures juntes (unir-se), calcular alguna cosa útil a partir de les dades rebudes, escriure el resultat a InfluxDB, enviar una alerta a Slack/Telegram/mail.

Tota la pila és fantàstica i detallada documentació, però sempre hi haurà coses útils que no s'indiquen explícitament als manuals. En aquest article, vaig decidir recollir una sèrie de consells tan útils i no òbvids (es descriu la sintaxi bàsica de TICKscipt aquí) i mostrar com es poden aplicar utilitzant un exemple de resolució d'un dels nostres problemes.

Anem!

float & int, errors de càlcul

Un problema absolutament estàndard, resolt mitjançant castes:

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

Ús per defecte ()

Si no s'omple cap etiqueta/camp, es produiran errors de càlcul:

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

ompliu unió (interior vs exterior)

Per defecte, join descartarà els punts on no hi hagi dades (interiors).
Amb fill('null'), es realitzarà una unió externa, després de la qual cal fer una () predeterminada i omplir els valors buits:

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

Aquí encara hi ha un matís. A l'exemple anterior, si una de les sèries (res1 o res2) està buida, la sèrie (dades) resultant també estarà buida. Hi ha diverses entrades sobre aquest tema a Github (1633, 1871, 6967) – estem esperant solucions i patint una mica.

Ús de condicions en càlculs (si és lambda)

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

Últims cinc minuts del gasoducte per al període

Per exemple, cal comparar els valors dels últims cinc minuts amb la setmana anterior. Podeu agafar dos lots de dades en dos lots separats o extreure part de les dades d'un període més gran:

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

Una alternativa durant els últims cinc minuts seria utilitzar un BarrierNode, que talla les dades abans de l'hora especificada:

|barrier()
        .period(5m)

Exemples d'ús de plantilles Go al missatge

Les plantilles corresponen al format del paquet text.plantillaA continuació es mostren alguns trencaclosques que es troben amb freqüència.

si una altra cosa

Posem les coses en ordre i no tornem a activar la gent amb text:

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

Dos dígits després del punt decimal al missatge

Millora de la llegibilitat del missatge:

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

Ampliació de variables al missatge

Mostrem més informació al missatge per respondre a la pregunta "Per què crida"?

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

Identificador únic d'alerta

Això és necessari quan hi ha més d'un grup a les dades, en cas contrari només es generarà una alerta:

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

Gestor personalitzat

La gran llista de controladors inclou exec, que us permet executar el vostre script amb els paràmetres passats (stdin): creativitat i res més!

Un dels nostres costums és un petit script de Python per enviar notificacions a Slack.
Al principi, volíem enviar una imatge de grafana protegida per autorització en un missatge. Després, escriviu OK al fil de l'alerta anterior del mateix grup, i no com a missatge independent. Una mica més tard: afegiu al missatge l'error més comú dels últims X minuts.

Un tema a part és la comunicació amb altres serveis i qualsevol acció iniciada per una alerta (només si el vostre seguiment funciona prou bé).
Un exemple de descripció del controlador, on slack_handler.py és el nostre script escrit per nosaltres mateixos:

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"]

Com depurar?

Opció amb sortida de registre

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

Watch (cli): kapacitor -url host-o-ip:9092 registres lvl=error

Opció amb httpOut

Mostra les dades del pipeline actual:

|httpOut('something')

Mira (obté): host-o-ip:9092/kapacitor/v1/tasks/task_name/something

Diagrama d'execució

  • Cada tasca retorna un arbre d'execució amb números útils en el format GraphViz.
  • Agafa un bloc punt.
  • Enganxeu-lo al visor, gaudir.

On més pots aconseguir un rastell?

marca de temps a influxdb en reescriptura

Per exemple, configurem una alerta per a la suma de sol·licituds per hora (groupBy(1h)) i volem registrar l'alerta que es va produir a influxdb (per mostrar de manera bella el fet del problema al gràfic de grafana).

influxDBOut() escriurà el valor de temps de l'alerta a la marca de temps en conseqüència, el punt del gràfic s'escriurà abans/després de l'arribada de l'alerta.

Quan es requereix precisió: solucionem aquest problema trucant a un gestor personalitzat, que escriurà dades a influxdb amb la marca de temps actual.

docker, compilació i desplegament

A l'inici, kapacitor pot carregar tasques, plantilles i controladors des del directori especificat a la configuració al bloc [load].

Per crear correctament una tasca, necessiteu les coses següents:

  1. Nom del fitxer: ampliat a l'identificador/nom de l'script
  2. Tipus: flux/lot
  3. dbrp: paraula clau per indicar en quina base de dades + política s'executa l'script (dbrp "proveïdor". "autogen")

Si alguna tasca per lots no conté una línia amb dbrp, tot el servei es negarà a començar i ho escriurà honestament al registre.

En cronògraf, al contrari, aquesta línia no hauria d'existir, no s'accepta a través de la interfície i genera un error.

Hack quan es construeix un contenidor: Dockerfile surt amb -1 si hi ha línies amb //.+dbrp, la qual cosa us permetrà comprendre immediatament el motiu de la fallada en muntar la compilació.

uneix un a molts

Tasca d'exemple: heu de prendre el percentil 95 del temps de funcionament del servei durant una setmana, comparar cada minut dels darrers 10 amb aquest valor.

No podeu fer una unió d'un a molts, la darrera/mitjana/mediana sobre un grup de punts converteix el node en un flux, es retornarà l'error "no es poden afegir vores no coincidents: lot -> flux".

El resultat d'un lot, com a variable en una expressió lambda, tampoc es substitueix.

Hi ha una opció per desar els números necessaris del primer lot a un fitxer mitjançant udf i carregar aquest fitxer mitjançant càrrega lateral.

Què hem resolt amb això?

Tenim uns 100 proveïdors hotelers, cadascun d'ells pot tenir diverses connexions, diguem-ne canal. Hi ha aproximadament 300 d'aquests canals, cadascun dels canals pot caure. De totes les mètriques registrades, controlarem la taxa d'error (sol·licituds i errors).

Per què no grafana?

Les alertes d'error configurades a Grafana tenen diversos inconvenients. Alguns són crítics, d'altres pots tancar els ulls, segons la situació.

Grafana no sap calcular entre mesures + alerta, però necessitem una tarifa (sol·licituds-errors)/sol·licituds.

Els errors semblen desagradables:

Trucs per processar mètriques a Kapacitor

I menys mal quan es veu amb sol·licituds reeixides:

Trucs per processar mètriques a Kapacitor

D'acord, podem calcular prèviament la tarifa en el servei abans de la grafana, i en alguns casos això funcionarà. Però no a la nostra, perquè... per a cada canal la seva pròpia proporció es considera "normal", i les alertes funcionen segons valors estàtics (les busquem amb els ulls, les canviem si hi ha alertes freqüents).

Aquests són exemples de "normals" per a diferents canals:

Trucs per processar mètriques a Kapacitor

Trucs per processar mètriques a Kapacitor

Ignorem el punt anterior i assumim que la imatge "normal" és similar per a tots els proveïdors. Ara tot està bé, i ens podem arreglar amb les alertes a grafana?
Podem, però realment no volem, perquè hem de triar una de les opcions:
a) fes molts gràfics per a cada canal per separat (i acompanya-los dolorosamente)
b) deixeu un gràfic amb tots els canals (i perdeu-vos entre les línies de colors i les alertes personalitzades)

Trucs per processar mètriques a Kapacitor

Com ho vas fer?

De nou, hi ha un bon exemple inicial a la documentació (Càlcul de tarifes entre sèries unides), es pot mirar o prendre com a base en problemes similars.

El que vam fer al final:

  • uneix dues sèries en poques hores, agrupant per canals;
  • ompliu la sèrie per grup si no hi havia dades;
  • compareu la mediana dels darrers 10 minuts amb les dades anteriors;
  • cridem si trobem alguna cosa;
  • escrivim les tarifes calculades i les alertes que es van produir a influxdb;
  • envia un missatge útil a Slack.

Al meu entendre, hem aconseguit aconseguir tot el que volíem aconseguir al final (i fins i tot una mica més amb manipuladors personalitzats) de la manera més bonica possible.

Podeu mirar a github.com exemple de codi и circuit mínim (graphviz) el guió resultant.

Un exemple del codi resultant:

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)

Quina és la conclusió?

Kapacitor és excel·lent per realitzar alertes de seguiment amb un munt d'agrupaments, realitzar càlculs addicionals basats en mètriques ja registrades, realitzar accions personalitzades i executar scripts (udf).

La barrera d'entrada no és molt alta: proveu-ho si la grafana o altres eines no satisfan completament els vostres desitjos.

Font: www.habr.com

Afegeix comentari