Me shumë mundësi, sot askush nuk pyet pse është e nevojshme të mblidhen matjet e shërbimit. Hapi tjetër logjik është të vendosni një alarm për matjet e mbledhura, i cili do të njoftojë për çdo devijim në të dhënat në kanalet e përshtatshme për ju (mail, Slack, Telegram). Në shërbimin e rezervimit të hoteleve online Ostrovok.ru të gjitha metrikat e shërbimeve tona derdhen në InfluxDB dhe shfaqen në Grafana, dhe alarmi bazë është konfiguruar gjithashtu atje. Për detyra të tilla si "ju duhet të llogaritni diçka dhe të krahasoni me të", ne përdorim Kapacitor.
Kapacitori është pjesë e grupit TICK që mund të përpunojë metrikë nga InfluxDB. Mund të lidhë disa matje së bashku (të bashkohet), të llogarisë diçka të dobishme nga të dhënat e marra, të shkruajë rezultatin në InfluxDB, të dërgojë një alarm në Slack/Telegram/mail.
E gjithë pirgja është e lezetshme dhe e detajuar dokumentacionin, por gjithmonë do të ketë gjëra të dobishme që nuk tregohen në mënyrë eksplicite në manuale. Në këtë artikull, vendosa të mbledh një numër këshillash të tilla të dobishme, jo të dukshme (sintaksa bazë e TICKscipt është përshkruar këtu) dhe tregoni se si ato mund të zbatohen duke përdorur një shembull të zgjidhjes së një prej problemeve tona.
Le të shkojë!
float & int, gabime në llogaritje
Një problem absolutisht standard, i zgjidhur përmes kastave:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Përdorimi i parazgjedhur ()
Nëse një etiketë/fushë nuk plotësohet, do të ndodhin gabime në llogaritje:
plotësoni bashkimin (e brendshme kundër e jashtme)
Si parazgjedhje, bashkimi do të heqë pikat ku nuk ka të dhëna (të brendshme).
Me fill('null'), do të kryhet një bashkim i jashtëm, pas së cilës duhet të bëni një default() dhe të plotësoni vlerat boshe:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Këtu ka ende një nuancë. Në shembullin e mësipërm, nëse një nga seritë (res1 ose res2) është bosh, seria (të dhënat) që rezulton gjithashtu do të jetë bosh. Ka disa bileta për këtë temë në Github (1633, 1871, 6967) – presim rregullime dhe vuajtje pak.
Përdorimi i kushteve në llogaritjet (nëse është në lambda)
|eval(lambda: if("value" > 0, true, false)
Pesë minutat e fundit nga gazsjellësi për periudhën
Për shembull, duhet të krahasoni vlerat e pesë minutave të fundit me javën e kaluar. Ju mund të merrni dy grupe të dhënash në dy grupe të veçanta ose të nxirrni një pjesë të të dhënave nga një periudhë më e madhe:
Një alternativë për pesë minutat e fundit do të ishte përdorimi i një BarrierNode, i cili ndërpret të dhënat përpara kohës së caktuar:
|barrier()
.period(5m)
Shembuj të përdorimit të modeleve Go në mesazh
Modelet korrespondojnë me formatin nga paketa teksti.shabllonMë poshtë janë disa enigma që hasen shpesh.
nese Tjeter
Ne i vendosim gjërat në rregull dhe nuk i nxisim njerëzit me tekst edhe një herë:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Dy shifra pas presjes dhjetore në mesazh
Përmirësimi i lexueshmërisë së mesazhit:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Zgjerimi i variablave në mesazh
Ne shfaqim më shumë informacion në mesazh për t'iu përgjigjur pyetjes "Pse po bërtet"?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Identifikues unik i alarmit
Kjo është një gjë e nevojshme kur ka më shumë se një grup në të dhëna, përndryshe do të gjenerohet vetëm një alarm:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Trajtues me porosi
Lista e madhe e mbajtësve përfshin exec, i cili ju lejon të ekzekutoni skriptin tuaj me parametrat e kaluar (stdin) - kreativitet dhe asgjë më shumë!
Një nga zakonet tona është një skrip i vogël Python për dërgimin e njoftimeve në Slack.
Në fillim, ne donim të dërgonim një fotografi grafana të mbrojtur nga autorizimi në një mesazh. Më pas, shkruani OK në temën e sinjalizimit të mëparshëm nga i njëjti grup, dhe jo si një mesazh i veçantë. Pak më vonë - shtoni mesazhit gabimin më të zakonshëm në X minutat e fundit.
Një temë më vete është komunikimi me shërbime të tjera dhe çdo veprim i nisur nga një alarm (vetëm nëse monitorimi juaj funksionon mjaft mirë).
Një shembull i një përshkrimi të mbajtësit, ku slack_handler.py është skripti ynë i shkruar vetë:
Për shembull, ne vendosim një alarm për shumën e kërkesave në orë (groupBy(1h)) dhe duam të regjistrojmë alarmin që ka ndodhur në influxdb (për të treguar bukur faktin e problemit në grafikun në grafana).
influxDBOut() do të shkruajë vlerën e kohës nga sinjalizimi në vulën kohore; në përputhje me rrethanat, pika në grafik do të shkruhet më herët/më vonë se sinjalizimi i mbërritur.
Kur kërkohet saktësi: ne punojmë rreth këtij problemi duke thirrur një mbajtës të personalizuar, i cili do të shkruajë të dhëna në influxdb me vulën kohore aktuale.
doker, ndërtimi dhe vendosja
Në nisje, kapacitori mund të ngarkojë detyrat, shabllonet dhe mbajtësit nga drejtoria e specifikuar në konfigurimin në bllokun [load].
Për të krijuar saktë një detyrë, ju nevojiten gjërat e mëposhtme:
Emri i skedarit – zgjerohet në id/emër të skriptit
Lloji – transmetim/batch
dbrp – fjalë kyçe për të treguar se në cilën bazë të dhënash + politikë funksionon skripti (dbrp “furnizuesi.” “autogjen”)
Nëse disa detyra grupore nuk përmbajnë një linjë me dbrp, i gjithë shërbimi do të refuzojë të fillojë dhe do të shkruajë sinqerisht për të në regjistër.
Në kronografi, përkundrazi, kjo linjë nuk duhet të ekzistojë; ajo nuk pranohet përmes ndërfaqes dhe gjeneron një gabim.
Hack gjatë ndërtimit të një kontejneri: Dockerfile del me -1 nëse ka linja me //.+dbrp, gjë që do t'ju lejojë të kuptoni menjëherë arsyen e dështimit kur montoni ndërtimin.
bashkohu një me shumë
Shembull i detyrës: duhet të merrni përqindjen e 95-të të kohës së funksionimit të shërbimit për një javë, krahasoni çdo minutë të 10 të fundit me këtë vlerë.
Nuk mund të bësh një bashkim një-me-shumë, e fundit/mesatarja/mediana mbi një grup pikash e kthen nyjen në një transmetim, gabimi "nuk mund të shtohen skajet e mospërputhshme të fëmijëve: grup -> transmetim" do të kthehet.
Rezultati i një grupi, si një variabël në një shprehje lambda, gjithashtu nuk zëvendësohet.
Ekziston një mundësi për të ruajtur numrat e nevojshëm nga grupi i parë në një skedar nëpërmjet udf-së dhe për të ngarkuar këtë skedar përmes ngarkesës anësore.
Çfarë zgjidhëm me këtë?
Kemi rreth 100 furnizues hotelesh, secili prej tyre mund të ketë disa lidhje, le ta quajmë kanal. Janë afërsisht 300 nga këto kanale, secili prej kanaleve mund të bjerë. Nga të gjitha metrikat e regjistruara, ne do të monitorojmë shkallën e gabimit (kërkesat dhe gabimet).
Pse jo grafana?
Sinjalizimet e gabimeve të konfiguruara në Grafana kanë disa disavantazhe. Disa janë kritike, disa mund t'i mbyllni sytë, në varësi të situatës.
Grafana nuk di te llogarise mes matjeve + alarmit, por na duhet nje norme (kerkesa-gabime)/kerkesa.
Gabimet duken të pakëndshme:
Dhe më pak e keqe kur shikohet me kërkesa të suksesshme:
Mirë, ne mund të parallogaritim tarifën në shërbim para grafana, dhe në disa raste kjo do të funksionojë. Por jo tek ne, sepse... për secilin kanal, raporti i tij konsiderohet "normal", dhe sinjalizimet funksionojnë sipas vlerave statike (i kërkojmë me sytë tanë, i ndryshojmë nëse ka sinjalizime të shpeshta).
Këta janë shembuj të "normales" për kanale të ndryshme:
Ne e injorojmë pikën e mëparshme dhe supozojmë se tabloja "normale" është e ngjashme për të gjithë furnizuesit. Tani çdo gjë është në rregull, dhe ne mund të kalojmë me alarme në grafana?
Ne mundemi, por vërtet nuk duam, sepse duhet të zgjedhim një nga opsionet:
a) bëni shumë grafikë për secilin kanal veç e veç (dhe shoqëroni me dhimbje)
b) lini një tabelë me të gjitha kanalet (dhe humbni në linjat shumëngjyrëshe dhe sinjalizimet e personalizuara)
bashkohu në dy seriale në pak orë, duke u grupuar sipas kanaleve;
plotësoni serinë sipas grupit nëse nuk kishte të dhëna;
krahasoni mesataren e 10 minutave të fundit me të dhënat e mëparshme;
bërtasim nëse gjejmë diçka;
ne shkruajmë tarifat e llogaritura dhe sinjalizimet që kanë ndodhur në influxdb;
dërgoni një mesazh të dobishëm për Slack.
Sipas mendimit tim, ne arritëm të arrijmë gjithçka që donim të arrinim në fund (dhe edhe pak më shumë me mbajtës të personalizuar) sa më bukur që të ishte e mundur.
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)
Cili është përfundimi?
Kapacitori është i shkëlqyeshëm në kryerjen e sinjalizimeve monitoruese me një sërë grupimesh, kryerjen e llogaritjeve shtesë bazuar në metrikat e regjistruara tashmë, kryerjen e veprimeve të personalizuara dhe ekzekutimin e skripteve (udf).
Pengesa e hyrjes nuk është shumë e lartë - provoni nëse grafana ose mjete të tjera nuk i plotësojnë plotësisht dëshirat tuaja.