Nipid metrikate töötlemiseks Kapacitoris

TĂ€napĂ€eval ei kĂŒsi tĂ”enĂ€oliselt keegi, miks on oluline koguda teenuste mÔÔdikute andmeid. JĂ€rgmine mĂ”istlik samm on seadistada mÔÔdikute alerte, mis teavitavad teid mistahes andmete kĂ”rvalekalletest mugavates kanales (meil, Slack, Telegram). Hotellide veebibroneerimisteenus Ostrovok.ru kĂ”ik meie teenuste mÔÔdikud suunatakse InfluxDB-sse ja kuvatakse Grafanas, kus on ka seadistatud pĂ”hialert. Selliste ĂŒlesannete jaoks nagu "peab midagi arvutama ja selle vĂ”rreldama" kasutame Kapacitorit.

Nipid metrikate töötlemiseks Kapacitoris
Kapacitor on osa TICK-stakist, mis suudab töödelda mÔÔdikuid InfluxDB-st. Ta suudab ĂŒhendada mitmeid mÔÔtmisi (join), arvutada saadud andmetest midagi kasulikku, kirjutada tulemuse tagasi InfluxDB-sse ning saata alerte Slacki, Telegrami vĂ”i meilile.

Kogu stack on vĂ€ga Ă€ge ja detailne dokumentatsiooni, kuid alati leidub kasulikke asju, mis ei ole aruannetes selgelt vĂ€lja toodud. Selles artiklis otsustasin kokku koguda mitmeid selliseid kasulikke, kuid mitte alati ilmseid nĂ€punĂ€iteid (TICKscripti pĂ”hisintra on kirjeldatud siit) ja nĂ€idata, kuidas neid rakendada meie ĂŒhe ĂŒlesande nĂ€itel.

LĂ€hme!

float & int, arvutusvead

ÄÀrmiselt tavaline probleem, millega saab hakkama castimisega:

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

default() kasutamine

Kui sildid/vÀljad ei ole tÀidetud, tekivad arvutusvead:

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

fill joinis (inner vs outer)

Vaikimisi loob join punkte, kus andmed puuduvad (inner).
fill('null') korral toimub outer join, mille jĂ€rel tuleb teha default() ja tĂ€ita tĂŒhjad vÀÀrtused:

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

Siin on siiski nĂŒanss. Kui ĂŒlaltoodud nĂ€ites on ĂŒks seeriatest (res1 vĂ”i res2) tĂŒhi, on ka lĂ”ppseeria (data) tĂŒhi. Selle teemal on mitu tiketit GitHubis (1633, 1871, 6967) – ootame parandusi ja veidi kannatame.

Tingimuste kasutamine arvutustes (if lambda-s)

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

Viimased viis minutit pipe'ist ajavahemikul

NÀiteks, kui peate vÔrreldama viimase viie minuti vÀÀrtusi eelmise nÀdalaga. VÔite vÔtta kaks andmepartiid kaht erineva batch'iga vÔi vÀlja vÔtta osa andmeid suuremast ajavahemikust:

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

Viimaste viie minuti alternatiiviks vÔib olla BarrierNode'i kasutamine, mis katkestab andmed enne mÀÀratud aega:

|barrier()
        .period(5m)

Go malle kasutamise nÀited message'is

Mallid vastavad paketi vormingule text.template, allpool on mĂ”ned sagedamini esinevad ĂŒlesanded.

if-else

Teeme korda, mitte ei hÀiri inimesi tekstiga liigse kordamisega:

|alert()
    ...
    .message(
        '{{ if eq .Level "OK" }}NĂŒĂŒd on kĂ”ik korras{{ else }}Pea, kĂ”ik on katki{{end}}'
    )

Kaks tÀhtsust koma jÀrel message'is

Parandame sÔnumi loetavust:

|alert()
    ...
    .message(
        'nĂŒĂŒdne vÀÀrtus on {{ index .Fields "value" | printf "%0.2f" }}'
    )

Muuttujate lahtimuukimine message'is

Kuvame sĂ”numis rohkem teavet kĂŒsimusele "Miks see kisa teeb?"

var warnAlert = 10
  |alert()
    ...
    .message(
       'TÀna vÀÀrtus vÀhem kui ' + string(warnAlert) + '%'
    )

Alerdi unikaalne identifikaator

Oluline asi, kui andmetes on rohkem kui ĂŒks grupp, vastasel juhul genereeritakse ainult ĂŒks alert:

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

Kohandatud handler'id

Pikas handler'ite loendis on exec, mis vĂ”imaldab kĂ€ivitada oma skripti edastatud parameetritega (stdin) – puhas looming!

Üks meie kohandustest on vĂ€ike Python'i skript, et saata teateid Slacki.
Algselt soovisime saata sĂ”numis pildi Grafanast, mis on kaitstud autentimisega. Hiljem – kirjutada OK eelmise alerdi teema alla, mitte eraldi sĂ”numina. Veel hiljem – lisada sĂ”numisse kĂ”ige sagedasem viga viimase X minuti jooksul.

Eriline teema on seos teiste teenustega ja kÔik tegevused, mis alerdi tÔttu algatatakse (ainult juhul, kui teie jÀlgimine töötab piisavalt hÀsti).
NĂ€ide handleri kirjelduse kohta, kus slack_handler.py on meie enda 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"]

Kuidas vigade leidmine?

Variant logi vÀljundiga

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

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

HTTPOut variant

Kuvab andmeid praeguses pipe'is:

|httpOut('something')

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

TĂ€itev skeem

  • Iga ĂŒlesanne tagastab tĂ€itmispuu kasulike numbritega vormingus graphviz.
  • VĂ”tame ploki dot.
  • Kinnita pildile, naudi.

Kust saab veel timestamp'i infl dö vat?

timestamp InfluxDB-s, kui see on tagurpidi

NÀiteks seadistame hoiatuse tundide pÔhjal tehtud pÀringute summa (groupBy(1h)) ja soovime salvestada juhtunud hoiatuse influxdb-s (et ilusasti nÀidata probleemide olemasolu graafikul grafanas).

influxDBOut() salvestab timestampi vÀÀrtuse alertist, seega salvestatakse punkt graafikus varem/hiljem, kui hoiatuse aeg saabus.

Kui on vajalik tÀpsus: lahendame selle probleemi kohandatud handler'i kutsumise kaudu, mis salvestab andmed influxdb-s koos praeguse timestamp'iga.

docker, koostamine ja juurutamine

Kapacitor'i kĂ€ivitamisel saab see laadida ĂŒlesandeid, malle ja handler'e kaustast, mis on mÀÀratud konfiguraatoris, blokis [load].

Ülesande korrektseks loomiseks on vajalikud jĂ€rgmised asjad:

  1. Failinimi – muutub id/skripti nimeks
  2. TĂŒĂŒp – stream/batch
  3. dbrp – mĂ€rksĂ”na skripti töötamiseks millises andmebaasis + poliitikas (dbrp "supplier"."autogen")

Kui mĂ”nes batch-ĂŒlesandes puudub dbrp rida, keeldub kogu teenus kĂ€ivitumisest ja kirjutab sellest ausalt logisse.

Chronograf'is ei tohi see rida olla, lÀbi liidese ei aktsepteerita seda ja see annab veateate.

Konteineri koostamise hack: Dockerfile vÀljub -1-ga, kui on rea //.+dbrp, mis aitab kohe mÔista, miks buildi koostamisel tÔrge tekkis.

join ĂŒks paljudele

NĂ€idisĂŒlesanne: tuleb vĂ”tta teenuse tööaja 95. percentiil nĂ€dalas, vĂ”rrelda iga viimase 10 minuti vÀÀrtust selle nĂ€itajaga.

Ei ole vĂ”imalik teha join ĂŒks paljudele, last/mean/median rĂŒhmade punktide pĂ”hjal muudab nodi stream'iks, tagastatakse viga "cannot add child mismatched edges: batch -> stream".

Batch'i tulemust, nagu muutuja lambda vÀljendis, ei saa samuti kasutada.

On vÔimalus salvestada esimesest batch'ist vajalikud numbrid faili lÀbi udf ja laadida see fail lÀbi sideload.

Mida me sellega lahendasime?

Meil on umbes 100 hotellipakkujat, igal neist vĂ”ib olla mitu ĂŒhendust, nimetame seda kanaliks. Neid kanaleid on umbes 300, iga kanal vĂ”ib ebaĂ”nnestuda. KĂ”iki salvestatud mÔÔdikuid jĂ€lgime veatehte mÀÀra (requests ja errors).

Miks mitte grafana?

Grafanas seadistatud veateated omavad mitmeid miinuseid. MÔned on kriitilised, mÔnedest vÔib mööda vaadata, sÔltuvalt olukorrast.

Grafana ei oska teha arvutusi mÔÔtmiste vahel + hÀirete seadistamine, aga meil on vajalik mÀÀr (requests-errors)/requests.

Vead nÀevad vÀlja kohutavad:

Nipid metrikate töötlemiseks Kapacitoris

Ja vÀhem kohutavad, kui vaadata edukate pÀringute kontekstis:

Nipid metrikate töötlemiseks Kapacitoris

Okei, me saame eelnevalt mÀÀrata mÀÀr teenuses enne grafanat, ja mÔnes olukorras see sobib. Aga mitte meie puhul, sest iga kanali jaoks arvutatakse "normaalne" suhe eraldi ning hoiatuste seadistused toimivad staatiliste vÀÀrtuste alusel (otsime silmadega, muudame, kui liiga tihti hoiatustele reageerime).

Need on nÀiteks "normaalsed" nÀitajad erinevate kanalite jaoks:

Nipid metrikate töötlemiseks Kapacitoris

Nipid metrikate töötlemiseks Kapacitoris

JĂ€tame eelneva punkti tĂ€helepanuta ja eeldame, et kĂ”ikide pakkujate "normaalne" olukord on sarnane. NĂŒĂŒd on kĂ”ik hĂ€sti, ja saame jĂ€tkata grafanas hoiatustega?
Saame, aga ei taha, kuna tuleb valida ĂŒks variantidest:
a) teha palju graafikuid igale kanalile eraldi (ja vaeva nÀha nende hooldamisega)
b) jĂ€tta ĂŒks graafik koos kĂ”igi kanalitega (ja eksida vĂ€rviliste joonte ja seadistatud hoiatuste seas)

Nipid metrikate töötlemiseks Kapacitoris

Kuidas tegime?

JĂ€llegi, dokumentatsioonis on hea algnĂ€ide (Calculating rates across joined series), mida saab vaadata vĂ”i kasutada sarnastes ĂŒlesannetes.

Mida lÔpuks tegime:

  • liitmine kahe seeria vahel mitme tunni jooksul, rĂŒhmitamine kanalite pĂ”hjal;
  • tĂ€idame seeriaid rĂŒhmade jĂ€rgi, kui andmeid ei olnud;
  • vĂ”rdleme viimase 10 minuti mediaani varasemate andmetega;
  • karjume, kui midagi avastame;
  • salvestame arvutatud mÀÀrad ja juhtunud hoiad influxdb-s;
  • saadame kasuliku sĂ”numi slack'i.

Minu arvates suutsime saavutada kÔik, mida soovisime saavutada (ja isegi natuke rohkem kohandatud kÀsitsemistega).

GitHub.com-is saab vaadata nÀidis kood ja minimaalne skeem (graphviz) saadud skriptist.

Edasi liikuv koodi nÀidis:

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')
 // tÀidame nullidega, kui neid polnud
 |default()
 .field('err.value', 0.0)
 .field('req.value', 0.0)
 // if lambda: arvutame reitingu, ainult kui vigu on olnud
 |eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0))
 .as('rate')

// salvestame arvutatud vÀÀrtused influxisse
rates
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('rates')

// valime andmed viimase 10 minuti jooksul, arvutame mediaani
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()
 // kogume sÔnumisse lingi grafana dashboardi graafikule
 .message(
 '{{ .Level }}: {{ index .Tags "channel" }} veaga / pÀringute suhe ({{ index .Tags "supplier" }})
{{ if eq .Level "OK" }}NĂŒĂŒd on kĂ”ik korras{{ else }}
'+string(todayPeriod)+' mediaan on {{ index .Fields "today.median" | printf "%0.2f" }}%, eelmise '+string(period)+' puhul on {{ 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" dubleerime kui "value", samuti kirjutame influxisse ĂŒlejÀÀnud alerte vĂ€ljad (keep)
trigger
 |eval(lambda: "today.median")
 .as('value')
 .keep()
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('alerts')
 .tag('alertName', name)

A millisei?

Kapacitor oskab suurpĂ€raselt tegelema jĂ€lgimise ja hĂ€iretega, luues arvukalt rĂŒhmitusi, tehes tĂ€iendavaid arvutusi juba salvestatud mÔÔdikute pĂ”hjal, viies lĂ€bi kohandatud toiminguid ja kĂ€ivitades skripte (udf).

SisenemislĂ€vi pole kuigi kĂ”rge – proovige seda, kui Grafana vĂ”i muud tööriistad ei rahulda teid tĂ€ielikult.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster