Tricks til at behandle metrics i Kapacitor

Mest sandsynligt er der i dag ingen, der spørger, hvorfor det er nødvendigt at indsamle servicemålinger. Det næste logiske trin er at konfigurere en advarsel for de indsamlede metrics, som giver besked om eventuelle afvigelser i dataene i kanaler, der er praktiske for dig (mail, Slack, Telegram). I online hotelbookingstjenesten Ostrovok.ru alle metrics for vores tjenester hældes ind i InfluxDB og vises i Grafana, og der er også konfigureret grundlæggende alarmering. Til opgaver som "du skal beregne noget og sammenligne med det", bruger vi Kapacitor.

Tricks til at behandle metrics i Kapacitor
Kapacitor er en del af TICK-stakken, der kan behandle metrics fra InfluxDB. Den kan koble flere målinger sammen (join), beregne noget nyttigt ud fra de modtagne data, skrive resultatet tilbage til InfluxDB, sende en advarsel til Slack/Telegram/mail.

Hele stakken er cool og detaljeret dokumentation, men der vil altid være nyttige ting, som ikke er eksplicit angivet i manualerne. I denne artikel besluttede jeg at samle en række sådanne nyttige, ikke-oplagte tips (den grundlæggende syntaks for TICKscipt er beskrevet her) og vis, hvordan de kan anvendes ved hjælp af et eksempel på løsning af et af vores problemer.

Lad os gå!

float & int, regnefejl

Et absolut standardproblem, løst gennem kaster:

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

Bruger standard()

Hvis et tag/felt ikke er udfyldt, vil der opstå beregningsfejl:

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

udfyld join (indre vs ydre)

Som standard vil join kassere punkter, hvor der ikke er nogen data (indre).
Med fill('null') udføres en outer join, hvorefter du skal lave en default() og udfylde de tomme værdier:

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

Der er stadig en nuance her. I eksemplet ovenfor, hvis en af ​​serierne (res1 eller res2) er tom, vil den resulterende serie (data) også være tom. Der er flere billetter om dette emne på Github (1633, 1871, 6967) – vi venter på rettelser og lider lidt.

Brug af betingelser i beregninger (hvis i lambda)

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

Sidste fem minutter fra rørledningen for perioden

For eksempel skal du sammenligne værdierne for de sidste fem minutter med den foregående uge. Du kan tage to batches af data i to separate batches eller udtrække en del af dataene fra en større periode:

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

Et alternativ for de sidste fem minutter ville være at bruge en BarrierNode, som afskærer data før det angivne tidspunkt:

|barrier()
        .period(5m)

Eksempler på brug af Go-skabeloner i besked

Skabeloner svarer til formatet fra pakken tekst.skabelonNedenfor er nogle ofte stødte på gåder.

hvis ellers

Vi bringer tingene i orden og trigger ikke folk med tekst igen:

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

To cifre efter decimaltegnet i meddelelsen

Forbedring af meddelelsens læsbarhed:

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

Udvide variabler i besked

Vi viser mere information i beskeden for at besvare spørgsmålet "Hvorfor råber det"?

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

Unik advarsels-id

Dette er en nødvendig ting, når der er mere end én gruppe i dataene, ellers vil der kun blive genereret én advarsel:

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

Brugerdefinerede handlere

Den store liste af handlere inkluderer exec, som giver dig mulighed for at udføre dit script med de beståede parametre (stdin) - kreativitet og intet mere!

En af vores skikke er et lille Python-script til at sende meddelelser til slack.
Først ønskede vi at sende et autorisationsbeskyttet grafana-billede i en besked. Skriv derefter OK i tråden til den forrige alarm fra samme gruppe, og ikke som en separat besked. Lidt senere - føj til beskeden den mest almindelige fejl i de sidste X minutter.

Et særskilt emne er kommunikation med andre tjenester og alle handlinger, der iværksættes af en advarsel (kun hvis din overvågning fungerer godt nok).
Et eksempel på en handlerbeskrivelse, hvor slack_handler.py er vores selvskrevne script:

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 fejlfinder man?

Mulighed med logudgang

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

Watch (cli): kapacitor -url vært-eller-ip:9092 logger lvl=fejl

Mulighed med httpOut

Viser data i den aktuelle pipeline:

|httpOut('something')

Se (få): vært-eller-ip:9092/kapacitor/v1/tasks/task_name/noget

Udførelsesordning

Hvor ellers kan du få en rake?

tidsstempel i influxdb ved tilbageskrivning

For eksempel opsætter vi en advarsel for summen af ​​anmodninger i timen (groupBy(1h)) og ønsker at registrere den advarsel, der opstod i influxdb (for smukt at vise problemet på grafen i grafana).

influxDBOut() vil skrive tidsværdien fra advarslen til tidsstemplet; følgelig vil punktet på diagrammet blive skrevet tidligere/senere end advarslen ankom.

Når nøjagtighed er påkrævet: vi løser dette problem ved at kalde en brugerdefineret handler, som vil skrive data til influxdb med det aktuelle tidsstempel.

docker, build og implementering

Ved opstart kan kapacitor indlæse opgaver, skabeloner og handlere fra den mappe, der er angivet i konfigurationen i [load]-blokken.

For at oprette en opgave korrekt skal du bruge følgende ting:

  1. Filnavn – udvidet til script-id/navn
  2. Type – stream/batch
  3. dbrp – nøgleord for at angive, hvilken database + politik scriptet kører i (dbrp "leverandør." "autogen")

Hvis en batch-opgave ikke indeholder en linje med dbrp, vil hele tjenesten nægte at starte og vil ærligt skrive om det i loggen.

I chronograf burde denne linje tværtimod ikke eksistere; den accepteres ikke gennem grænsefladen og genererer en fejl.

Hack, når du bygger en container: Dockerfile afsluttes med -1, hvis der er linjer med //.+dbrp, hvilket giver dig mulighed for med det samme at forstå årsagen til fejlen, når du samler buildet.

slutte sig til en til mange

Eksempelopgave: du skal tage den 95. percentil af tjenestens driftstid i en uge, sammenligne hvert minut af de sidste 10 med denne værdi.

Du kan ikke lave en en-til-mange joinforbindelse, sidste/middelværdi/median over en gruppe af punkter forvandler noden til en strøm, fejlen "kan ikke tilføje underordnede uoverensstemmende kanter: batch -> stream" vil blive returneret.

Resultatet af en batch, som en variabel i et lambda-udtryk, erstattes heller ikke.

Der er mulighed for at gemme de nødvendige numre fra første batch til en fil via udf og indlæse denne fil via sideload.

Hvad løste vi med dette?

Vi har omkring 100 hotelleverandører, hver af dem kan have flere forbindelser, lad os kalde det en kanal. Der er cirka 300 af disse kanaler, hver af kanalerne kan falde af. Af alle de registrerede målinger vil vi overvåge fejlprocenten (anmodninger og fejl).

Hvorfor ikke grafana?

Fejlalarmer konfigureret i Grafana har flere ulemper. Nogle er kritiske, nogle kan du lukke øjnene for, alt efter situationen.

Grafana ved ikke hvordan man regner mellem målinger + alarmering, men vi har brug for en rate (requests-errors)/requests.

Fejlene ser grimme ud:

Tricks til at behandle metrics i Kapacitor

Og mindre ondt, når det ses med vellykkede anmodninger:

Tricks til at behandle metrics i Kapacitor

Okay, vi kan forudberegne satsen i tjenesten før grafana, og i nogle tilfælde vil dette virke. Men ikke i vores, fordi... for hver kanal betragtes dens eget forhold som "normalt", og alarmer fungerer i henhold til statiske værdier (vi leder efter dem med vores øjne, ændrer dem, hvis der er hyppige alarmer).

Disse er eksempler på "normal" for forskellige kanaler:

Tricks til at behandle metrics i Kapacitor

Tricks til at behandle metrics i Kapacitor

Vi ignorerer det foregående punkt og antager, at det "normale" billede er ens for alle leverandører. Nu er alt i orden, og vi kan klare os med alarmer i grafana?
Vi kan, men vi vil virkelig ikke, for vi skal vælge en af ​​mulighederne:
a) lav en masse grafer for hver kanal separat (og ledsage dem smerteligt)
b) efterlad et diagram med alle kanaler (og gå tabt i de farverige linjer og tilpassede advarsler)

Tricks til at behandle metrics i Kapacitor

Hvordan gjorde du det?

Igen er der et godt starteksempel i dokumentationen (Beregning af satser på tværs af samlede serier), kan kigges på eller tages som udgangspunkt i lignende problemer.

Hvad vi gjorde til sidst:

  • deltage i to serier på få timer, gruppering efter kanaler;
  • udfyld serien for gruppe, hvis der ikke var nogen data;
  • sammenligne medianen for de sidste 10 minutter med tidligere data;
  • vi råber, hvis vi finder noget;
  • vi skriver de beregnede rater og advarsler, der fandt sted i influxdb;
  • send en nyttig besked til slack.

Efter min mening lykkedes det os at opnå alt, hvad vi ønskede at få til sidst (og endda lidt mere med brugerdefinerede handlere) så smukt som muligt.

Du kan se på github.com kode eksempel и minimal kredsløb (graphviz) det resulterende script.

Et eksempel på den resulterende kode:

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)

Hvad er konklusionen?

Kapacitor er fantastisk til at udføre overvågnings-alarmer med en masse grupperinger, udføre yderligere beregninger baseret på allerede registrerede metrics, udføre tilpassede handlinger og køre scripts (udf).

Adgangsbarrieren er ikke særlig høj - prøv det, hvis grafana eller andre værktøjer ikke fuldt ud opfylder dine ønsker.

Kilde: www.habr.com

Tilføj en kommentar