Kavad mÔÔdikute töötlemiseks Kapacitoris

TĂ€napĂ€eval ei tekita kellelgi enam kĂŒsimust, miks on oluline koguda teenuste mÔÔdikuid. JĂ€rgmine loogiline samm on seadistada hoiatamine kogutud mÔÔdikute jaoks, mis teavitab teid kĂ”igist andmete kĂ”rvalekalletest mugavates kanalites (e-post, Slack, Telegram). Hotelli broneerimise teenuses Ostrovok.ru kĂ”ik meie teenuste mÔÔdikud voolavad InfluxDB-sse ja kuvatakse Grafanas, kus on seadistatud ka pĂ”hihoiatamine. Ülesannete korral, kus on vaja midagi arvutada ja vĂ”rrelda, kasutame Kapacitort.

Kavad mÔÔdikute töötlemiseks Kapacitoris
Kapacitor on osa TICK-stĂ€kist, mis oskab töödelda mÔÔdikuid InfluxDB-st. See suudab ĂŒhendada mitu mÔÔtmist (join), arvutada saadud andmetest midagi kasulikku, salvestada tulemuse tagasi InfluxDB-sse ning saata hoiatuse Slacki/Telegrami/e-postiga.

Kogu stĂ€kk on ÀÀrmiselt lahe ja detailne, dokumentatsioon, kuid alati leidub kasulikke nĂ€punĂ€iteid, mis ei ole manuaalides otseselt vĂ€lja toodud. KĂ€esolevas artiklis otsustasin koguda rida selliseid kasulikke ja mitte nii ilmselgeid nĂ”uandeid (TICKscipti pĂ”hikood on kirjeldatud siin) ja nĂ€idata, kuidas neid saab rakendada, kasutades nĂ€itena ĂŒhte meie ĂŒlesannet.

LĂ€hme!

float & int, arvutuste vead

Absoluutselt tavaline probleem, lahendatakse kastiga:

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 silt/valik ei ole tÀidetud, tekivad arvutustes vead:

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

fill join'is (inner vs outer)

Vaikimisi loob join punktid, kus andmeid pole (inner).
fill(‘null’) korral toimub outer join, pĂ€rast mida on vaja 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 ikkagi nĂŒanss. Kui eespool toodud nĂ€ites on ĂŒks seeriatest (res1 vĂ”i res2) tĂŒhi, on ka lĂ”pptulemus (data) tĂŒhi. Selle teema kohta on GitHubis mitmeid piletit (1633, 1871, 6967) – ootame parandusi ja kannatame veidi.

Tingimuste kasutamine arvutustes (if lambda's)

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

Viimased viis minutit pipeline's perioodi jooksul

NÀiteks, teil on vaja vÔrrelda viimase viie minuti vÀÀrtusi eelmise nÀdala omadega. Andmete saamiseks saate vÔtta kaks partiid eraldi batch'ina vÔi vÀlja tÔmmata osa andmeid suuremast perioodist:

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

Viimase viie minuti alternatiiviks vÔib olla BarrierNode'i kasutamine, mis katkestab andmed varem mÀÀratud ajast:

|barrier()
        .period(5m)

Go'ste mallide kasutamise nÀited message'is

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

if-else

Korrastame olukorda, mitte ei aktiveeri inimesi tekstiga ĂŒleliia:

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

Kaks numbrit pÀrast koma message'is

Parandame sÔnumi loetavust:

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

Muutujate arendamine message'is

Anname sĂ”numis rohkem teavet kĂŒsimusele «Miks see karjub?»

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

Alerdi unikaalne identifikaator

TĂ€pne asi, kui andmetes on rohkem kui ĂŒks rĂŒhm, muidu genereeritakse ainult ĂŒks alerd:

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

Kohandatud handler'id

Suures handlerite loendis on exec, mis vĂ”imaldab kĂ€ivitada oma skripti edastatud parameetritega (stdin) – puhas looming!

Üks meie kohandusi on vĂ€ike python'i skript teavituste saatmiseks slack'i.
Esmalt tahtsime saata sĂ”numis graafana pilti, mida kaitseb autoriseerimine. SeejĂ€rel – kirjutada OK eelneva alerdi teema alla, mitte eraldi sĂ”numina. Veidi hiljem – lisada sĂ”numisse kĂ”ige sagedasem viga viimase X minuti jooksul.

Eriline teema – ĂŒhendus teiste teenustega ja kĂ”ik tegevused, mida alerdi initsiatiivil algatada (ainult juhul, kui teie jĂ€lgimine töötab piisavalt hĂ€sti).
NÀide handleri kirjeldusest, kus slack_handler.py on meie kÀsitsi kirjutatud 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 tÔrkeotsingut teha?

Variant logisse vÀljastamiseks

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

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

Variant httpOut

Kuvab andmed praeguses torustikus:

|httpOut('midagi')

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

Teostusplaan

  • Iga ĂŒlesanne tagastab teostuspuu kasulike numbritega formaadis graphviz.
  • VĂ”tame ploki dot.
  • Kandke viewerisse, nautige.

Kust veel saab kÀtte grommingud

timestamp influxdb's tagasikiri

NÀiteks seame suhtluse alerti, mis pÔhineb tunni jooksul tehtud pÀringute arvul (groupBy(1h)), ja soovime salvestada toimunud alerti influxdb-sse (et ilusasti nÀidata probleemi olemasolu graafikul grafanas).

influxDBOut() salvestab timestampi vÀÀrtuse alertist, seega punkt graafikul salvestatakse varem/hiljem, kui alert saabus.

Kui vajalik on tÀpsus: lahendame selle probleemi kohandatud handleri kutsumise kaudu, mis salvestab andmed influxdb-sse kohaliku timestampiga.

docker, ehitamine ja juurutamine

Kapacitori kĂ€ivitamisel vĂ”ib see laadida ĂŒles ĂŒlesandeid, malle ja handler'eid kaustast, mis on mÀÀratud konfiguratsioonis plokis [load].

Korralikuks ĂŒlesande loomiseks on vajalikud jĂ€rgmised asjad:

  1. Failinimi – laieneb id/nimega skripti
  2. TĂŒĂŒp – stream/batch
  3. dbrp – vĂ”tmesĂ”na, et mĂ€rkida, millises andmebaasis + poliitikas skript töötab (dbrp «supplier».«autogen»)

Kui mÔnes batch-töös ei ole rida dbrp, keeldub kogu teenus kÀivitamast ja kirjutab ausalt sellest logisse.

Chronograf'is see rida aga ei peaks olema, liidese kaudu ei vÔeta seda vastu ja see annab vea.

Konteineri ehitamise nipp: Dockerfile vÀljub -1, kui esinevad read, kus on //.+dbrp, mis vÔimaldab kohe aru saada ehituse ebaÔnnestumise pÔhjusest.

join ĂŒks paljude seas

Ülesanne-nĂ€ide: tuleb vĂ”tta 95. protsentiil teenuse tööajast nĂ€dala jooksul, vĂ”rrelda viimase 10 minuti igat minutit selle vÀÀrtusega.

Ühte paljusid join'e ei saa teha, last/mean/median grupi punktide pĂ”hjal muudab sĂ”lme streamiks, tagastatakse viga «cannot add child mismatched edges: batch -> stream».

Batchi tulemus, nagu muutujana lambda vÀljendis, ei too samuti asendatakse.

On vĂ”imalus salvestada vajalikud numbrid esimesest batch'ist faili lĂ€bi udf ja laadida see fail ĂŒles sideload'i kaudu.

Mida me sellega lahendasime?

Meil on umbes 100 hotellide teenusepakkujat, igal neist vĂ”ib olla mitmeid ĂŒhendusi, nimetame neid kanaliteks. Neid kanaleid on umbes 300, iga kanali vĂ”ib katketa. KĂ”ikide salvestatavate mÔÔdikute hulgast jĂ€lgime vigade mÀÀra (requests ja errors).

Miks mitte grafana?

Grafanas seadistatud vigade alertid omavad mitmeid miinuseid. MÔned on kriitilised, teistele vÔib silma kinni pigistada, olenevalt olukorrast.

Grafana ei oska teha arvutusi mÔÔdikute vahel + alerting, aga meil on vaja mÀÀra (requests-errors)/requests.

Vead nÀevad vÀlja kohutavad:

Kavad mÔÔdikute töötlemiseks Kapacitoris

Ja vÀhem kohutav, kui vaadata koos edukaid pÀringutega:

Kavad mÔÔdikute töötlemiseks Kapacitoris

Olgu, me saame teenuse enne Grafanasse sissearvutada reitingu, ja mÔnes mÔttes sobib see. Kuid mitte meie puhul, kuna iga kanali jaoks arvutatakse oma 'normaalne' suhe, ja hÀired töötavad staatiliste vÀÀrtuste alusel (otsime silmadega, muudame, kui see sageli hÀirib).

Need on erinevate kanalite 'normaalsed' nÀited:

Kavad mÔÔdikute töötlemiseks Kapacitoris

Kavad mÔÔdikute töötlemiseks Kapacitoris

JĂ€tame eelneva punkti tĂ€helepanuta ja oletame, et kĂ”igil teenusepakkujatel on 'normaalne' pilt sarnane. NĂŒĂŒd on kĂ”ik korras ja saame Grafanasse hĂ€iretega toime tulla?
Saame kĂŒll, kuid ei tahaks seda teha, sest peame valima ĂŒhe jĂ€rgmistest variantidest:
a) teha iga kanali jaoks eraldi palju graafikuid (ja vaeva nende hooldamisega)
b) jĂ€tta ĂŒks graafik kĂ”igi kanalitega (ja kaotsida end vĂ€rvikas joonte ja seadistatud hĂ€irete seas)

Kavad mÔÔdikute töötlemiseks Kapacitoris

Kuidas tegime?

JĂ€llegi, dokumentatsioonis on hea algusnĂ€ide (Calculating rates across joined series), saab piiluda vĂ”i vĂ”tta aluseks sarnaste ĂŒlesannete puhul.

Mida me lÔpuks tegime:

  • kahe seeria liitmine mĂ”ne tunni jooksul, gruppimise kanalite kaupa;
  • tĂ€idame seeriad gruppide kaupa, kui andmeid polnud;
  • vĂ”rdleme viimase 10 minuti mediaani eelmiste andmetega;
  • karjume, kui midagi avastame;
  • kirjutame arvutatud reitingud ja toimunud hĂ€ired influxdb-sse;
  • saadetame kasulikku sĂ”numit Slacki.

Minu arvates suutsime maksimaalselt ilusasti kÔik, mida sooviksime vÀljundina saada (ja isegi rohkem, koos kohandatud kÀsitlejatega).

Github.com-is saab vaadata nÀidiskoodi ja minimaalse skeemi (graphviz) saadud skriptist.

NĂ€ide saadud koodist:

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 null-vÀÀrtused, kui neid ei olnud
 |default()
 .field('err.value', 0.0)
 .field('req.value', 0.0)
 // if lambda: arvutame mÀÀr, ainult kui vigu esines
 |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 andmeid 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 armatuurlauale
 .message(
 '{{ .Level }}: {{ index .Tags "channel" }} err/req 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)+' 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 influksisse ĂŒlejÀÀnud alamd
trigger
 |eval(lambda: "today.median")
 .as('value')
 .keep()
 |influxDBOut()
 .quiet()
 .create()
 .database('kapacitor')
 .retentionPolicy('autogen')
 .measurement('alerts')
 .tag('alertName', name)

Ja kuidas see vÀljund on?

Kapacitor oskab suurepĂ€raselt teostada seire- ja hĂ€irivusteenuseid koos hulga rĂŒhmitamisega, teha tĂ€iendavaid arvutusi juba salvestatud mÔÔdikute pĂ”hjal, teostada kohandatud toiminguid ja kĂ€ivitada skripte (udf).

Sisenemise kĂŒnnis ei ole vĂ€ga kĂ”rge – proovige seda, kui grafana vĂ”i muud tööriistad ei tĂ€ida tĂ€ielikult teie soove.

Allikas: habr.com

Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega veebisaitidele, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega veebisaitidele, VPS VDS serverid - ProHoster