Triki za obdelavo metrik v Kapacitorju

Najverjetneje se danes nihče ne sprašuje, zakaj je potrebno zbirati storitvene metrike. Naslednji logični korak je nastavitev opozorila za zbrane metrike, ki vas bo obvestilo o morebitnih odstopanjih v podatkih na kanalih, ki vam ustrezajo (pošta, Slack, Telegram). V spletni storitvi za rezervacije hotelov Ostrovok.ru vse metrike naših storitev so zlite v InfluxDB in prikazane v Grafani, tam pa je konfigurirano tudi osnovno opozarjanje. Za naloge, kot je "nekaj morate izračunati in s tem primerjati," uporabljamo Kapacitor.

Triki za obdelavo metrik v Kapacitorju
Kapacitor je del sklada TICK, ki lahko obdeluje metrike iz InfluxDB. Lahko poveže več meritev skupaj (join), izračuna kaj koristnega iz prejetih podatkov, zapiše rezultat nazaj v InfluxDB, pošlje opozorilo na Slack/Telegram/mail.

Celoten sklad je kul in podroben dokumentacijo, a vedno se bodo našle uporabne stvari, ki v priročnikih niso izrecno navedene. V tem članku sem se odločil zbrati več takšnih uporabnih, neočitnih nasvetov (opisana je osnovna sintaksa TICKscipta tukaj) in pokažite, kako jih je mogoče uporabiti na primeru reševanja enega od naših problemov.

Gremo!

float & int, računske napake

Absolutno standarden problem, rešen s kastami:

var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))

Uporaba default()

Če oznaka/polje ni izpolnjeno, pride do napak pri izračunu:

|default()
        .tag('status', 'empty')
        .field('value', 0)

izpolnite spoj (notranji proti zunanji)

Privzeto bo pridružitev zavrgla točke, kjer ni podatkov (notranje).
S fill('null') bo izvedeno zunanje združevanje, po katerem morate narediti default() in izpolniti prazne vrednosti:

var data = res1
    |join(res2)
        .as('res1', 'res2)
        .fill('null')
    |default()
        .field('res1.value', 0.0)
        .field('res2.value', 100.0)

Tukaj je še vedno odtenek. V zgornjem primeru, če je ena od serij (res1 ali res2) prazna, bodo prazne tudi nastale serije (podatki). Na Githubu je več vstopnic na to temo (1633, 1871, 6967) – čakamo na popravke in malo trpimo.

Uporaba pogojev v izračunih (če so v lambda)

|eval(lambda: if("value" > 0, true, false)

Zadnjih pet minut od cevovoda za obdobje

Na primer, morate primerjati vrednosti zadnjih petih minut s prejšnjim tednom. Lahko vzamete dva paketa podatkov v dveh ločenih paketih ali izvlečete del podatkov iz večjega obdobja:

 |where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)

Alternativa za zadnjih pet minut bi bila uporaba BarrierNode, ki prekine podatke pred določenim časom:

|barrier()
        .period(5m)

Primeri uporabe predlog Go v sporočilu

Predloge ustrezajo formatu iz paketa besedilo.predlogaSpodaj je nekaj pogostih ugank.

če potem

Stvari spravimo v red in ljudi ne sprožamo več z besedilom:

|alert()
    ...
    .message(
        '{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
    )

Dve števki za decimalno vejico v sporočilu

Izboljšanje berljivosti sporočila:

|alert()
    ...
    .message(
        'now value is {{ index .Fields "value" | printf "%0.2f" }}'
    )

Razširitev spremenljivk v sporočilu

V sporočilu prikažemo več informacij za odgovor na vprašanje "Zakaj kriči"?

var warnAlert = 10
  |alert()
    ...
    .message(
       'Today value less then '+string(warnAlert)+'%'
    )

Enolični identifikator opozorila

To je nujno, če je v podatkih več kot ena skupina, sicer bo ustvarjeno samo eno opozorilo:

|alert()
      ...
      .id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')

Obdelovalci po meri

Velik seznam rokovalcev vključuje exec, ki vam omogoča izvajanje skripta s posredovanimi parametri (stdin) - ustvarjalnost in nič več!

Eden od naših običajev je majhen skript Python za pošiljanje obvestil v slack.
Sprva smo želeli v sporočilu poslati avtorizacijsko zaščiteno sliko grafana. Nato napišite OK v nit prejšnjega opozorila iz iste skupine in ne kot ločeno sporočilo. Malo kasneje - dodajte sporočilu najpogostejšo napako v zadnjih X minutah.

Posebna tema je komunikacija z drugimi storitvami in vsa dejanja, ki jih sproži opozorilo (samo če vaš nadzor deluje dovolj dobro).
Primer opisa upravljalnika, kjer je slack_handler.py naš samonapisani skript:

topic: slack_graph
id: slack_graph.alert
match: level() != INFO AND changed() == TRUE
kind: exec
options:
  prog: /sbin/slack_handler.py
  args: ["-c", "CHANNELID", "--graph", "--search"]

Kako odpraviti napake?

Možnost z izpisom dnevnika

|log()
      .level("error")
      .prefix("something")

Oglejte si (cli): kapacitor -url gostitelj-ali-ip:9092 dnevniki lvl=napaka

Možnost s httpOut

Prikazuje podatke v trenutnem cevovodu:

|httpOut('something')

Oglejte si (dobite): gostitelj-ali-ip:9092/kapacitor/v1/tasks/task_name/something

Shema izvedbe

  • Vsaka naloga vrne izvedbeno drevo z uporabnimi številkami v formatu grafviz.
  • Vzemi blok pika.
  • Prilepite v pregledovalnik, uživajte.

Kje drugje se da dobiti grablje?

časovni žig v influxdb pri povratnem pisanju

Na primer, nastavimo opozorilo za vsoto zahtev na uro (groupBy(1h)) in želimo zabeležiti opozorilo, ki se je pojavilo v influxdb (da lepo prikažemo dejstvo težave na grafu v grafani).

influxDBOut() bo zapisal časovno vrednost iz opozorila v časovni žig; v skladu s tem bo točka na grafikonu zapisana prej/pozneje, kot je prispelo opozorilo.

Ko je zahtevana natančnost: to težavo rešimo tako, da pokličemo obravnavo po meri, ki bo zapisal podatke v influxdb s trenutnim časovnim žigom.

docker, gradnja in uvajanje

Ob zagonu lahko kapacitor naloži naloge, predloge in upravljalnike iz imenika, določenega v konfiguraciji v bloku [load].

Če želite pravilno ustvariti nalogo, potrebujete naslednje:

  1. Ime datoteke – razširjeno v ID/ime skripta
  2. Tip – tok/serija
  3. dbrp – ključna beseda, ki označuje, v kateri zbirki podatkov + pravilnik se izvaja skript (dbrp “supplier.” “autogen”)

Če neka paketna naloga ne vsebuje vrstice z dbrp, se bo celotna storitev zavrnila zagon in bo o tem pošteno zapisala v dnevnik.

Nasprotno, v kronografu ta vrstica ne bi smela obstajati, ni sprejeta prek vmesnika in ustvari napako.

Hack pri gradnji vsebnika: Dockerfile se zapre z -1, če obstajajo vrstice z //.+dbrp, kar vam bo omogočilo, da takoj razumete razlog za napako pri sestavljanju gradnje.

pridruži se enemu mnogim

Primer naloge: vzeti morate 95. percentil časa delovanja storitve za en teden, primerjati vsako minuto zadnjih 10 s to vrednostjo.

Ne morete izvesti združevanja ena proti več, zadnja/povprečna vrednost/mediana nad skupino točk spremeni vozlišče v tok, vrnjena bo napaka »ni mogoče dodati podrejenih neujemajočih se robov: serija -> tok«.

Rezultat serije kot spremenljivka v lambda izrazu prav tako ni nadomeščen.

Obstaja možnost shranjevanja potrebnih številk iz prve serije v datoteko prek udf in nalaganje te datoteke prek stranskega prenosa.

Kaj smo s tem rešili?

Imamo približno 100 hotelskih dobaviteljev, vsak ima lahko več povezav, recimo temu kanal. Teh kanalov je približno 300, vsak lahko odpade. Od vseh zabeleženih metrik bomo spremljali stopnjo napak (zahtev in napak).

Zakaj ne grafana?

Opozorila o napakah, konfigurirana v Grafani, imajo več pomanjkljivosti. Nekateri so kritični, pred nekaterimi si lahko zatisnete oči, odvisno od situacije.

Grafana ne zna računati med meritvami + alarmiranjem, ampak rabimo stopnjo (zahteve-napake)/zahteve.

Napake izgledajo grdo:

Triki za obdelavo metrik v Kapacitorju

In manjše zlo, če gledamo z uspešnimi zahtevami:

Triki za obdelavo metrik v Kapacitorju

V redu, lahko vnaprej izračunamo stopnjo v storitvi pred grafano in v nekaterih primerih bo to delovalo. Ampak ne pri nas, ker... za vsak kanal se njegovo razmerje šteje za "normalno", opozorila pa delujejo glede na statične vrednosti (iščemo jih z očmi, jih spremenimo, če so pogosta opozorila).

To so primeri »normalnega« za različne kanale:

Triki za obdelavo metrik v Kapacitorju

Triki za obdelavo metrik v Kapacitorju

Prejšnjo točko zanemarimo in predpostavljamo, da je »normalna« slika podobna za vse dobavitelje. Zdaj je vse v redu in lahko preživimo z opozorili v grafani?
Lahko, a res nočemo, saj moramo izbrati eno od možnosti:
a) narediti veliko grafov za vsak kanal posebej (in jih boleče spremljati)
b) pustite en grafikon z vsemi kanali (in se izgubite v barvitih črtah in prilagojenih opozorilih)

Triki za obdelavo metrik v Kapacitorju

Kako ti je uspelo?

Spet je dober začetni primer v dokumentaciji (Izračun stopenj v združenih serijah), lahko pokukamo ali vzamemo za osnovo pri podobnih problemih.

Kaj smo naredili na koncu:

  • združite dve seriji v nekaj urah, združevanje po kanalih;
  • izpolnite serije po skupinah, če ni bilo podatkov;
  • primerjajte mediano zadnjih 10 minut s prejšnjimi podatki;
  • kričimo, če kaj najdemo;
  • pišemo izračunane stopnje in opozorila, ki so se zgodila v influxdb;
  • pošlji uporabno sporočilo na slack.

Po mojem mnenju nam je vse, kar smo želeli na koncu (in še malo več s custom handlerji) uspelo doseči čim lepše.

Lahko pogledate na github.com primer kode и minimalno vezje (grafviz) dobljeni skript.

Primer nastale kode:

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)

Kakšen je zaključek?

Kapacitor je odličen pri izvajanju spremljanja-opozoril s kopico skupin, izvajanju dodatnih izračunov na podlagi že zabeleženih metrik, izvajanju dejanj po meri in izvajanju skriptov (udf).

Ovira za vstop ni zelo visoka - poskusite, če grafana ali druga orodja ne zadovoljijo vaših želja v celoti.

Vir: www.habr.com

Dodaj komentar