Metrikas apstrādes triki programmā Kapacitor

Visticamāk, Å”odien neviens nejautā, kāpēc ir jāvāc servisa rādÄ«tāji. Nākamais loÄ£iskais solis ir ievāktajiem rādÄ«tājiem iestatÄ«t brÄ«dinājumu, kas jums ērtos kanālos (pasts, Slack, Telegram) ziņos par jebkādām novirzēm datos. TieÅ”saistes viesnÄ«cu rezervÄ“Å”anas pakalpojumā Ostrovok.ru visi mÅ«su pakalpojumu rādÄ«tāji tiek ievietoti InfluxDB un parādÄ«ti Grafana, un tur ir arÄ« konfigurēti pamata brÄ«dinājumi. Tādiem uzdevumiem kā ā€œjums kaut kas jāaprēķina un jāsalÄ«dzina ar toā€, mēs izmantojam Kapacitor.

Metrikas apstrādes triki programmā Kapacitor
Kapacitor ir daļa no TICK steka, kas var apstrādāt metriku no InfluxDB. Tas var savienot vairākus mērījumus kopā (savienoties), aprēķināt kaut ko noderīgu no saņemtajiem datiem, atrakstīt rezultātu atpakaļ uz InfluxDB, nosūtīt brīdinājumu uz Slack/Telegram/mail.

Visa kaudze ir forÅ”a un detalizēta dokumentācija, taču vienmēr bÅ«s noderÄ«gas lietas, kas rokasgrāmatās nav skaidri norādÄ«tas. Å ajā rakstā es nolēmu apkopot vairākus Ŕādus noderÄ«gus, nepārprotamus padomus (ir aprakstÄ«ta TICKscipt pamata sintakse Å”eit) un parādiet, kā tos var pielietot, izmantojot piemēru, kā atrisināt kādu no mÅ«su problēmām.

Iesim!

float & int, aprēķinu kļūdas

Absolūti standarta problēma, kas atrisināta caur kastām:

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

Izmantojot noklusējuma ()

Ja atzīme/lauks nav aizpildīts, aprēķinos var rasties kļūdas:

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

aizpildiet savienojumu (iekŔējais vai ārējais)

Pēc noklusējuma pievienoÅ”anās atmetÄ«s punktus, kur nav datu (iekŔējie).
Izmantojot fill('null'), tiks veikta ārēja savienoÅ”ana, pēc kuras jums ir jāveic noklusējuma () un jāaizpilda tukŔās vērtÄ«bas:

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

Å eit joprojām ir nianse. IepriekÅ” minētajā piemērā, ja viena no sērijām (res1 vai res2) ir tukÅ”a, iegÅ«tā sērija (dati) arÄ« bÅ«s tukÅ”a. Vietnē Github ir vairākas biļetes par Å”o tēmu (1633, 1871, 6967) ā€“ gaidām labojumus un nedaudz cieÅ”am.

NosacÄ«jumu izmantoÅ”ana aprēķinos (ja lambda)

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

Pēdējās piecas minūtes no cauruļvada periodam

Piemēram, jums ir jāsalÄ«dzina pēdējo piecu minÅ«Å”u vērtÄ«bas ar iepriekŔējo nedēļu. Varat ņemt divas datu paketes divās atseviŔķās paketēs vai iegÅ«t daļu datu no lielāka perioda:

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

Alternatīva pēdējām piecām minūtēm būtu izmantot BarrierNode, kas izslēdz datus pirms norādītā laika:

|barrier()
        .period(5m)

Go veidņu izmantoÅ”anas piemēri ziņojumā

Veidnes atbilst formātam no iepakojuma text.templateZemāk ir dažas bieži sastopamas mīklas.

ja-vēl

Mēs sakārtojam lietas un vairs neizraisām cilvēkus ar tekstu:

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

Divi cipari aiz komata ziņojumā

Ziņojuma lasāmÄ«bas uzlaboÅ”ana:

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

MainÄ«go izvērÅ”ana ziņojumā

Ziņojumā parādām vairāk informācijas, lai atbildētu uz jautājumu ā€œKāpēc tā kliedzā€?

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

Unikāls brīdinājuma identifikators

Tas ir nepiecieÅ”ams, ja datos ir vairāk nekā viena grupa, pretējā gadÄ«jumā tiks Ä£enerēts tikai viens brÄ«dinājums:

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

Pielāgots apstrādātājs

Lielajā apdarinātāju sarakstā ir iekļauts exec, kas ļauj izpildīt savu skriptu ar nodotajiem parametriem (stdin) - radoŔums un nekas vairāk!

Viena no mÅ«su paražām ir neliels Python skripts paziņojumu nosÅ«tÄ«Å”anai uz atslābumu.
Sākumā mēs gribējām nosÅ«tÄ«t ar autorizāciju aizsargātu grafana attēlu ziņojumā. Pēc tam ierakstiet OK pavedienā uz iepriekŔējo brÄ«dinājumu no tās paÅ”as grupas, nevis kā atseviŔķu ziņojumu. Nedaudz vēlāk - pievienojiet ziņojumam biežāko kļūdu pēdējo X minÅ«Å”u laikā.

AtseviŔķa tēma ir saziņa ar citiem dienestiem un jebkādas darbÄ«bas, ko ierosina brÄ«dinājums (tikai tad, ja jÅ«su uzraudzÄ«ba darbojas pietiekami labi).
Apdarinātāja apraksta piemērs, kur slack_handler.py ir mÅ«su paÅ”u rakstÄ«tais skripts:

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"]

Kā atkļūdot?

Iespēja ar žurnāla izvadi

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

Skatīties (cli): kapacitor -url resursdators vai IP:9092 žurnāli lvl=kļūda

Opcija ar httpOut

Rāda datus paÅ”reizējā konveijerā:

|httpOut('something')

Skatīties (saņemt): resursdators vai IP:9092/kapacitor/v1/tasks/task_name/something

Izpildes shēma

  • Katrs uzdevums atgriež izpildes koku ar noderÄ«giem skaitļiem formātā grafviz.
  • Paņemiet bloku punkts.
  • IelÄ«mējiet to skatÄ«tājā, Izbaudi.

Kur vēl var dabūt grābekli?

laika zīmogs influxdb ierakstīŔanas laikā

Piemēram, mēs iestatām brīdinājumu par pieprasījumu summu stundā (groupBy(1h)) un vēlamies reģistrēt brīdinājumu, kas notika influxdb (lai skaisti parādītu problēmas faktu grafana diagrammā).

influxDBOut() ierakstīs laika vērtību no brīdinājuma līdz laika zīmogam; attiecīgi punkts diagrammā tiks ierakstīts agrāk/vēlāk nekā brīdinājums ir saņemts.

Ja nepiecieÅ”ama precizitāte: mēs apstrādājam Å”o problēmu, izsaucot pielāgotu apdarinātāju, kas ierakstÄ«s datus influxdb ar paÅ”reizējo laikspiedolu.

doks, uzbūve un izvietoŔana

StartÄ“Å”anas laikā kapacitor var ielādēt uzdevumus, veidnes un apstrādātājus no direktorija, kas norādÄ«ta konfigurācijā blokā [load].

Lai pareizi izveidotu uzdevumu, ir nepiecieŔamas Ŕādas lietas:

  1. Faila nosaukums ā€” izvērsts skripta ID/nosaukumā
  2. Tips ā€“ straume/partija
  3. dbrp ā€” atslēgvārds, kas norāda, kurā datu bāzē + politikā skripts darbojas (dbrp ā€œpiegādātājs.ā€ ā€œautogenā€)

Ja kādā pakeŔu uzdevumā nav rindas ar dbrp, viss pakalpojums atteiksies sākt un godīgi ierakstīs par to žurnālā.

Gluži pretēji, hronogrāfijā Å”ai lÄ«nijai nevajadzētu pastāvēt; tā netiek pieņemta caur interfeisu un rada kļūdu.

Uzlauzt, veidojot konteineru: Dockerfile iziet ar -1, ja ir rindas ar //.+dbrp, kas ļaus jums uzreiz saprast neveiksmes iemeslu, montējot būvi.

pievienoties viens pret daudziem

Uzdevuma piemērs: jums ir jāņem pakalpojuma darbÄ«bas laika nedēļas 95. procentile, salÄ«dziniet katru minÅ«ti no pēdējām 10 ar Å”o vērtÄ«bu.

JÅ«s nevarat veikt savienojumu viens pret daudziem, pēdējais/vidējais/vidējais punktu grupā pārvērÅ” mezglu par straumi, tiks atgriezta kļūda ā€œnevar pievienot pakārtotās malas neatbilstoÅ”as: partija ā€”> straumeā€.

Arī partijas rezultāts kā mainīgais lambda izteiksmē netiek aizstāts.

Ir iespēja saglabāt nepiecieÅ”amos numurus no pirmās partijas failā, izmantojot udf, un ielādēt Å”o failu, izmantojot sānu ielādi.

Ko mēs ar Å”o atrisinājām?

Mums ir ap 100 viesnīcu piegādātāju, katram var būt vairāki pieslēgumi, sauksim to par kanālu. Šo kanālu ir aptuveni 300, katrs no tiem var nokrist. No visiem reģistrētajiem rādītājiem mēs uzraudzīsim kļūdu līmeni (pieprasījumus un kļūdas).

Kāpēc ne grafana?

Grafana konfigurētajiem kļūdu brīdinājumiem ir vairāki trūkumi. Daži no tiem ir kritiski, pret dažiem jūs varat aizvērt acis atkarībā no situācijas.

Grafana neprot rēķināt starp mērījumiem + brīdinājumiem, bet mums vajag ātrumu (pieprasījumi-kļūdas)/pieprasījumi.

Kļūdas izskatās nepatīkamas:

Metrikas apstrādes triki programmā Kapacitor

Un mazāk ļaunuma, ja to aplūko ar veiksmīgiem pieprasījumiem:

Metrikas apstrādes triki programmā Kapacitor

Labi, mēs varam iepriekÅ” aprēķināt likmi pakalpojumā pirms grafana, un dažos gadÄ«jumos tas darbosies. Bet ne pie mums, jo... katram kanālam tā attiecÄ«ba tiek uzskatÄ«ta par ā€œnormāluā€, un brÄ«dinājumi darbojas saskaņā ar statiskām vērtÄ«bām (mēs tos meklējam ar acÄ«m, mainām, ja ir bieži brÄ«dinājumi).

Å ie ir ā€œparastoā€ dažādu kanālu piemēri:

Metrikas apstrādes triki programmā Kapacitor

Metrikas apstrādes triki programmā Kapacitor

Mēs ignorējam iepriekŔējo punktu un pieņemam, ka ā€œparastāā€ aina visiem piegādātājiem ir lÄ«dzÄ«ga. Tagad viss ir kārtÄ«bā, un mēs varam iztikt ar brÄ«dinājumiem grafana?
Mēs varam, bet mēs ļoti nevēlamies, jo mums ir jāizvēlas viena no iespējām:
a) izveidojiet daudz grafiku katram kanālam atseviŔķi (un sāpīgi pavadiet tos)
b) atstājiet vienu diagrammu ar visiem kanāliem (un pazūdiet krāsainajās līnijās un pielāgotajos brīdinājumos)

Metrikas apstrādes triki programmā Kapacitor

Kā tu to izdarīji?

ArÄ« dokumentācijā ir labs sākuma piemērs (Likmes aprēķināŔana apvienotajās sērijās), var aplÅ«kot vai ņemt par pamatu lÄ«dzÄ«gās problēmās.

Ko mēs beigās izdarījām:

  • pievienoties divām sērijām dažu stundu laikā, grupējot pēc kanāliem;
  • aizpildiet sēriju pa grupām, ja datu nebija;
  • salÄ«dzināt pēdējo 10 minÅ«Å”u mediānu ar iepriekŔējiem datiem;
  • mēs kliedzam, ja kaut ko atrodam;
  • mēs rakstām aprēķinātās likmes un brÄ«dinājumus, kas radās influxdb;
  • nosÅ«tiet noderÄ«gu ziņu slack.

Manuprāt, mums izdevās beigās sasniegt visu, ko vēlējāmies iegūt (un pat nedaudz vairāk ar pielāgotiem hendleriem) pēc iespējas skaistāk.

JÅ«s varat apskatÄ«t github.com koda piemērs Šø minimālā ķēde (graphviz) iegÅ«tais skripts.

Iegūtā koda piemērs:

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)

Kāds ir secinājums?

Kapacitor lieliski spēj veikt uzraudzības brīdinājumus ar virkni grupējumu, veikt papildu aprēķinus, pamatojoties uz jau reģistrētiem rādītājiem, veikt pielāgotas darbības un palaist skriptus (udf).

IekļūŔanas barjera nav Ä«paÅ”i augsta ā€“ izmēģiniet to, ja grafana vai citi instrumenti pilnÄ«bā neapmierina jÅ«su vēlmes.

Avots: www.habr.com

Pievieno komentāru