Tips for behandling av metrikker i Kapacitor

Sannsynligvis er det ingen som lenger stiller spÞrsmÄl ved hvorfor det er nÞdvendig Ä samle mÄlinger av tjenester. Neste logiske steg er Ä sette opp varsling for de innsamlede mÄlingene, som vil varsle om eventuelle avvik i dataene til de kanalene du foretrekker (e-post, Slack, Telegram). I tjenesten for online hotellreservasjoner Ostrovok.ru renner alle mÄlinger av vÄre tjenester inn i InfluxDB og vises i Grafana, hvor det ogsÄ er satt opp grunnleggende varsling. For oppgaver hvor "man mÄ beregne noe og sammenligne med dette" bruker vi Kapacitor.

Tips for behandling av metrikker i Kapacitor
Kapacitor er en del av TICK-stakken, som kan behandle mÄlinger fra InfluxDB. Den kan koble sammen flere mÄlinger (join), beregne noe nytt fra de oppnÄdde dataene, skrive resultatet tilbake til InfluxDB, og sende varsler i Slack/Telegram/e-post.

Hele stakken har en kul og detaljer dokumentasjonen, men det vil alltid vÊre nyttige ting som ikke nÞdvendigvis er angitt eksplisitt i manualene. I denne artikkelen har jeg bestemt meg for Ä samle en rekke slike nyttige, men kanskje ikke Äpenbare rÄd (den grunnleggende syntaksen til TICKscipt er beskrevet her) og vise hvordan de kan brukes, med et eksempel pÄ en av vÄre smÄ oppgaver.

La oss gÄ!

float & int, beregningsfeil

Det er et helt standard problem som lĂžses ved Ă„ bruke typekonvertering:

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

Bruk av default()

Hvis tag/omrÄde ikke er fylt ut, oppstÄr det beregningsfeil:

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

fill i join (inner vs outer)

Som standard vil join kaste bort punkter der det ikke er data (inner).
Ved Ă„ bruke fill('null') vil det bli utfĂžrt en outer join, etterfulgt av default() for Ă„ fylle ut tomme verdier:

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

Det er imidlertid en nyanse her. Hvis en av seriene (res1 eller res2) er tom, vil ogsĂ„ den endelige serien (data) vĂŠre tom. Det er flere saker om dette pĂ„ GitHub (1633, 1871, 6967) – vi venter pĂ„ feilrettinger og lider litt.

Bruk av betingelser i beregninger (if i lambda)

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

De siste fem minuttene fra pipelinen for perioden

For eksempel, hvis du trenger Ă„ sammenligne verdiene for de siste fem minuttene med den forrige uken. Du kan ta to sett med data i separate batcher eller hente deler av dataene fra en stĂžrre periode:

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

Et alternativ for de siste fem minuttene kan vĂŠre Ă„ bruke BarrierNode-noden, som stopper dataene fĂžr angitt tid:

|barrier()
        .period(5m)

Eksempler pÄ bruk av Go-maler i meldinger

Maler fĂžlger formatet fra pakken text.template, her er noen vanlige oppgaver.

if-else

Vi organiserer, uten Ă„ trigge folk med unĂždvendig tekst:

|alert()
    ...
    .message(
        '{{ if eq .Level "OK" }}Det er greit nÄ{{ else }}Sjef, alt er Þdelagt{{end}}'
    )

To desimaler etter komma i meldingen

Vi forbedrer lesbarheten til meldingen:

|alert()
    ...
    .message(
        'nÄ verdien er {{ index .Fields "value" | printf "%0.2f" }}'
    )

Utdrag av variabler i meldingen

Vi viser mer informasjon i meldingen for Ä svare pÄ spÞrsmÄlet «Hvorfor skriker den?»:

var warnAlert = 10
  |alert()
    ...
    .message(
       'Dagens verdi er under '+string(warnAlert)+'%'
    )

Unik identifikator for varsler

En nyttig ting nÄr det er mer enn en gruppe i dataene, ellers vil det bare bli generert ett varsel:

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

Egendefinerte handlere

I den store listen over handlere finnes det en exec som lar deg kjþre ditt eget skript med sendte parametere (stdin) – bare kreativitet!

En av vÄre egendefinerte er et lite Python-skript for Ä sende varsler til Slack.
FĂžrst ville vi sende et bilde fra Grafana, beskyttet med autentisering, i meldingen. Deretter – skrive OK i trĂ„den til det forrige varselet fra den samme gruppen, i stedet for som en egen melding. Litt senere – legge til i meldingen den vanligste feilen de siste X minuttene.

Et eget emne – kobling til andre tjenester og eventuelle handlinger initiert av varsler (bare hvis overvĂ„kingen din fungerer bra).
Eksempel pÄ beskrivelse av en handler, hvor slack_handler.py er vÄrt egenutviklede skript:

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

Hvordan feilsĂžke?

Alternativ med output til logg

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

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

Alternativ med httpOut

Viser data i den nÄvÊrende pipelinen:

|httpOut('something')

Se (get): host-or-ip:9092\/kapacitor\/v1\/tasks\/task_name\/something

KjĂžringsskjema

  • Hver oppgave returnerer et kjĂžretrĂŠr med nyttige tall i formatet graphviz.
  • Vi tar en blokk dot.
  • Setter inn i viewer, nyter det.

Hvor annet kan man hente timestamp fra InfluxDB ved tilbakefĂžring

timestamp i influxdb ved tilbakefĂžring

For example, we set up an alert for the number of requests over an hour (groupBy(1h)) and want to record the triggered alert in influxdb (to visually show the existence of a problem on a graph in grafana).

influxDBOut() will record the time value from the alert, accordingly, the point on the graph will be recorded earlier/later than the alert was received.

When precision is required: we bypass this issue by calling a custom handler that will write data to influxdb with the current timestamp.

docker, build and deploy

Upon starting, kapacitor can load tasks, templates, and handlers from the directory specified in the config, in the [load] block.

To correctly create a task, the following things are needed:

  1. File name – expands to id/name of the script
  2. Type – stream/batch
  3. dbrp – a keyword to specify which database + policy the script operates in (dbrp "supplier"."autogen")

If any batch task lacks the dbrp line, the entire service will refuse to start and will write this honestly in the log.

In chronograf, on the contrary, this line should not be present; it is not accepted through the interface and will throw an error.

Build container hack: Dockerfile exits with -1 if there are lines with //.+dbrp, which will immediately let you understand the reason for the build failure.

join one to many

Example task: we need to take the 95th percentile of service response time over a week, comparing each minute from the last 10 with this value.

You cannot do a one-to-many join; last/mean/median over a group of points turns the node into a stream, returning the error "cannot add child mismatched edges: batch -> stream."

The result of the batch, as a variable in a lambda expression, is also not substituted.

There is a way to save the required numbers from the first batch to a file via udf and load this file through sideload.

What were we solving with this?

We have about 100 hotel suppliers, each of them may have several connections, which we will call a channel. There are about 300 of these channels, each of which may fail. Of all the recorded metrics, we will monitor the error rate (requests and errors).

Why not grafana?

Alerts for errors set up in grafana have several downsides. Some are critical, while others can be overlooked, depending on the situation.

Grafana does not handle calculations between measures + alerting, while we need the rate (requests-errors)/requests.

Errors look menacing:

Tips for behandling av metrikker i Kapacitor

And less menacing when viewed with successful requests:

Tips for behandling av metrikker i Kapacitor

OK, vi kan forhÄndsberegne raten i tjenesten fÞr Grafana, og i noen tilfeller vil dette vÊre tilstrekkelig. Men ikke i vÄrt tilfelle, fordi for hver kanal beregnes et «normalt» forhold, og alerter fungerer etter statiske verdier (vi ser med Þynene, endrer hvis det alerter ofte).

Dette er eksempler pÄ «normalt» for ulike kanaler:

Tips for behandling av metrikker i Kapacitor

Tips for behandling av metrikker i Kapacitor

Vi ser bort fra forrige punkt og antar at alle leverandÞrer har en «normal» situasjon som ligner. NÄ er alt bra, og kan vi klare oss med alerter i Grafana?
Vi kan, men det frister veldig lite, fordi vi mÄ velge en av alternativene:
a) lage mange grafer for hver kanal separat (og plage oss med Ă„ holde dem oppdatert)
b) beholde én graf med alle kanaler (og bli tapt i fargerike linjer og konfigurerte alerter)

Tips for behandling av metrikker i Kapacitor

Hvordan lĂžste vi dette?

Som fÞr, finnes det et godt starteksempel i dokumentasjonen (Calculating rates across joined series), som man kan se pÄ eller ta utgangspunkt i for lignende oppgaver.

Hva vi fikk til slutt:

  • join av to serier over flere timer, gruppering etter kanaler;
  • fyller serier etter grupper, hvis det ikke var noen data;
  • sammenligner medianen de siste 10 minuttene med tidligere data;
  • ropte ut hvis vi oppdaget noe;
  • skrev ut de beregnede ratene og oppstĂ„tte alerter i InfluxDB;
  • sendte en nyttig melding til Slack.

Etter min mening lyktes vi maks med alt vi Þnsket Ä oppnÄ (og kanskje litt mer med tilpassede hÄndterere).

PĂ„ github.com kan man se kodeeksempelet og den minimale skjemaet (graphviz) til den resulterende skriptet.

Eksempel pÄ den resulterende koden:

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')
 // fill in zero values if there were none
 |default()
 .field('err.value', 0.0)
 .field('req.value', 0.0)
 // if in lambda: calculate rate only if there were errors
 |eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0))
 .as('rate')

// write calculated values to influx
rates
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('rates')

// select data for the last 10 minutes, calculate median
var todayRate = rates
 |where(lambda: duration((unixNano(now()) - unixNano("time")) / 1000, 1u)  warnAlert)
 .warnReset(lambda: ("prev.median" - "today.median") < warnReset)
 .flapping(0.25, 0.5)
 .stateChangesOnly()
 // collect in message a link to the grafana dashboard chart
 .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=UTC0300'
 )
 .id('{{ index .Tags "name" }}\/{{ index .Tags "channel" }}')
 .levelTag('level')
 .messageField('message')
 .durationField('duration')
 .topic('slack_graph')

// duplicate "today.median" as "value", also write other fields of the alert to influx (keep)
trigger
 |eval(lambda: "today.median")
 .as('value')
 .keep()
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('alerts')
 .tag('alertName', name)

Og hva er resultatet?

Kapacitor gjÞr en fantastisk jobb med Ä utfÞre overvÄking og varsling med mange grupperinger, utfÞre tilleggskalkulasjoner pÄ allerede lagrede mÄlinger, utfÞre tilpassede handlinger og kjÞre skript (udf).

Inngangsbarrieren er ikke sĂ„ hĂžy – prĂžv det hvis Grafana eller andre verktĂžy ikke fullt ut oppfyller dine Ăžnsker.

Kilde: habr.com

KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KjĂžp pĂ„litelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster