Tricks för att hantera metrics i Kapacitor

Troligtvis har ingen idag lÀngre nÄgra frÄgor om varför man bör samla in service-metrik. Ett logiskt nÀsta steg Àr att stÀlla in en alert som notifierar om eventuella avvikelser i data via de kanaler som passar dig (e-post, Slack, Telegram). I vÄr online-hotellbokningstjÀnst Ostrovok.ru flödar alla metrik frÄn vÄra tjÀnster in i InfluxDB och visas i Grafana, dÀr grundlÀggande alerting ocksÄ Àr konfigurerat. För uppgifter som "behöver rÀkna nÄgot och jÀmföra med detta" anvÀnder vi Kapacitor.

Tricks för att hantera metrics i Kapacitor
Kapacitor Àr en del av TICK-stacken som kan bearbeta metrik frÄn InfluxDB. Den kan koppla samman flera mÀtningar (join), berÀkna nÄgot anvÀndbart frÄn de erhÄllna data, skriva resultaten tillbaka till InfluxDB och skicka en alert till Slack/Twitter/e-post.

Hela stacken har en fantastisk och detaljerad dokumentationen, men det finns alltid anvÀndbara saker som inte anges uttryckligen i manualerna. I denna artikel har jag valt att sammanstÀlla en rad sÄdana anvÀndbara och kanske inte sjÀlvklara tips (den grundlÀggande syntaxen för TICKscript beskrivs hÀr) och visa hur de kan tillÀmpas med exempel pÄ en av vÄra uppgifter.

LÄt oss köra!

float & int, berÀkningsfel

Ett helt standardproblem som löses via casting:

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

AnvÀndning av default()

Om taggen/fÀltet inte Àr ifyllt kommer det att uppstÄ berÀkningsfel:

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

fill i join (inner vs outer)

Per default kommer join att avvisa punkter dÀr data saknas (inner).
Vid fill('null') kommer en outer join att utföras, efter vilken man behöver göra default() och fylla de tomma vÀrdena:

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

Det finns Ă€ndĂ„ en nyans hĂ€r. Om en av serierna (res1 eller res2) i exemplet ovan Ă€r tom, kommer den slutgiltiga serien (data) ocksĂ„ att vara tom. Det finns flera biljetter om detta pĂ„ GitHub (1633, 1871, 6967) – vi vĂ€ntar pĂ„ fixar och lider lite.

AnvÀndning av villkor i berÀkningar (if i lambda)

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

De senaste fem minuterna frÄn pipelinen under perioden

Till exempel, om du behöver jÀmföra vÀrdena för de senaste fem minuterna med föregÄende vecka. Du kan ta tvÄ datauppsÀttningar med tvÄ separata batchar eller hÀmta en del av datan frÄn en större period:

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

Ett alternativ för de sista fem minuterna kan vara att anvÀnda noden BarrierNode, som skÀr av data tidigare Àn angiven tid:

|barrier()
        .period(5m)

Exempel pÄ anvÀndning av Go-mallar i meddelanden

Mallarna följer formatet frÄn paketet text.template, nedan nÄgra vanliga uppgifter.

if-else

Vi hÄller ordning utan att trigga folk med överflödig text:

|alert()
    ...
    .message(
        '{{ if eq .Level "OK" }}Det Àr okej nu{{ else }}Chefen, allt Àr trasigt{{end}}'
    )

TvÄ decimaler efter kommatecknet i meddelandet

FörbÀttrar lÀsbarheten i meddelandet:

|alert()
    ...
    .message(
        'nu vÀrdet Àr {{ index .Fields "value" | printf "%0.2f" }}'
    )

UtvÀrdering av variabler i meddelandet

Vi ger mer information i meddelandet för att svara pÄ frÄgan 'Varför skriker det?'

var warnAlert = 10
  |alert()
    ...
    .message(
       'Dagens vÀrde Àr mindre Àn '+string(warnAlert)+'%'
    )

Unik identifierare för alerten

NödvÀndig nÀr det finns mer Àn en grupp i datan, annars genereras bara en alert:

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

Anpassade handlers

I en stor lista av handlers finns exec, vilket gör det möjligt att köra ditt eget skript med överförda parametrar (stdin) – bara kreativitet!

En av vÄra anpassningar Àr ett litet Python-skript för att skicka meddelanden till Slack.
Först ville vi skicka en bild frÄn Grafana i meddelandet, skyddad av autentisering. Sedan ville vi skriva OK i trÄden till den föregÄende alerten frÄn samma grupp, istÀllet för ett separat meddelande. Lite senare ville vi Àven lÀgga till det vanligaste felet de senaste X minuterna i meddelandet.

Ett separat Ă€mne – kopplingar till andra tjĂ€nster och Ă„tgĂ€rder initierade av alerten (bara om din övervakning fungerar tillrĂ€ckligt bra).
Exempel pÄ beskrivning av en handler, dÀr slack_handler.py Àr vÄrt egna 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"]

Hur debuggar man?

Alternativ med loggutskrift

|log()
      .level("error")
      .prefix("nÄgot")

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

Alternativ med httpOut

Visar data i den aktuella pipeline:

|httpOut('nÄgot')

Visa (get): host-or-ip:9092\/kapacitor\/v1\/tasks\/task_name\/nÄgot

Schema för exekvering

  • Varje task returnerar ett exekveringstrĂ€d med anvĂ€ndbara siffror i formatet graphviz.
  • Vi tar block dot.
  • Vi sĂ€tter in i visaren, njut av det.

Var annars kan man fÄ timestamp i influxdb vid bakÄtupptagning

timestamp i influxdb vid omvÀnd registrering

Till exempel stÀller vi in en alert för antalet förfrÄgningar per timme (groupBy(1h)) och vill spara den intrÀffade alerten i influxdb (för att snyggt visa att det finns ett problem pÄ grafen i grafana).

influxDBOut() kommer att spara timestamp-vÀrdet tid frÄn alerten, vilket innebÀr att punkten pÄ grafen kommer att sparas tidigare/efter att alerten kommit.

NÀr precision krÀvs: vi löser detta problem genom att kalla pÄ en anpassad handler som sparar data i influxdb med den aktuella timestampen.

docker, bygg och distribution

Vid starten kan kapacitor ladda upp uppgifter, mallar och handlare frÄn en katalog som anges i konfigurationsfilen under blocket [load].

För att korrekt skapa en uppgift behövs följande saker:

  1. Filnamn – utvidgas till id/namn pĂ„ skriptet
  2. Typ – stream/batch
  3. dbrp – nyckelord för att ange vilken databas + policy skriptet arbetar i (dbrp «supplier».«autogen»)

Om det i nÄgon batch-uppgift inte finns en rad med dbrp, kommer hela tjÀnsten vÀgra att starta och Àrligt skriva detta i loggen.

I chronograf fÄr denna rad dock inte vara, den accepteras inte via grÀnssnittet och ger ett felmeddelande.

En hack vid byggandet av containern: Dockerfile avslutas med -1 om det finns rader med //.+dbrp, vilket gör att man genast förstÄr orsaken till felet vid byggandet.

join ett till mÄnga

Exempeluppgift: Man behöver ta 95:e percentilen av tjÀnstetiden för en vecka, jÀmföra varje minut av de senaste 10 med detta vÀrde.

Det gÄr inte att göra join ett till mÄnga, last/mean/median för grupp av punkter omvandlar noden till stream, och ett felmeddelande "cannot add child mismatched edges: batch -> stream" returneras.

Resultatet av batchen, som en variabel i lambda-uttrycket, sÀtts inte in heller.

Det finns ett alternativ att spara nödvÀndiga siffror frÄn den första batchen i en fil via udf och ladda upp den filen via sideload.

Vad löste vi med detta?

Vi har omkring 100 hotellleverantörer, till var och en av dem kan det finnas flera anslutningar, lÄt oss kalla det en kanal. Det finns ungefÀr 300 av dessa kanaler, var och en av kanalerna kan brytas. Blant de inspelade mÀtvÀrdena kommer vi att övervaka felenivÄ (requests och errors).

Varför inte grafana?

Alerts för fel som stÀllts in i grafana har flera nackdelar. Vissa Àr kritiska, för andra kan man blunda, beroende pÄ situationen.

Grafana kan inte göra berÀkningar mellan mÀtvÀrden + alerting, och vi behöver ju raten (requests-errors)/requests.

Felen ser elaka ut:

Tricks för att hantera metrics i Kapacitor

Och mindre elaka nÀr man ser dem med lyckade förfrÄgningar:

Tricks för att hantera metrics i Kapacitor

Okej, vi kan förhandsberÀkna hastigheten i tjÀnsten fram till Grafana, och i vissa fall kan det fungera. Men inte i vÄrt fall, eftersom varje kanal har sin egen relation som rÀknas som 'normal', och larm fungerar pÄ statiska vÀrden (vi letar med ögonen, Àndrar om det larmar ofta).

HÀr Àr exempel pÄ 'normal' för olika kanaler:

Tricks för att hantera metrics i Kapacitor

Tricks för att hantera metrics i Kapacitor

LÄt oss bortse frÄn tidigare punkt och anta att alla leverantörer har en 'normal' bild som Àr liknande. Nu Àr allt bra, kan vi klara oss med larm i Grafana?
Vi kan, men vi vill verkligen inte, för vi mÄste vÀlja ett av alternativen:
a) skapa mÄnga diagram för varje kanal separat (och plÄga oss sjÀlva med underhÄllet)
b) behÄlla ett diagram med alla kanaler (och tappa bort oss i fÀrgglada linjer och instÀllda larm)

Tricks för att hantera metrics i Kapacitor

Hur gjorde vi?

Återigen, dokumentationen har ett bra start exempel (Calculating rates across joined series), som vi kan titta pĂ„ eller anvĂ€nda som grund i liknande uppgifter.

Vad vi gjorde i slutÀndan:

  • slĂ„ samman tvĂ„ serier pĂ„ nĂ„gra timmar, gruppering efter kanaler;
  • fyller serierna per grupp om det inte fanns nĂ„gra data;
  • jĂ€mför medianen för de senaste 10 minuterna med tidigare data;
  • skriker om vi upptĂ€cker nĂ„got;
  • skriver de berĂ€knade hastigheterna och intrĂ€ffade larm i influxdb;
  • skickar ett anvĂ€ndbart meddelande till slack.

Enligt min Äsikt lyckades vi maximalt fÄ till allt vi helst skulle vilja ha som utgÄng (och till och med lite mer med anpassade handlare).

PÄ github.com kan man se exempel pÄ kod och minimala schemat (graphviz) av det resulterande skriptet.

Exempel pÄ den resulterande 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')
 // fyll med nollor om det inte fanns nÄgra
 |default()
 .field('err.value', 0.0)
 .field('req.value', 0.0)
 // if i lambda: berÀkna hastighet, endast om det fanns fel
 |eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0))
 .as('rate')

// skriv de berÀknade vÀrdena till Influx
rates
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('rates')

// vÀlj data för de senaste 10 minuterna och berÀkna medianen
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()
 // samla lÀnken till grafana dashboard grafik i meddelandet
 .message(
 '{{ .Level }}: {{ index .Tags "channel" }} err/req ratio ({{ index .Tags "supplier" }})
{{ if eq .Level "OK" }}Det Àr okej nu{{ else }}
'+string(todayPeriod)+' median Àr {{ index .Fields "today.median" | printf "%0.2f" }}%, jÀmfört med tidigare '+string(period)+' Àr {{ 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')

// "today.median" dupliceras som "value", samt skriver de andra fÀlten för alarmet i Influx (keep)
trigger
 |eval(lambda: "today.median")
 .as('value')
 .keep()
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('alerts')
 .tag('alertName', name)

Och vad Àr resultatet?

Kapacitor Àr utmÀrkt pÄ att utföra övervakning och larm med mÄnga gruppering, göra ytterligare berÀkningar pÄ redan inspelade mÀtvÀrden, utföra anpassade ÄtgÀrder och köra skript (udf).

IngĂ„ngströskeln Ă€r inte sĂ€rskilt hög – prova det om Grafana eller andra verktyg inte helt tillfredsstĂ€ller dina önskemĂ„l.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster