Todennäköisesti nykyään kukaan ei kysy, miksi palvelumittareita on tarpeen kerätä. Seuraava looginen askel on asettaa kerätyille mittareille hälytys, joka ilmoittaa mahdollisista poikkeamista tiedoissa sinulle sopivissa kanavissa (posti, Slack, Telegram). Hotellin online-varauspalvelussa Ostrovok.ru kaikki palveluidemme mittarit kaadetaan InfluxDB:hen ja näytetään Grafanassa, ja siellä on myös perushälytys. Tehtävissä, kuten "sinun täytyy laskea jotain ja verrata siihen", käytämme Kapacitoria.
Kapacitor on osa TICK-pinoa, joka voi käsitellä InfluxDB:n mittareita. Se voi yhdistää useita mittauksia (liittyä), laskea jotain hyödyllistä vastaanotetuista tiedoista, kirjoittaa tuloksen takaisin InfluxDB:hen, lähettää hälytyksen Slackiin/Telegramiin/mailiin.
Koko pino on siisti ja yksityiskohtainen dokumentointi, mutta aina löytyy hyödyllisiä asioita, joita ei ole erikseen mainittu oppaissa. Tässä artikkelissa päätin kerätä joukon sellaisia hyödyllisiä, ei-ilmeisiä vinkkejä (TICKsciptin perussyntaksi on kuvattu täällä) ja näytä, kuinka niitä voidaan soveltaa esimerkin avulla yhden ongelmamme ratkaisemisesta.
Mennään!
float & int, laskentavirheet
Täysin tavallinen ongelma, joka ratkaistaan kastien kautta:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Käytä oletusarvoa ()
Jos tunnistetta/kenttää ei täytetty, tapahtuu laskentavirheitä:
Oletusarvoisesti liittäminen hylkää kohdat, joissa ei ole tietoja (sisäinen).
Fil('null') -komennolla suoritetaan ulompi liitos, jonka jälkeen sinun on tehtävä default() ja täytettävä tyhjät arvot:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Tässä on vielä vivahde. Yllä olevassa esimerkissä, jos jokin sarjoista (res1 tai res2) on tyhjä, tuloksena oleva sarja (data) on myös tyhjä. Githubissa on useita lippuja tästä aiheesta (1633, 1871, 6967) – odotamme korjauksia ja vähän kärsimystä.
Ehtojen käyttö laskelmissa (jos lambda)
|eval(lambda: if("value" > 0, true, false)
Viimeiset viisi minuuttia putkesta ajanjaksolle
Sinun on esimerkiksi verrattava viimeisen viiden minuutin arvoja edelliseen viikkoon. Voit ottaa kaksi tietoerää kahtena erillisenä eränä tai poimia osan tiedoista suuremmalta ajanjaksolta:
Vaihtoehtona viimeisen viiden minuutin aikana olisi käyttää BarrierNodea, joka katkaisee tiedot ennen määritettyä aikaa:
|barrier()
.period(5m)
Esimerkkejä Go-mallien käyttämisestä viestissä
Mallit vastaavat pakkauksen muotoa text.templateAlla on joitain usein kohtaamia pulmia.
tai muuten
Laitamme asiat järjestykseen emmekä provosoi ihmisiä uudelleen tekstillä:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Kaksi numeroa desimaalipilkun jälkeen viestissä
Viestin luettavuuden parantaminen:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Viestin muuttujien laajentaminen
Näytämme viestissä lisätietoja vastataksemme kysymykseen "Miksi se huutaa"?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Ainutlaatuinen hälytystunniste
Tämä on välttämätön asia, kun tiedoissa on useampi kuin yksi ryhmä, muuten vain yksi hälytys luodaan:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Mukautettu käsittelijä
Suuri luettelo käsittelijöistä sisältää exec, jonka avulla voit suorittaa komentosarjasi läpäistyillä parametreilla (stdin) - luovuutta ja ei mitään muuta!
Yksi tavoistamme on pieni Python-skripti ilmoitusten lähettämiseen löysälle.
Aluksi halusimme lähettää valtuussuojatun grafana-kuvan viestissä. Kirjoita sen jälkeen OK säikeeseen samasta ryhmästä edelliseen hälytykseen, älä erillisenä viestinä. Hieman myöhemmin - lisää viestiin yleisin virhe viimeisen X minuutin aikana.
Erillinen aihe on kommunikointi muiden palvelujen kanssa ja hälytyksen käynnistämät toimet (vain jos valvontasi toimii riittävän hyvin).
Esimerkki käsittelijän kuvauksesta, jossa slack_handler.py on itse kirjoittamamme skripti:
dbrp – avainsana, joka ilmaisee, missä tietokannassa + käytännössä komentosarja suoritetaan (dbrp “supplier.” “autogen”)
Jos jokin erätehtävä ei sisällä riviä dbrp:llä, koko palvelu kieltäytyy käynnistymästä ja kirjoittaa siitä rehellisesti lokiin.
Chronografissa päinvastoin tätä riviä ei pitäisi olla; sitä ei hyväksytä rajapinnan kautta ja se tuottaa virheen.
Hakkerointi konttia rakennettaessa: Dockerfile poistuu merkinnällä -1, jos siinä on rivit //.+dbrp, jonka avulla voit heti ymmärtää epäonnistumisen syyn koontiversiota koottaessa.
liity yksi moniin
Esimerkkitehtävä: sinun on otettava palvelun viikon käyttöajasta 95. prosenttipiste, vertaa jokaista minuuttia viimeisestä 10:stä tähän arvoon.
Et voi tehdä yhdestä moneen -liitosta, viimeinen/keskiarvo/mediaani pisteryhmän yli muuttaa solmun virraksi, palautetaan virhe "ei voi lisätä aliyhteensopimattomia reunoja: erä -> virta".
Erän tulosta, joka on muuttuja lambda-lausekkeessa, ei myöskään korvata.
On mahdollisuus tallentaa tarvittavat numerot ensimmäisestä erästä tiedostoon udf:n kautta ja ladata tämä tiedosto sivulatauksella.
Mitä ratkaisimme tällä?
Meillä on noin 100 hotellitoimittajaa, joista jokaisella voi olla useita yhteyksiä, kutsutaan sitä kanavaksi. Näitä kanavia on noin 300, jokainen kanava voi pudota. Kaikista tallennetuista mittareista seuraamme virheprosenttia (pyynnöt ja virheet).
Miksei grafana?
Grafanaan määritetyillä virhehälytyksillä on useita haittoja. Jotkut ovat kriittisiä, joistakin voit sulkea silmäsi tilanteesta riippuen.
Grafana ei osaa laskea mittausten + hälytyksen välillä, mutta tarvitsemme nopeuden (pyynnöt-virheet)/pyynnöt.
Virheet näyttävät ikäviltä:
Ja vähemmän pahaa, kun sitä tarkastellaan onnistuneiden pyyntöjen kanssa:
Okei, voimme laskea hinnan etukäteen palvelussa ennen grafanaa, ja joissain tapauksissa tämä toimii. Mutta ei meidän, koska... jokaiselle kanavalle sen omaa suhdetta pidetään "normaalina", ja hälytykset toimivat staattisten arvojen mukaan (etsimme niitä silmillämme, vaihdamme niitä, jos hälytyksiä tulee usein).
Nämä ovat esimerkkejä "normaalista" eri kanaville:
Jätämme edellisen kohdan huomiotta ja oletamme, että "normaali" kuva on samanlainen kaikille toimittajille. Nyt kaikki on hyvin, ja selviämmekö grafanan hälytyksistä?
Voimme, mutta emme todellakaan halua, koska meidän on valittava yksi seuraavista vaihtoehdoista:
a) tehdä paljon kaavioita jokaiselle kanavalle erikseen (ja tuskallisesti mukana)
b) jätä yksi kaavio, jossa on kaikki kanavat (ja eksy värikkäisiin linjoihin ja mukautettuihin hälytyksiin)
Miten teit sen?
Dokumentaatiossa on jälleen hyvä aloitusesimerkki (Lasketaan liitettyjen sarjojen hintoja), voidaan kurkistaa tai ottaa pohjaksi vastaavissa ongelmissa.
Mitä teimme lopulta:
liitä kaksi sarjaa muutamassa tunnissa ryhmittelemällä kanavien mukaan;
täytä sarja ryhmittäin, jos tietoja ei ollut;
vertaa viimeisen 10 minuutin mediaania aikaisempiin tietoihin;
huudamme jos löydämme jotain;
kirjoitamme lasketut hinnat ja influxdb:ssä tapahtuneet hälytykset;
lähetä hyödyllinen viesti slackille.
Mielestäni onnistuimme saavuttamaan kaiken mitä halusimme lopussa (ja vielä vähän enemmän custom handlereilla) mahdollisimman kauniisti.
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)
Ja mikä on johtopäätös?
Kapacitor on erinomainen suorittamaan valvontahälytyksiä useilla ryhmittelyillä, suorittamaan lisälaskelmia jo tallennettujen mittareiden perusteella, suorittamaan mukautettuja toimintoja ja suorittamaan komentosarjoja (udf).
Pääsyn este ei ole kovin korkea - kokeile sitä, jos grafana tai muut työkalut eivät täysin täytä toiveitasi.