Seguruenik, gaur egun inork ez du galdetzen zergatik den beharrezkoa zerbitzu-neurriak biltzea. Hurrengo urrats logikoa da bildutako metriketarako alerta bat konfiguratzea, datuen desbideratzeen berri emango duena zuretzat komeni diren kanaletan (posta, Slack, Telegram). Online hotelak erreserbatzeko zerbitzuan Ostrovok.ru Gure zerbitzuen neurketa guztiak InfluxDB-ra isurtzen dira eta Grafanan bistaratzen dira, eta oinarrizko alertak ere bertan konfiguratzen dira. "Zerbait kalkulatu eta horrekin alderatu behar duzu" bezalako zereginetarako, Kapacitor erabiltzen dugu.
Kapacitor InfluxDB-ko neurketak prozesatu ditzakeen TICK pilaren parte da. Hainbat neurketa elkarrekin konekta ditzake (elkartu), jasotako datuetatik zerbait erabilgarria kalkulatu, emaitza berriro idatzi InfluxDB-ra, alerta bat bidali Slack/Telegram/mail-era.
Pila osoa polita eta zehatza da dokumentazioa, baina beti egongo dira eskuliburuetan esplizituki adierazten ez diren gauza erabilgarriak. Artikulu honetan, argiak ez diren aholku erabilgarriak biltzea erabaki nuen (TICKscipt-en oinarrizko sintaxia deskribatzen da Hemen) eta erakutsi nola aplika daitezkeen gure problemaren bat ebazteko adibide bat erabiliz.
Goazen!
float & int, kalkulu-erroreak
Arazo guztiz estandarra, kasten bidez konpondua:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
default() erabiliz
Etiketa/eremu bat betetzen ez bada, kalkulu-erroreak gertatuko dira:
Lehenespenez, elkartzeak daturik ez dagoen puntuak baztertuko ditu (barrukoa).
fill('null') bidez, kanpoko elkarketa bat egingo da, eta ondoren () lehenetsia egin eta balio hutsak bete behar dituzu:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Hemen badago oraindik Γ±abardura bat. Goiko adibidean, serieren bat (res1 edo res2) hutsik badago, ondoriozko seriea (datuak) ere hutsik egongo da. Gai honi buruzko hainbat sarrera daude Github-en (1633, 1871, 6967) β konponketen zain eta pixka bat sufritzen ari gara.
Baldintzak erabiltzea kalkuluetan (lambda bada)
|eval(lambda: if("value" > 0, true, false)
Azken bost minutuak aldirako kanalizaziotik
Adibidez, azken bost minutuetako balioak aurreko astearekin alderatu behar dituzu. Bi datu-lote har ditzakezu bi sorta ezberdinetan edo datuen zati bat epe handiago batetik atera:
Azken bost minutuetarako alternatiba bat BarrierNode erabiltzea izango litzateke, datuak zehaztutako ordua baino lehen mozten dituena:
|barrier()
.period(5m)
Mezuan Go txantiloiak erabiltzearen adibideak
Txantiloiak paketearen formatuari dagozkio testua.txantiloiaJarraian, maiz aurkitzen diren puzzle batzuk daude.
bestela
Gauzak ordenatzen ditugu eta ez dugu jendea berriro testuarekin aktibatzen:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Mezuko hamartarren ondoren bi zifra
Mezuaren irakurgarritasuna hobetzea:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Mezuan aldagaiak zabaltzea
Mezuan informazio gehiago erakusten dugu "Zergatik ari da oihuka" galderari erantzuteko?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Alerta-identifikatzaile bakarra
Hau beharrezkoa da datuetan talde bat baino gehiago dagoenean, bestela alerta bakarra sortuko da:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Kudeatzaile pertsonalizatuak
Kudeatzaileen zerrenda handiak exec barne hartzen du, zure script-a gainditutako parametroekin (stdin) exekutatzeko aukera ematen duena - sormena eta ezer gehiago!
Gure ohituretako bat slack-era jakinarazpenak bidaltzeko Python script txiki bat da.
Hasieran, baimen bidez babestutako grafana argazki bat bidali nahi genuen mezu batean. Ondoren, idatzi OK harian talde bereko aurreko alertarako, eta ez aparteko mezu gisa. Pixka bat geroago - gehitu mezuari azken X minutuetako akats ohikoena.
Aparteko gaia beste zerbitzu batzuekin komunikazioa eta alerta batek abiarazitako edozein ekintza dira (zure monitorizazioak behar bezain ondo funtzionatzen badu).
Kudeatzailearen deskribapenaren adibide bat, non slack_handler.py gure norberak idatzitako scripta den:
Esaterako, orduko eskaeren baturarako alerta bat ezarri dugu (groupBy(1h)) eta influxdb-n gertatutako alerta erregistratu nahi dugu (grafanaren grafikoan arazoaren errealitatea ederki erakusteko).
influxDBOut() denbora-balioa alertatik denbora-zigilura idatziko du; horren arabera, grafikoko puntua alerta iritsi baino lehenago/beranduago idatziko da.
Zehaztasuna behar denean: arazo honi aurre egiten diogu kudeatzaile pertsonalizatu bati deituz, eta honek influxdb-n datuak idatziko ditu uneko denbora-zigiluarekin.
Zeregin bat behar bezala sortzeko, gauza hauek behar dituzu:
Fitxategiaren izena - script id/izena zabaldu da
Mota - korronte/sorta
dbrp - script-a zein datu-base + politikatan exekutatzen den adierazteko gako-hitza (dbrp "hornitzailea". "autogen")
Batch atazaren batek dbrp-rekin lerrorik ez badu, zerbitzu osoak uko egingo dio abiarazteari eta zintzotasunez idatziko du erregistroan.
Chronograf-en, aitzitik, lerro honek ez luke existitu behar; ez da interfazearen bidez onartzen eta errore bat sortzen du.
Hack edukiontzi bat eraikitzean: Dockerfile -1-rekin irteten da //.+dbrp-rekin lerroak badaude, eta horrek hutsegitearen arrazoia berehala ulertuko dizu eraikuntza muntatzean.
batu bat askorekin
Egiteko adibidea: zerbitzuaren funtzionamendu-denboraren 95. pertzentilea hartu behar duzu astebetez, alderatu azken 10etako minutu bakoitza balio honekin.
Ezin duzu bat-bateko batuketa bat egin, puntu talde baten gainean azkena/batez bestekoa/mediana nodoa korronte bihurtzen du, "Ezin dira gehitu ertz desegokian seme-alabak: batch -> korrontea" errorea itzuliko da.
Batch baten emaitza, lambda adierazpen batean aldagai gisa, ere ez da ordezkatzen.
Lehen lotetik beharrezko zenbakiak udf bidez fitxategi batera gordetzeko eta fitxategi hau sideload bidez kargatzeko aukera dago.
Zer konpondu dugu honekin?
100 hotel hornitzaile inguru ditugu, horietako bakoitzak hainbat konexio izan ditzake, dei diezaiogun kanala. Gutxi gorabehera 300 kanal daude, kanal bakoitza eror daiteke. Grabatutako neurketa guztien artean, errore-tasa (eskaerak eta akatsak) kontrolatuko dugu.
Zergatik ez grafana?
Grafanan konfiguratutako errore-alertak hainbat desabantaila dituzte. Batzuk kritikoak dira, beste batzuk begiak itxi ditzakezu, egoeraren arabera.
Grafanak ez daki neurketen + alertaren artean kalkulatzen, baina tasa bat (eskaerak-erroreak)/eskaerak behar dugu.
Akatsak txarrak dirudite:
Eta gaitz gutxiago eskaera arrakastatsuekin ikusten denean:
Ados, grafana baino lehen zerbitzuan tarifa aldez aurretik kalkulatu dezakegu, eta kasu batzuetan honek funtzionatuko du. Baina gurean ez, zeren... kanal bakoitzerako bere ratioa "normaltzat" jotzen da eta alertak balio estatikoen arabera funtzionatzen du (begiekin bilatzen ditugu, maiz alertak badaude aldatu).
Hauek dira kanal ezberdinetarako "normal" adibideak:
Aurreko puntuari jaramonik egiten ez diogu eta irudi βnormalaβ hornitzaile guztientzat antzekoa dela suposatzen dugu. Orain dena ondo dago, eta abisuekin aurrera egin gaitezke grafana-n?
Ahal dugu, baina benetan ez dugu nahi, aukeretako bat aukeratu behar dugulako:
a) egin grafiko asko kanal bakoitzerako bereizita (eta minez lagundu)
b) utzi taula bat kanal guztiekin (eta galdu kolore koloretsuetan eta pertsonalizatutako alertetan)
Nola egin duzu?
Berriz ere, dokumentazioan hasierako adibide on bat dago (Lotutako serieen tasak kalkulatzea), antzeko arazoetan begiratu edo oinarritzat har daiteke.
Zer egin genuen azkenean:
batu bi serie ordu gutxitan, kanalen arabera taldekatuz;
serieak taldeka bete daturik ez bazegoen;
alderatu azken 10 minutuetako mediana aurreko datuekin;
zerbait aurkitzen badugu oihukatzen dugu;
influxdb-n gertatu diren kalkulatutako tarifak eta alertak idazten ditugu;
bidali mezu erabilgarria Slack-i.
Nire ustez, amaieran lortu nahi genuen guztia (eta are gehiago kudeatzaile pertsonalizatuekin) ahalik eta ederren lortzea lortu genuen.
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)
Zein da ondorioa?
Kapacitor bikaina da taldekatze mordo batekin monitorizazio-alertak egiteko, dagoeneko grabatutako metriketan oinarritutako kalkulu osagarriak egiteko, ekintza pertsonalizatuak egiteko eta script-ak (udf) exekutatzen.
Sartzeko oztopoa ez da oso altua - proba ezazu grafana edo beste tresna batzuek zure nahiak guztiz asetzen ez baditu.