Bragðarefur til að vinna úr mæligildum í Kapacitor
Líklegast spyr enginn í dag hvers vegna nauðsynlegt sé að safna þjónustumælingum. Næsta rökrétta skref er að setja upp viðvörun fyrir söfnuðu mæligildi, sem mun tilkynna um öll frávik í gögnum á rásum sem henta þér (póstur, Slack, Telegram). Í hótelbókunarþjónustu á netinu Ostrovok.ru öllum mæligildum þjónustu okkar er hellt inn í InfluxDB og birt í Grafana og grunnviðvörun er einnig stillt þar. Fyrir verkefni eins og „þú þarft að reikna eitthvað og bera saman við það,“ notum við Kapacitor.
Kapacitor er hluti af TICK staflanum sem getur unnið úr mælingum frá InfluxDB. Það getur tengt nokkrar mælingar saman (join), reiknað eitthvað gagnlegt út frá mótteknum gögnum, skrifað niðurstöðuna aftur í InfluxDB, sent viðvörun til Slack/Telegram/mail.
Allur staflinn er flottur og ítarlegur skjöl, en það verða alltaf gagnlegir hlutir sem eru ekki sérstaklega tilgreindir í handbókunum. Í þessari grein ákvað ég að safna mörgum slíkum gagnlegum, óaugljósum ráðum (grunnsetningafræði TICKscipt er lýst hér) og sýndu hvernig hægt er að beita þeim með því að nota dæmi um að leysa eitt af vandamálum okkar.
Við skulum fara!
flot & int, reikningsvillur
Algerlega staðlað vandamál, leyst í gegnum kasta:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Að nota sjálfgefið()
Ef merki/reitur er ekki fyllt út munu reiknivillur eiga sér stað:
Sjálfgefið er að join fleygir punktum þar sem engin gögn eru til (innri).
Með fill('null') verður ytri tenging framkvæmd, eftir það þarftu að gera sjálfgefið() og fylla út tóm gildin:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Hér er enn blæbrigði. Í dæminu hér að ofan, ef ein af röðunum (res1 eða res2) er tóm, verður röðin (gögnin) sem myndast einnig tóm. Það eru nokkrir miðar um þetta efni á Github (1633, 1871, 6967) – við bíðum eftir lagfæringum og þjáumst aðeins.
Að nota skilyrði í útreikningum (ef í lambda)
|eval(lambda: if("value" > 0, true, false)
Síðustu fimm mínútur frá leiðslu fyrir tímabilið
Til dæmis þarftu að bera saman gildi síðustu fimm mínútna við fyrri viku. Þú getur tekið tvær lotur af gögnum í tveimur aðskildum lotum eða dregið út hluta af gögnum frá stærra tímabili:
Annar valkostur síðustu fimm mínúturnar væri að nota BarrierNode, sem slekkur á gögnum fyrir tiltekinn tíma:
|barrier()
.period(5m)
Dæmi um notkun Go sniðmát í skilaboðum
Sniðmát samsvara sniðinu úr pakkanum textasniðmátHér að neðan eru nokkrar þrautir sem oft koma upp.
ef annað
Við setjum hlutina í röð og kveikjum ekki á fólki með texta aftur:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Tveir tölustafir á eftir aukastaf í skilaboðum
Að bæta læsileika skilaboðanna:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Stækkandi breytur í skilaboðum
Við birtum frekari upplýsingar í skilaboðunum til að svara spurningunni „Af hverju er það að öskra“?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Einstakt viðvörunarauðkenni
Þetta er nauðsynlegt þegar það eru fleiri en einn hópur í gögnunum, annars verður aðeins ein viðvörun búin til:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Sérsniðin meðhöndlun
Stóri listinn yfir stjórnendur inniheldur exec, sem gerir þér kleift að framkvæma handritið þitt með samþykktum breytum (stdin) - sköpunargáfu og ekkert annað!
Einn af siðum okkar er lítið Python forskrift til að senda tilkynningar til að slaka á.
Í fyrstu vildum við senda leyfisverndaða grafana mynd í skilaboðum. Síðan skaltu skrifa OK í þráðinn við fyrri viðvörun frá sama hópi, en ekki sem sérstök skilaboð. Stuttu seinna - bættu við skilaboðin algengustu mistökunum á síðustu X mínútum.
Sérstakt umræðuefni er samskipti við aðra þjónustu og allar aðgerðir sem viðvörun hefur hafið (aðeins ef eftirlit þitt virkar nógu vel).
Dæmi um meðhöndlunarlýsingu, þar sem slack_handler.py er sjálfskrifað handrit okkar:
Til dæmis setjum við upp viðvörun fyrir summan af beiðnum á klukkustund (groupBy(1h)) og viljum skrá viðvörunina sem kom í influxdb (til að sýna fallega staðreynd vandamálsins á grafinu í grafana).
influxDBOut() mun skrifa tímagildið frá viðvöruninni til tímastimpilsins; í samræmi við það verður punkturinn á töflunni skrifaður fyrr/síðar en viðvörunin barst.
Þegar nákvæmni er krafist: við vinnum í kringum þetta vandamál með því að hringja í sérsniðna meðhöndlun, sem mun skrifa gögn í influxdb með núverandi tímastimpli.
hafnarverkamaður, smíði og dreifing
Við ræsingu getur kapacitor hlaðið verkefnum, sniðmátum og meðhöndlum úr möppunni sem tilgreind er í stillingunni í [load] blokkinni.
Til að búa til verkefni rétt þarftu eftirfarandi hluti:
Skráarnafn - stækkað í handritaauðkenni/nafn
Tegund – straumur/lota
dbrp – lykilorð til að gefa til kynna í hvaða gagnagrunni + stefnu handritið keyrir (dbrp “birgir.” “autogen”)
Ef eitthvert lotuverkefni inniheldur ekki línu með dbrp, mun öll þjónustan neita að byrja og skrifa heiðarlega um það í annálinn.
Í chronograf, þvert á móti, ætti þessi lína ekki að vera til; hún er ekki samþykkt í gegnum viðmótið og myndar villu.
Hakk þegar gámur er byggður: Dockerfile fer út með -1 ef það eru línur með //.+dbrp, sem gerir þér kleift að skilja strax ástæðuna fyrir biluninni þegar þú setur smíðina saman.
sameinast einum á móti mörgum
Dæmi um verkefni: þú þarft að taka 95. hundraðshluta af rekstrartíma þjónustunnar í viku, berðu saman hverja mínútu af síðustu 10 við þetta gildi.
Þú getur ekki gert einn-á-marga sameiningu, síðasti/meðaltal/miðgildi yfir hóp af punktum breytir hnútnum í straum, villan „getur ekki bætt við barni sem ekki passar: hópur -> straumur“ verður skilað.
Niðurstaðan úr lotu, sem breytu í lambda tjáningu, kemur heldur ekki í staðinn.
Það er möguleiki að vista nauðsynlegar tölur frá fyrstu lotu í skrá í gegnum udf og hlaða þessari skrá með sideload.
Hvað leystum við með þessu?
Við erum með um 100 hótelbirgja, hver þeirra getur haft nokkrar tengingar, við skulum kalla það rás. Það eru um það bil 300 af þessum rásum, hver rásin getur dottið af. Af öllum skráðum mælingum munum við fylgjast með villuhlutfallinu (beiðnir og villur).
Af hverju ekki grafana?
Villuviðvaranir sem eru stilltar í Grafana hafa nokkra ókosti. Sumt er gagnrýnivert, annað er hægt að loka augunum fyrir, allt eftir aðstæðum.
Grafana kann ekki að reikna á milli mælinga + viðvörunar en við þurfum hlutfall (requests-errors)/requests.
Villurnar líta illa út:
Og minna illt þegar það er skoðað með árangursríkum beiðnum:
Allt í lagi, við getum fyrirfram reiknað út hlutfallið í þjónustunni fyrir grafana, og í sumum tilfellum mun þetta virka. En ekki hjá okkur, því... fyrir hverja rás er hlutfall hennar talið „eðlilegt“ og viðvaranir virka samkvæmt kyrrstæðum gildum (við leitum að þeim með augunum, breytum þeim ef það eru tíðar viðvaranir).
Þetta eru dæmi um „venjulegt“ fyrir mismunandi rásir:
Við hunsum fyrra atriðið og gerum ráð fyrir að „venjuleg“ myndin sé svipuð fyrir alla birgja. Nú er allt í lagi og við getum komist af með viðvaranir í Grafana?
Við getum það, en við viljum það ekki, vegna þess að við verðum að velja einn af kostunum:
a) búa til fullt af línuritum fyrir hverja rás fyrir sig (og fylgja þeim sársaukafullt)
b) skildu eftir eitt kort með öllum rásum (og týndu þér í litríkum línum og sérsniðnum viðvörunum)
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)
Hver er niðurstaðan?
Kapacitor er frábært í að framkvæma vöktunarviðvaranir með fullt af hópum, framkvæma viðbótarútreikninga byggða á þegar skráðum mæligildum, framkvæma sérsniðnar aðgerðir og keyra forskriftir (udf).
Aðgangshindrunin er ekki mjög mikil - reyndu það ef grafana eða önnur verkfæri fullnægja ekki óskum þínum að fullu.