Mest sandsynligt er der i dag ingen, der har noget spørgsmål om, hvorfor det er nødvendigt at indsamle servicemålinger. Det næste logiske trin er at opsætte advarsler for de indsamlede metrics, som vil give dig besked om eventuelle afvigelser i dataene via kanaler, der er praktiske for dig (mail, Slack, Telegram). I online hotelbookingstjenesten Alle metrics for vores tjenester streames til InfluxDB og vises i Grafana, hvor grundlæggende alarmering også er konfigureret. Til opgaver som "behov for at beregne noget og sammenligne det med dette" bruger vi Kapacitor.

Kapacitor er en del af TICK-stakken, som kan behandle metrics fra InfluxDB. Den kan forbinde flere dimensioner sammen (join), beregne noget nyttigt ud fra de modtagne data, skrive resultatet tilbage til InfluxDB, sende en advarsel til Slack/Telegram/mail.
Hele stakken har en cool og detaljeret , men der er altid 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 ) og vis, hvordan de kan anvendes ved at bruge eksemplet med at løse et af vores problemer.
Lad os gå!
float & int, regnefejl
Et absolut standard problem, løst gennem støbning:
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 taggen/feltet 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).
Når 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 (, , ) – vi venter på rettelser og lider lidt.
Brug af betingelser i beregninger (hvis i lambda)
|eval(lambda: if("value" > 0, true, false)
De sidste fem minutter fra pipelinen 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 er at bruge en BarrierNode-knude, som afskærer data før det angivne tidspunkt:
|barrier()
.period(5m)
Eksempler på brug af Go-skabeloner i besked
Skabelonerne svarer til formatet fra pakken , nedenfor er nogle ofte stødte på problemer.
hvis ellers
Lad os bringe tingene i orden og ikke trigge folk med tekst unødigt:
|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" }}'
)
Udfoldelse af variabler i besked
Vi viser mere information i beskeden for at besvare spørgsmålet "Hvorfor skriger det?"
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Unik advarsels-id
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 brugerdefinerede er et lille python-script til at sende meddelelser til slack.
Først ville vi sende et billede fra Grafana i en besked, beskyttet af autorisation. Skriv derefter OK i tråden til den forrige advarsel fra samme gruppe, og ikke i en separat besked. Lidt senere skal du tilføje den mest almindelige fejl over de sidste X minutter til beskeden.
Et særskilt emne er forbindelsen til andre tjenester og eventuelle handlinger iværksat af advarslen (kun hvis din overvågning fungerer godt nok).
Et eksempel på en handlerbeskrivelse, hvor slack_handler.py er vores brugerdefinerede 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 udgang til log
|log()
.level("error")
.prefix("something")
Watch (cli): kapacitor -url :9092 logger lvl=fejl
Mulighed med httpOut
Viser data i den aktuelle pipeline:
|httpOut('something')
Se (få): :9092/kapacitor/v1/tasks/task_name/noget
Udførelsesordning
- Hver opgave returnerer et udførelsestræ med nyttige tal i formatet .
- Vi tager en blok .
- Indsæt i fremviser, .
Hvor ellers kan du blive raket?
tidsstempel i influxdb ved tilbageskrivning
For eksempel opsætter vi en alarm for summen af anmodninger i timen (groupBy(1h)) og ønsker at registrere alarmen, der opstod i influxdb (for pænt at vise problemet på grafen i grafana).
influxDBOut() vil skrive tidsværdien fra advarslen til tidsstemplet, så punktet på diagrammet vil blive skrevet tidligere/senere end advarslen ankom.
Når præcision er påkrævet: vi omgår dette problem ved at kalde en brugerdefineret handler, som vil skrive data til influxdb med det aktuelle tidsstempel.
docker, bygge og implementere
Ved start 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:
- Filnavn - udvides til id/scriptnavn
- Type – stream/batch
- dbrp – nøgleord til at specificere i hvilken database + politik scriptet fungerer (dbrp "leverandør"."autogen")
Hvis en batch-opgave ikke har en linje med dbrp, vil hele tjenesten nægte at starte og ærligt skrive om det i loggen.
I kronograf skulle denne linje tværtimod ikke være der; det accepteres ikke gennem grænsefladen og returnerer en fejl.
Hack, når du bygger en container: Dockerfile afsluttes med -1, hvis der er linjer med //.+dbrp, hvilket giver dig mulighed for straks at forstå årsagen til byggefejlen.
slutte sig til en til mange
Eksempelopgave: du skal tage den 95. percentil af tjenestens oppetid i en uge, sammenligne hvert minut af de sidste 10 med denne værdi.
Du kan ikke lave en en-til-mange join, sidste/middelværdi/median på en gruppe af punkter forvandler noden til en strøm, fejlen "kan ikke tilføje underordnede uoverensstemmende kanter: batch -> stream" vil vende tilbage.
Resultatet af batchen, som en variabel i lambda-udtrykket, 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 omkring 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 ignoreres, afhængigt af situationen.
Grafana kan ikke lave inter-dimensionelle beregninger + alarmering, men vi har brug for en rate (requests-errors)/requests.
Fejlene ser grimme ud:

Og mindre ondt, hvis du ser på vellykkede forespørgsler:

Okay, vi kan forudberegne satsen i tjenesten før Grafana, og i nogle tilfælde vil dette virke. Men ikke i vores, for for hver kanal betragtes dens eget forhold som "normalt", og alarmer fungerer i henhold til statiske værdier (vi ser med øjnene, vi ændrer det, hvis det advarer ofte).
Disse er eksempler på "normal" for forskellige kanaler:


Lad os se bort fra det foregående punkt og antage, 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 vedligehold dem smerteligt)
b) efterlad et diagram med alle kanaler (og gå tabt i farverige linjer og tilpassede advarsler)

Hvordan gjorde du det?
Igen er der et godt starteksempel i dokumentationen (), kan du tage et kig eller bruge det som grundlag i lignende problemer.
Hvad vi gjorde til sidst:
- deltage i to episoder på få timer, gruppering efter kanaler;
- vi udfylder serien efter grupper, hvis der ikke var data;
- sammenligne medianen af de sidste 10 minutter med de tidligere data;
- råbe, hvis vi finder noget;
- vi skriver de beregnede satser og de advarsler, der opstod til influxdb;
- send en nyttig besked til slack.
Efter min mening lykkedes det os at opnå alt, hvad vi ønskede at få til sidst på den smukkeste måde (og endda lidt mere med custom handlers).
Du kan tage et kig på github.com и det modtagne manuskript.
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)
Så hvad er konklusionen?
Kapacitor er fantastisk til at overvåge og alarmere med en masse grupper, udføre yderligere beregninger baseret på allerede registrerede metrics, udføre tilpassede handlinger og køre scripts (udf).
Indgangstærsklen 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
