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 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.

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, , 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 ) 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 (, , ) â 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 , 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 :9092 logs lvl=error
Variant httpOut
Kuvab andmed praeguses torustikus:
|httpOut('midagi')
Vaata (get): :9092\/kapacitor\/v1\/tasks\/task_name\/midagi
Teostusplaan
- Iga ĂŒlesanne tagastab teostuspuu kasulike numbritega formaadis .
- VÔtame ploki .
- Kandke viewerisse, .
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:
- Failinimi â laieneb id/nimega skripti
- TĂŒĂŒp â stream/batch
- 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:

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

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:


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)

Kuidas tegime?
JĂ€llegi, dokumentatsioonis on hea algusnĂ€ide (), 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 ja 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
