VisticamÄk, Å”odien neviens nejautÄ, kÄpÄc ir jÄvÄc servisa rÄdÄ«tÄji. NÄkamais loÄ£iskais solis ir ievÄktajiem rÄdÄ«tÄjiem iestatÄ«t brÄ«dinÄjumu, kas jums Ärtos kanÄlos (pasts, Slack, Telegram) ziÅos par jebkÄdÄm novirzÄm datos. TieÅ”saistes viesnÄ«cu rezervÄÅ”anas pakalpojumÄ Ostrovok.ru visi mÅ«su pakalpojumu rÄdÄ«tÄji tiek ievietoti InfluxDB un parÄdÄ«ti Grafana, un tur ir arÄ« konfigurÄti pamata brÄ«dinÄjumi. TÄdiem uzdevumiem kÄ ājums kaut kas jÄaprÄÄ·ina un jÄsalÄ«dzina ar toā, mÄs izmantojam Kapacitor.
Kapacitor ir daļa no TICK steka, kas var apstrÄdÄt metriku no InfluxDB. Tas var savienot vairÄkus mÄrÄ«jumus kopÄ (savienoties), aprÄÄ·inÄt kaut ko noderÄ«gu no saÅemtajiem datiem, atrakstÄ«t rezultÄtu atpakaļ uz InfluxDB, nosÅ«tÄ«t brÄ«dinÄjumu uz Slack/Telegram/mail.
Visa kaudze ir forÅ”a un detalizÄta dokumentÄcija, taÄu vienmÄr bÅ«s noderÄ«gas lietas, kas rokasgrÄmatÄs nav skaidri norÄdÄ«tas. Å ajÄ rakstÄ es nolÄmu apkopot vairÄkus Å”Ädus noderÄ«gus, nepÄrprotamus padomus (ir aprakstÄ«ta TICKscipt pamata sintakse Å”eit) un parÄdiet, kÄ tos var pielietot, izmantojot piemÄru, kÄ atrisinÄt kÄdu no mÅ«su problÄmÄm.
Iesim!
float & int, aprÄÄ·inu kļūdas
AbsolÅ«ti standarta problÄma, kas atrisinÄta caur kastÄm:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Izmantojot noklusÄjuma ()
Ja atzÄ«me/lauks nav aizpildÄ«ts, aprÄÄ·inos var rasties kļūdas:
aizpildiet savienojumu (iekÅ”Äjais vai ÄrÄjais)
PÄc noklusÄjuma pievienoÅ”anÄs atmetÄ«s punktus, kur nav datu (iekÅ”Äjie).
Izmantojot fill('null'), tiks veikta ÄrÄja savienoÅ”ana, pÄc kuras jums ir jÄveic noklusÄjuma () un jÄaizpilda tukÅ”Äs vÄrtÄ«bas:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Å eit joprojÄm ir nianse. IepriekÅ” minÄtajÄ piemÄrÄ, ja viena no sÄrijÄm (res1 vai res2) ir tukÅ”a, iegÅ«tÄ sÄrija (dati) arÄ« bÅ«s tukÅ”a. VietnÄ Github ir vairÄkas biļetes par Å”o tÄmu (1633, 1871, 6967) ā gaidÄm labojumus un nedaudz cieÅ”am.
NosacÄ«jumu izmantoÅ”ana aprÄÄ·inos (ja lambda)
|eval(lambda: if("value" > 0, true, false)
PÄdÄjÄs piecas minÅ«tes no cauruļvada periodam
PiemÄram, jums ir jÄsalÄ«dzina pÄdÄjo piecu minÅ«Å”u vÄrtÄ«bas ar iepriekÅ”Äjo nedÄļu. Varat Åemt divas datu paketes divÄs atseviŔķÄs paketÄs vai iegÅ«t daļu datu no lielÄka perioda:
AlternatÄ«va pÄdÄjÄm piecÄm minÅ«tÄm bÅ«tu izmantot BarrierNode, kas izslÄdz datus pirms norÄdÄ«tÄ laika:
|barrier()
.period(5m)
Go veidÅu izmantoÅ”anas piemÄri ziÅojumÄ
Veidnes atbilst formÄtam no iepakojuma text.templateZemÄk ir dažas bieži sastopamas mÄ«klas.
ja-vÄl
MÄs sakÄrtojam lietas un vairs neizraisÄm cilvÄkus ar tekstu:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Divi cipari aiz komata ziÅojumÄ
ZiÅojuma lasÄmÄ«bas uzlaboÅ”ana:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
MainÄ«go izvÄrÅ”ana ziÅojumÄ
ZiÅojumÄ parÄdÄm vairÄk informÄcijas, lai atbildÄtu uz jautÄjumu āKÄpÄc tÄ kliedzā?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
UnikÄls brÄ«dinÄjuma identifikators
Tas ir nepiecieÅ”ams, ja datos ir vairÄk nekÄ viena grupa, pretÄjÄ gadÄ«jumÄ tiks Ä£enerÄts tikai viens brÄ«dinÄjums:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
PielÄgots apstrÄdÄtÄjs
LielajÄ apdarinÄtÄju sarakstÄ ir iekļauts exec, kas ļauj izpildÄ«t savu skriptu ar nodotajiem parametriem (stdin) - radoÅ”ums un nekas vairÄk!
Viena no mÅ«su paražÄm ir neliels Python skripts paziÅojumu nosÅ«tÄ«Å”anai uz atslÄbumu.
SÄkumÄ mÄs gribÄjÄm nosÅ«tÄ«t ar autorizÄciju aizsargÄtu grafana attÄlu ziÅojumÄ. PÄc tam ierakstiet OK pavedienÄ uz iepriekÅ”Äjo brÄ«dinÄjumu no tÄs paÅ”as grupas, nevis kÄ atseviŔķu ziÅojumu. Nedaudz vÄlÄk - pievienojiet ziÅojumam biežÄko kļūdu pÄdÄjo X minÅ«Å”u laikÄ.
AtseviŔķa tÄma ir saziÅa ar citiem dienestiem un jebkÄdas darbÄ«bas, ko ierosina brÄ«dinÄjums (tikai tad, ja jÅ«su uzraudzÄ«ba darbojas pietiekami labi).
ApdarinÄtÄja apraksta piemÄrs, kur slack_handler.py ir mÅ«su paÅ”u rakstÄ«tais skripts:
PiemÄram, mÄs iestatÄm brÄ«dinÄjumu par pieprasÄ«jumu summu stundÄ (groupBy(1h)) un vÄlamies reÄ£istrÄt brÄ«dinÄjumu, kas notika influxdb (lai skaisti parÄdÄ«tu problÄmas faktu grafana diagrammÄ).
influxDBOut() ierakstÄ«s laika vÄrtÄ«bu no brÄ«dinÄjuma lÄ«dz laika zÄ«mogam; attiecÄ«gi punkts diagrammÄ tiks ierakstÄ«ts agrÄk/vÄlÄk nekÄ brÄ«dinÄjums ir saÅemts.
Ja nepiecieÅ”ama precizitÄte: mÄs apstrÄdÄjam Å”o problÄmu, izsaucot pielÄgotu apdarinÄtÄju, kas ierakstÄ«s datus influxdb ar paÅ”reizÄjo laikspiedolu.
doks, uzbūve un izvietoŔana
StartÄÅ”anas laikÄ kapacitor var ielÄdÄt uzdevumus, veidnes un apstrÄdÄtÄjus no direktorija, kas norÄdÄ«ta konfigurÄcijÄ blokÄ [load].
Lai pareizi izveidotu uzdevumu, ir nepiecieÅ”amas Å”Ädas lietas:
Faila nosaukums ā izvÄrsts skripta ID/nosaukumÄ
Tips ā straume/partija
dbrp ā atslÄgvÄrds, kas norÄda, kurÄ datu bÄzÄ + politikÄ skripts darbojas (dbrp āpiegÄdÄtÄjs.ā āautogenā)
Ja kÄdÄ pakeÅ”u uzdevumÄ nav rindas ar dbrp, viss pakalpojums atteiksies sÄkt un godÄ«gi ierakstÄ«s par to žurnÄlÄ.
Gluži pretÄji, hronogrÄfijÄ Å”ai lÄ«nijai nevajadzÄtu pastÄvÄt; tÄ netiek pieÅemta caur interfeisu un rada kļūdu.
Uzlauzt, veidojot konteineru: Dockerfile iziet ar -1, ja ir rindas ar //.+dbrp, kas ļaus jums uzreiz saprast neveiksmes iemeslu, montÄjot bÅ«vi.
pievienoties viens pret daudziem
Uzdevuma piemÄrs: jums ir jÄÅem pakalpojuma darbÄ«bas laika nedÄļas 95. procentile, salÄ«dziniet katru minÅ«ti no pÄdÄjÄm 10 ar Å”o vÄrtÄ«bu.
JÅ«s nevarat veikt savienojumu viens pret daudziem, pÄdÄjais/vidÄjais/vidÄjais punktu grupÄ pÄrvÄrÅ” mezglu par straumi, tiks atgriezta kļūda ānevar pievienot pakÄrtotÄs malas neatbilstoÅ”as: partija ā> straumeā.
ArÄ« partijas rezultÄts kÄ mainÄ«gais lambda izteiksmÄ netiek aizstÄts.
Ir iespÄja saglabÄt nepiecieÅ”amos numurus no pirmÄs partijas failÄ, izmantojot udf, un ielÄdÄt Å”o failu, izmantojot sÄnu ielÄdi.
Ko mÄs ar Å”o atrisinÄjÄm?
Mums ir ap 100 viesnÄ«cu piegÄdÄtÄju, katram var bÅ«t vairÄki pieslÄgumi, sauksim to par kanÄlu. Å o kanÄlu ir aptuveni 300, katrs no tiem var nokrist. No visiem reÄ£istrÄtajiem rÄdÄ«tÄjiem mÄs uzraudzÄ«sim kļūdu lÄ«meni (pieprasÄ«jumus un kļūdas).
KÄpÄc ne grafana?
Grafana konfigurÄtajiem kļūdu brÄ«dinÄjumiem ir vairÄki trÅ«kumi. Daži no tiem ir kritiski, pret dažiem jÅ«s varat aizvÄrt acis atkarÄ«bÄ no situÄcijas.
Grafana neprot rÄÄ·inÄt starp mÄrÄ«jumiem + brÄ«dinÄjumiem, bet mums vajag Ätrumu (pieprasÄ«jumi-kļūdas)/pieprasÄ«jumi.
Kļūdas izskatÄs nepatÄ«kamas:
Un mazÄk ļaunuma, ja to aplÅ«ko ar veiksmÄ«giem pieprasÄ«jumiem:
Labi, mÄs varam iepriekÅ” aprÄÄ·inÄt likmi pakalpojumÄ pirms grafana, un dažos gadÄ«jumos tas darbosies. Bet ne pie mums, jo... katram kanÄlam tÄ attiecÄ«ba tiek uzskatÄ«ta par ānormÄluā, un brÄ«dinÄjumi darbojas saskaÅÄ ar statiskÄm vÄrtÄ«bÄm (mÄs tos meklÄjam ar acÄ«m, mainÄm, ja ir bieži brÄ«dinÄjumi).
Å ie ir āparastoā dažÄdu kanÄlu piemÄri:
MÄs ignorÄjam iepriekÅ”Äjo punktu un pieÅemam, ka āparastÄā aina visiem piegÄdÄtÄjiem ir lÄ«dzÄ«ga. Tagad viss ir kÄrtÄ«bÄ, un mÄs varam iztikt ar brÄ«dinÄjumiem grafana?
MÄs varam, bet mÄs ļoti nevÄlamies, jo mums ir jÄizvÄlas viena no iespÄjÄm:
a) izveidojiet daudz grafiku katram kanÄlam atseviŔķi (un sÄpÄ«gi pavadiet tos)
b) atstÄjiet vienu diagrammu ar visiem kanÄliem (un pazÅ«diet krÄsainajÄs lÄ«nijÄs un pielÄgotajos brÄ«dinÄjumos)
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)
KÄds ir secinÄjums?
Kapacitor lieliski spÄj veikt uzraudzÄ«bas brÄ«dinÄjumus ar virkni grupÄjumu, veikt papildu aprÄÄ·inus, pamatojoties uz jau reÄ£istrÄtiem rÄdÄ«tÄjiem, veikt pielÄgotas darbÄ«bas un palaist skriptus (udf).
IekļūŔanas barjera nav Ä«paÅ”i augsta ā izmÄÄ£iniet to, ja grafana vai citi instrumenti pilnÄ«bÄ neapmierina jÅ«su vÄlmes.