Trucchi per l'elaborazione delle metriche in Kapacitor

Molto probabilmente oggi nessuno si chiede perché sia ​​necessario raccogliere metriche di servizio. Il prossimo passo logico è impostare un avviso per le metriche raccolte, che notificherà eventuali deviazioni nei dati nei canali a te convenienti (posta, Slack, Telegram). Nel servizio di prenotazione alberghiera online Ostrovok.ru tutte le metriche dei nostri servizi vengono riversate in InfluxDB e visualizzate in Grafana, dove vengono configurati anche gli avvisi di base. Per compiti come "devi calcolare qualcosa e confrontarlo", utilizziamo Kapacitor.

Trucchi per l'elaborazione delle metriche in Kapacitor
Kapacitor fa parte dello stack TICK in grado di elaborare i parametri da InfluxDB. Può collegare diverse misurazioni insieme (unire), calcolare qualcosa di utile dai dati ricevuti, riscrivere il risultato su InfluxDB, inviare un avviso a Slack/Telegram/mail.

L'intero stack è interessante e dettagliato documentazione, ma ci saranno sempre cose utili che non sono esplicitamente indicate nei manuali. In questo articolo ho deciso di raccogliere una serie di suggerimenti utili e non ovvi (è descritta la sintassi di base di TICKscipt qui) e mostrare come possono essere applicati utilizzando un esempio di risoluzione di uno dei nostri problemi.

Andiamo!

float & int, errori di calcolo

Un problema assolutamente standard, risolto attraverso le caste:

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

Usando default()

Se un tag/campo non viene compilato si verificheranno errori di calcolo:

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

compila join (interno vs esterno)

Per impostazione predefinita, l'unione scarterà i punti in cui non sono presenti dati (interni).
Con fill('null'), verrà eseguito un outside join, dopodiché dovrai eseguire un default() e riempire i valori vuoti:

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

C'è ancora una sfumatura qui. Nell'esempio sopra, se una delle serie (res1 o res2) è vuota, anche la serie risultante (dati) sarà vuota. Ci sono diversi ticket su questo argomento su Github (1633, 1871, 6967) – stiamo aspettando soluzioni e soffriamo un po’.

Utilizzo delle condizioni nei calcoli (se in lambda)

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

Ultimi cinque minuti dalla pipeline per il periodo

Ad esempio è necessario confrontare i valori degli ultimi cinque minuti con quelli della settimana precedente. Puoi prendere due batch di dati in due batch separati o estrarre parte dei dati da un periodo più ampio:

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

Un'alternativa per gli ultimi cinque minuti sarebbe quella di utilizzare un BarrierNode, che interrompe i dati prima del tempo specificato:

|barrier()
        .period(5m)

Esempi di utilizzo dei modelli Go nel messaggio

I modelli corrispondono al formato del pacchetto testo.modelloDi seguito sono riportati alcuni enigmi incontrati frequentemente.

se altro

Mettiamo le cose in ordine e non stimoliamo ancora una volta le persone con il testo:

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

Due cifre dopo la virgola nel messaggio

Migliorare la leggibilità del messaggio:

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

Espansione delle variabili nel messaggio

Mostriamo più informazioni nel messaggio per rispondere alla domanda "Perché sta urlando"?

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

Identificatore di avviso univoco

Questa è una cosa necessaria quando nei dati è presente più di un gruppo, altrimenti verrà generato un solo avviso:

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

Gestore personalizzato

L'ampio elenco di gestori include exec, che ti consente di eseguire il tuo script con i parametri passati (stdin): creatività e niente di più!

Una delle nostre usanze è un piccolo script Python per l'invio di notifiche a Slack.
Inizialmente volevamo inviare in un messaggio un'immagine di grafana protetta da autorizzazione. Successivamente, scrivi OK nel thread relativo all'avviso precedente dello stesso gruppo e non come messaggio separato. Un po' più tardi: aggiungi al messaggio l'errore più comune negli ultimi X minuti.

Un argomento separato è la comunicazione con altri servizi e qualsiasi azione avviata da un avviso (solo se il monitoraggio funziona sufficientemente bene).
Un esempio di descrizione del gestore, dove slack_handler.py è il nostro script autoprodotto:

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

Come eseguire il debug?

Opzione con output di registro

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

Guarda (cli): kapacitor -url host-o-ip:9092 registra lvl=errore

Opzione con httpOut

Mostra i dati nella pipeline corrente:

|httpOut('something')

Guarda (ottieni): host-o-ip:9092/kapacitor/v1/tasks/nome_attività/qualcosa

Schema di esecuzione

  • Ogni attività restituisce un albero di esecuzione con numeri utili nel formato grafico.
  • Prendi un blocco punto.
  • Incollalo nel visualizzatore, Godere.

Dove altro puoi trovare un rastrello?

timestamp in influxdb al writeback

Ad esempio, impostiamo un avviso per la somma delle richieste all'ora (groupBy(1h)) e vogliamo registrare l'avviso che si è verificato in influxdb (per mostrare magnificamente il fatto del problema sul grafico in grafana).

influxDBOut() scriverà il valore temporale dall'avviso nel timestamp; di conseguenza, il punto sul grafico verrà scritto prima/dopo l'arrivo dell'avviso.

Quando è richiesta precisione: risolviamo questo problema chiamando un gestore personalizzato, che scriverà i dati su influxdb con il timestamp corrente.

finestra mobile, compilazione e distribuzione

All'avvio, kapacitor può caricare attività, modelli e gestori dalla directory specificata nella configurazione nel blocco [load].

Per creare correttamente un'attività, sono necessarie le seguenti cose:

  1. Nome file: espanso in ID/nome script
  2. Tipo: flusso/batch
  3. dbrp – parola chiave per indicare in quale database + policy viene eseguito lo script (dbrp “fornitore.”“autogen”)

Se alcune attività batch non contengono una riga con dbrp, l'intero servizio rifiuterà di avviarsi e ne scriverà onestamente nel registro.

In cronografo invece questa riga non dovrebbe esistere, non viene accettata dall'interfaccia e genera un errore.

Hack durante la creazione di un contenitore: Dockerfile esce con -1 se sono presenti righe con //.+dbrp, che ti permetteranno di capire immediatamente il motivo dell'errore durante l'assemblaggio della build.

unisciti uno a molti

Attività di esempio: devi prendere il 95° percentile del tempo di funzionamento del servizio per una settimana, confrontare ogni minuto degli ultimi 10 con questo valore.

Non è possibile eseguire un'unione uno-a-molti, ultimo/media/mediana su un gruppo di punti trasforma il nodo in un flusso, verrà restituito l'errore "impossibile aggiungere bordi secondari non corrispondenti: batch -> flusso".

Anche il risultato di un batch, come variabile in un'espressione lambda, non viene sostituito.

Esiste un'opzione per salvare i numeri necessari dal primo batch in un file tramite udf e caricare questo file tramite sideload.

Cosa abbiamo risolto con questo?

Abbiamo circa 100 fornitori alberghieri, ognuno di loro può avere diversi collegamenti, chiamiamolo un canale. Esistono circa 300 di questi canali, ciascuno dei quali può cadere. Di tutte le metriche registrate, monitoreremo il tasso di errore (richieste ed errori).

Perché non grafana?

Gli avvisi di errore configurati in Grafana presentano diversi svantaggi. Alcuni sono fondamentali, altri davanti ai quali puoi chiudere gli occhi, a seconda della situazione.

Grafana non sa come calcolare tra misurazioni + avvisi, ma abbiamo bisogno di una tariffa (richieste-errori)/richieste.

Gli errori sembrano brutti:

Trucchi per l'elaborazione delle metriche in Kapacitor

E meno male se visto con richieste accolte:

Trucchi per l'elaborazione delle metriche in Kapacitor

Ok, possiamo precalcolare la tariffa nel servizio prima di grafana e in alcuni casi funzionerà. Ma non nel nostro, perché... per ogni canale il proprio rapporto è considerato “normale”, e gli alert funzionano secondo valori statici (li cerchiamo con gli occhi, li cambiamo se ci sono alert frequenti).

Questi sono esempi di “normale” per diversi canali:

Trucchi per l'elaborazione delle metriche in Kapacitor

Trucchi per l'elaborazione delle metriche in Kapacitor

Ignoriamo il punto precedente e assumiamo che il quadro “normale” sia simile per tutti i fornitori. Adesso va tutto bene e possiamo cavarcela con gli avvisi in grafana?
Possiamo, ma in realtà non vogliamo, perché dobbiamo scegliere una delle opzioni:
a) creare molti grafici per ciascun canale separatamente (e accompagnarli faticosamente)
b) lasciare un grafico con tutti i canali (e perdersi tra linee colorate e avvisi personalizzati)

Trucchi per l'elaborazione delle metriche in Kapacitor

Come hai fatto?

Ancora una volta, c'è un buon esempio iniziale nella documentazione (Calcolo dei tassi tra le serie unite), può essere sbirciato o preso come base in problemi simili.

Cosa abbiamo fatto alla fine:

  • unisci due serie in poche ore, raggruppandole per canali;
  • compilare la serie per gruppi se non c'erano dati;
  • confrontare la mediana degli ultimi 10 minuti con i dati precedenti;
  • urliamo se troviamo qualcosa;
  • scriviamo le tariffe calcolate e gli avvisi che si sono verificati in influxdb;
  • invia un messaggio utile a slack.

Secondo me, siamo riusciti a ottenere tutto ciò che volevamo ottenere alla fine (e anche qualcosa in più con i gestori personalizzati) nel modo più bello possibile.

Puoi guardare github.com esempio di codice и circuito minimo (graphviz) la sceneggiatura risultante.

Un esempio del codice risultante:

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)

E qual è la conclusione?

Kapacitor è ottimo nell'eseguire avvisi di monitoraggio con una serie di raggruppamenti, eseguire calcoli aggiuntivi basati su metriche già registrate, eseguire azioni personalizzate ed eseguire script (udf).

La barriera all'ingresso non è molto alta: provalo se la grafana o altri strumenti non soddisfano pienamente i tuoi desideri.

Fonte: habr.com

Aggiungi un commento