Uwezekano mkubwa zaidi, leo hakuna mtu anayeuliza kwa nini ni muhimu kukusanya metrics za huduma. Hatua inayofuata ya kimantiki ni kuweka arifa ya vipimo vilivyokusanywa, ambayo itakuarifu kuhusu hitilafu zozote za data katika vituo vinavyokufaa (barua, Slack, Telegramu). Katika huduma ya uhifadhi wa hoteli mtandaoni Ostrovok.ru vipimo vyote vya huduma zetu humiminwa kwenye InfluxDB na kuonyeshwa Grafana, na arifa za kimsingi pia zimesanidiwa hapo. Kwa kazi kama vile "unahitaji kuhesabu kitu na kulinganisha nacho," tunatumia Kapacitor.
Kapacitor ni sehemu ya safu ya TICK inayoweza kuchakata vipimo kutoka kwa InfluxDB. Inaweza kuunganisha vipimo kadhaa pamoja (kujiunga), kukokotoa kitu muhimu kutoka kwa data iliyopokelewa, kuandika matokeo kwa InfluxDB, kutuma arifa kwa Slack/Telegram/mail.
Mkusanyiko mzima ni mzuri na wa kina nyaraka, lakini daima kutakuwa na mambo muhimu ambayo hayajaonyeshwa kwa uwazi katika miongozo. Katika nakala hii, niliamua kukusanya vidokezo kadhaa muhimu, visivyo dhahiri (syntax ya msingi ya TICKscipt imeelezewa. hapa) na uonyeshe jinsi yanavyoweza kutumika kwa kutumia mfano wa kutatua mojawapo ya matatizo yetu.
Hebu kwenda!
kuelea & int, makosa ya hesabu
Shida ya kawaida kabisa, iliyotatuliwa kupitia castes:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Kwa kutumia chaguo-msingi()
Ikiwa lebo/uga haujajazwa, hitilafu za hesabu zitatokea:
Kwa chaguomsingi, kujiunga kutatupa pointi ambapo hakuna data (ndani).
Wakati fill('null') uunganisho wa nje utafanywa, baada ya hapo unahitaji kufanya chaguo-msingi() na ujaze maadili tupu:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Bado kuna nuance hapa. Katika mfano hapo juu, ikiwa moja ya safu (res1 au res2) haina tupu, safu inayotokana (data) pia itakuwa tupu. Kuna tikiti kadhaa kwenye mada hii kwenye Github (1633, 1871, 6967) - tunasubiri marekebisho na mateso kidogo.
Kutumia hali katika mahesabu (ikiwa katika lambda)
|eval(lambda: if("value" > 0, true, false)
Dakika tano za mwisho kutoka kwa bomba kwa kipindi hicho
Kwa mfano, unahitaji kulinganisha maadili ya dakika tano zilizopita na wiki iliyopita. Unaweza kuchukua makundi mawili ya data katika makundi mawili tofauti au kutoa sehemu ya data kutoka kwa kipindi kikubwa zaidi:
Njia mbadala kwa dakika tano zilizopita itakuwa kutumia BarrierNode, ambayo hukata data kabla ya muda uliowekwa:
|barrier()
.period(5m)
Mifano ya kutumia violezo vya Go katika ujumbe
Violezo vinalingana na umbizo kutoka kwa kifurushi maandishi.kiolezoChini ni baadhi ya mafumbo yanayokutana mara kwa mara.
ikiwa -ngine
Tunaweka mambo kwa mpangilio na tusiwachochee watu kwa maandishi tena:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Nambari mbili baada ya nukta ya desimali katika ujumbe
Kuboresha usomaji wa ujumbe:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Kupanua vigezo katika ujumbe
Tunaonyesha maelezo zaidi katika ujumbe ili kujibu swali "Kwa nini inapiga kelele"?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Kitambulisho cha kipekee cha tahadhari
Hili ni jambo la lazima wakati kuna zaidi ya kikundi kimoja kwenye data, vinginevyo tahadhari moja tu itatolewa:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Vidhibiti maalum
Orodha kubwa ya washughulikiaji ni pamoja na exec, ambayo hukuruhusu kutekeleza hati yako na vigezo vilivyopitishwa (stdin) - ubunifu na hakuna zaidi!
Moja ya desturi zetu ni hati ndogo ya Python ya kutuma arifa kwa kulegea.
Mwanzoni, tulitaka kutuma picha ya grafana iliyoidhinishwa katika ujumbe. Baadaye, andika SAWA kwenye uzi kwa arifa iliyotangulia kutoka kwa kikundi kimoja, na sio kama ujumbe tofauti. Baadaye kidogo - ongeza kwenye ujumbe kosa la kawaida katika dakika X zilizopita.
Mada tofauti ni mawasiliano na huduma zingine na hatua zozote zinazoanzishwa na arifa (ikiwa tu ufuatiliaji wako utafanya kazi vya kutosha).
Mfano wa maelezo ya kidhibiti, ambapo slack_handler.py ni hati yetu iliyojiandika:
Kwa mfano, tunaweka arifa ya jumla ya maombi kwa saa (groupBy(1h)) na tunataka kurekodi arifa iliyotokea katika influxdb (ili kuonyesha kwa uzuri ukweli wa tatizo kwenye grafana kwenye grafana).
influxDBOut() itaandika thamani ya muda kutoka kwa tahadhari hadi muhuri wa muda; kwa hivyo, sehemu kwenye chati itaandikwa mapema/baadaye kuliko tahadhari ilipofika.
Wakati usahihi unahitajika: tunashughulikia tatizo hili kwa kupiga simu kidhibiti maalum, ambacho kitaandika data kwa influxdb na muhuri wa saa wa sasa.
docker, kujenga na kupeleka
Wakati wa kuanza, kapacitor inaweza kupakia kazi, violezo na vidhibiti kutoka kwa saraka iliyobainishwa kwenye usanidi kwenye kizuizi cha [mzigo].
Ili kuunda kazi kwa usahihi, unahitaji vitu vifuatavyo:
Jina la faili - limepanuliwa kuwa kitambulisho cha hati/jina
Aina - mkondo/bechi
dbrp - neno kuu la kuonyesha ni sera gani ya hifadhidata + ambayo hati inatumika (dbrp "mtoa huduma.""autogen")
Ikiwa kazi fulani ya kundi haina mstari na dbrp, huduma nzima itakataa kuanza na itaandika kwa uaminifu juu yake kwenye logi.
Katika chronograf, kinyume chake, mstari huu haupaswi kuwepo; haikubaliki kupitia interface na hutoa kosa.
Hack wakati wa kujenga chombo: Dockerfile hutoka na -1 ikiwa kuna mistari iliyo na // +dbrp, ambayo itakuruhusu kuelewa mara moja sababu ya kutofaulu wakati wa kukusanya jengo.
kujiunga moja kwa wengi
Kazi ya mfano: unahitaji kuchukua asilimia 95 ya muda wa uendeshaji wa huduma kwa wiki, kulinganisha kila dakika ya 10 iliyopita na thamani hii.
Huwezi kuunganisha moja hadi nyingi, mwisho/wastani/wastani juu ya kundi la pointi hugeuza nodi kuwa mtiririko, hitilafu "haiwezi kuongeza kingo zisizolingana za mtoto: batch -> mkondo" itarejeshwa.
Matokeo ya kundi, kama kigezo katika usemi wa lambda, pia hayabadilishwi.
Kuna chaguo la kuhifadhi nambari zinazohitajika kutoka kwa kundi la kwanza hadi faili kupitia udf na kupakia faili hii kupitia upakiaji wa kando.
Tulisuluhisha nini na hii?
Tuna takriban wauzaji 100 wa hoteli, kila mmoja wao anaweza kuwa na viunganisho kadhaa, wacha tuite kituo. Kuna takriban 300 ya chaneli hizi, kila moja ya chaneli inaweza kuanguka. Kati ya vipimo vyote vilivyorekodiwa, tutafuatilia kiwango cha makosa (maombi na hitilafu).
Kwa nini sio grafana?
Arifa za hitilafu zilizosanidiwa huko Grafana zina hasara kadhaa. Baadhi ni muhimu, wengine unaweza kufunga macho yako, kulingana na hali hiyo.
Grafana hajui jinsi ya kukokotoa kati ya vipimo + arifa, lakini tunahitaji kiwango (maombi-makosa)/maombi.
Makosa yanaonekana kuwa mabaya:
Na ubaya mdogo unapotazamwa na maombi yaliyofaulu:
Sawa, tunaweza kuhesabu mapema kiwango cha huduma kabla ya grafana, na katika hali zingine hii itafanya kazi. Lakini sio kwetu, kwa sababu ... kwa kila chaneli uwiano wake unachukuliwa kuwa "kawaida", na arifu hufanya kazi kulingana na maadili tuli (tunazitafuta kwa macho yetu, zibadilishe ikiwa kuna arifu za mara kwa mara).
Hii ni mifano ya "kawaida" kwa chaneli tofauti:
Tunapuuza hatua ya awali na kudhani kuwa picha "ya kawaida" ni sawa kwa wasambazaji wote. Sasa kila kitu kiko sawa, na tunaweza kuendelea na arifa katika grafana?
Tunaweza, lakini hatutaki, kwa sababu tunapaswa kuchagua moja ya chaguzi:
a) tengeneza grafu nyingi kwa kila chaneli kando (na uandamane nazo kwa uchungu)
b) acha chati moja iliyo na chaneli zote (na upotee katika mistari ya rangi na arifa zilizobinafsishwa)
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)
Hitimisho ni nini?
Kapacitor ni mzuri katika kutekeleza arifa za ufuatiliaji na kundi la vikundi, akifanya hesabu za ziada kulingana na vipimo vilivyorekodiwa, kutekeleza vitendo maalum na hati zinazoendesha (udf).
Kizuizi cha kuingia sio juu sana - jaribu ikiwa grafana au zana zingine hazikidhi kabisa matamanio yako.