Kapacitor-en metrikak prozesatzeko trikimailuak

Seguruenik, gaur egun inork ez du galdetzen zergatik den beharrezkoa zerbitzu-neurriak biltzea. Hurrengo urrats logikoa da bildutako metriketarako alerta bat konfiguratzea, datuen desbideratzeen berri emango duena zuretzat komeni diren kanaletan (posta, Slack, Telegram). Online hotelak erreserbatzeko zerbitzuan Ostrovok.ru Gure zerbitzuen neurketa guztiak InfluxDB-ra isurtzen dira eta Grafanan bistaratzen dira, eta oinarrizko alertak ere bertan konfiguratzen dira. "Zerbait kalkulatu eta horrekin alderatu behar duzu" bezalako zereginetarako, Kapacitor erabiltzen dugu.

Kapacitor-en metrikak prozesatzeko trikimailuak
Kapacitor InfluxDB-ko neurketak prozesatu ditzakeen TICK pilaren parte da. Hainbat neurketa elkarrekin konekta ditzake (elkartu), jasotako datuetatik zerbait erabilgarria kalkulatu, emaitza berriro idatzi InfluxDB-ra, alerta bat bidali Slack/Telegram/mail-era.

Pila osoa polita eta zehatza da dokumentazioa, baina beti egongo dira eskuliburuetan esplizituki adierazten ez diren gauza erabilgarriak. Artikulu honetan, argiak ez diren aholku erabilgarriak biltzea erabaki nuen (TICKscipt-en oinarrizko sintaxia deskribatzen da Hemen) eta erakutsi nola aplika daitezkeen gure problemaren bat ebazteko adibide bat erabiliz.

Goazen!

float & int, kalkulu-erroreak

Arazo guztiz estandarra, kasten bidez konpondua:

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

default() erabiliz

Etiketa/eremu bat betetzen ez bada, kalkulu-erroreak gertatuko dira:

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

bete juntadura (barrukoa vs kanpokoa)

Lehenespenez, elkartzeak daturik ez dagoen puntuak baztertuko ditu (barrukoa).
fill('null') bidez, kanpoko elkarketa bat egingo da, eta ondoren () lehenetsia egin eta balio hutsak bete behar dituzu:

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

Hemen badago oraindik Γ±abardura bat. Goiko adibidean, serieren bat (res1 edo res2) hutsik badago, ondoriozko seriea (datuak) ere hutsik egongo da. Gai honi buruzko hainbat sarrera daude Github-en (1633, 1871, 6967) – konponketen zain eta pixka bat sufritzen ari gara.

Baldintzak erabiltzea kalkuluetan (lambda bada)

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

Azken bost minutuak aldirako kanalizaziotik

Adibidez, azken bost minutuetako balioak aurreko astearekin alderatu behar dituzu. Bi datu-lote har ditzakezu bi sorta ezberdinetan edo datuen zati bat epe handiago batetik atera:

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

Azken bost minutuetarako alternatiba bat BarrierNode erabiltzea izango litzateke, datuak zehaztutako ordua baino lehen mozten dituena:

|barrier()
        .period(5m)

Mezuan Go txantiloiak erabiltzearen adibideak

Txantiloiak paketearen formatuari dagozkio testua.txantiloiaJarraian, maiz aurkitzen diren puzzle batzuk daude.

bestela

Gauzak ordenatzen ditugu eta ez dugu jendea berriro testuarekin aktibatzen:

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

Mezuko hamartarren ondoren bi zifra

Mezuaren irakurgarritasuna hobetzea:

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

Mezuan aldagaiak zabaltzea

Mezuan informazio gehiago erakusten dugu "Zergatik ari da oihuka" galderari erantzuteko?

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

Alerta-identifikatzaile bakarra

Hau beharrezkoa da datuetan talde bat baino gehiago dagoenean, bestela alerta bakarra sortuko da:

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

Kudeatzaile pertsonalizatuak

Kudeatzaileen zerrenda handiak exec barne hartzen du, zure script-a gainditutako parametroekin (stdin) exekutatzeko aukera ematen duena - sormena eta ezer gehiago!

Gure ohituretako bat slack-era jakinarazpenak bidaltzeko Python script txiki bat da.
Hasieran, baimen bidez babestutako grafana argazki bat bidali nahi genuen mezu batean. Ondoren, idatzi OK harian talde bereko aurreko alertarako, eta ez aparteko mezu gisa. Pixka bat geroago - gehitu mezuari azken X minutuetako akats ohikoena.

Aparteko gaia beste zerbitzu batzuekin komunikazioa eta alerta batek abiarazitako edozein ekintza dira (zure monitorizazioak behar bezain ondo funtzionatzen badu).
Kudeatzailearen deskribapenaren adibide bat, non slack_handler.py gure norberak idatzitako scripta den:

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

Nola arazketa?

Erregistro irteera duen aukera

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

Watch (cli): kapacitor -url ostalari-edo-ip:9092 erregistroak lvl=error

httpOut-ekin aukera

Uneko kanalizazioan datuak erakusten ditu:

|httpOut('something')

Ikusi (lortu): ostalari-edo-ip:9092/kapacitor/v1/tasks/task_name/something

Exekuzio-diagrama

  • Zeregin bakoitzak exekuzio zuhaitz bat itzultzen du formatuan zenbaki erabilgarriak dituena grafikoa.
  • Hartu bloke bat dot.
  • Itsatsi ikuslean, gozatu.

Non bestela lor dezakezu arrastel bat?

denbora-zigilua influxdb-n idazketan

Esaterako, orduko eskaeren baturarako alerta bat ezarri dugu (groupBy(1h)) eta influxdb-n gertatutako alerta erregistratu nahi dugu (grafanaren grafikoan arazoaren errealitatea ederki erakusteko).

influxDBOut() denbora-balioa alertatik denbora-zigilura idatziko du; horren arabera, grafikoko puntua alerta iritsi baino lehenago/beranduago idatziko da.

Zehaztasuna behar denean: arazo honi aurre egiten diogu kudeatzaile pertsonalizatu bati deituz, eta honek influxdb-n datuak idatziko ditu uneko denbora-zigiluarekin.

docker, eraikitzea eta inplementatzea

Abiaraztean, kapacitor-ek zereginak, txantiloiak eta kudeatzaileak karga ditzake [load] blokeko konfigurazioan zehaztutako direktoriotik.

Zeregin bat behar bezala sortzeko, gauza hauek behar dituzu:

  1. Fitxategiaren izena - script id/izena zabaldu da
  2. Mota - korronte/sorta
  3. dbrp - script-a zein datu-base + politikatan exekutatzen den adierazteko gako-hitza (dbrp "hornitzailea". "autogen")

Batch atazaren batek dbrp-rekin lerrorik ez badu, zerbitzu osoak uko egingo dio abiarazteari eta zintzotasunez idatziko du erregistroan.

Chronograf-en, aitzitik, lerro honek ez luke existitu behar; ez da interfazearen bidez onartzen eta errore bat sortzen du.

Hack edukiontzi bat eraikitzean: Dockerfile -1-rekin irteten da //.+dbrp-rekin lerroak badaude, eta horrek hutsegitearen arrazoia berehala ulertuko dizu eraikuntza muntatzean.

batu bat askorekin

Egiteko adibidea: zerbitzuaren funtzionamendu-denboraren 95. pertzentilea hartu behar duzu astebetez, alderatu azken 10etako minutu bakoitza balio honekin.

Ezin duzu bat-bateko batuketa bat egin, puntu talde baten gainean azkena/batez bestekoa/mediana nodoa korronte bihurtzen du, "Ezin dira gehitu ertz desegokian seme-alabak: batch -> korrontea" errorea itzuliko da.

Batch baten emaitza, lambda adierazpen batean aldagai gisa, ere ez da ordezkatzen.

Lehen lotetik beharrezko zenbakiak udf bidez fitxategi batera gordetzeko eta fitxategi hau sideload bidez kargatzeko aukera dago.

Zer konpondu dugu honekin?

100 hotel hornitzaile inguru ditugu, horietako bakoitzak hainbat konexio izan ditzake, dei diezaiogun kanala. Gutxi gorabehera 300 kanal daude, kanal bakoitza eror daiteke. Grabatutako neurketa guztien artean, errore-tasa (eskaerak eta akatsak) kontrolatuko dugu.

Zergatik ez grafana?

Grafanan konfiguratutako errore-alertak hainbat desabantaila dituzte. Batzuk kritikoak dira, beste batzuk begiak itxi ditzakezu, egoeraren arabera.

Grafanak ez daki neurketen + alertaren artean kalkulatzen, baina tasa bat (eskaerak-erroreak)/eskaerak behar dugu.

Akatsak txarrak dirudite:

Kapacitor-en metrikak prozesatzeko trikimailuak

Eta gaitz gutxiago eskaera arrakastatsuekin ikusten denean:

Kapacitor-en metrikak prozesatzeko trikimailuak

Ados, grafana baino lehen zerbitzuan tarifa aldez aurretik kalkulatu dezakegu, eta kasu batzuetan honek funtzionatuko du. Baina gurean ez, zeren... kanal bakoitzerako bere ratioa "normaltzat" jotzen da eta alertak balio estatikoen arabera funtzionatzen du (begiekin bilatzen ditugu, maiz alertak badaude aldatu).

Hauek dira kanal ezberdinetarako "normal" adibideak:

Kapacitor-en metrikak prozesatzeko trikimailuak

Kapacitor-en metrikak prozesatzeko trikimailuak

Aurreko puntuari jaramonik egiten ez diogu eta irudi β€œnormala” hornitzaile guztientzat antzekoa dela suposatzen dugu. Orain dena ondo dago, eta abisuekin aurrera egin gaitezke grafana-n?
Ahal dugu, baina benetan ez dugu nahi, aukeretako bat aukeratu behar dugulako:
a) egin grafiko asko kanal bakoitzerako bereizita (eta minez lagundu)
b) utzi taula bat kanal guztiekin (eta galdu kolore koloretsuetan eta pertsonalizatutako alertetan)

Kapacitor-en metrikak prozesatzeko trikimailuak

Nola egin duzu?

Berriz ere, dokumentazioan hasierako adibide on bat dago (Lotutako serieen tasak kalkulatzea), antzeko arazoetan begiratu edo oinarritzat har daiteke.

Zer egin genuen azkenean:

  • batu bi serie ordu gutxitan, kanalen arabera taldekatuz;
  • serieak taldeka bete daturik ez bazegoen;
  • alderatu azken 10 minutuetako mediana aurreko datuekin;
  • zerbait aurkitzen badugu oihukatzen dugu;
  • influxdb-n gertatu diren kalkulatutako tarifak eta alertak idazten ditugu;
  • bidali mezu erabilgarria Slack-i.

Nire ustez, amaieran lortu nahi genuen guztia (eta are gehiago kudeatzaile pertsonalizatuekin) ahalik eta ederren lortzea lortu genuen.

Github.com webgunera begiratu dezakezu kodearen adibidea ΠΈ zirkuitu minimoa (grafikoa) ondoriozko gidoia.

Sortutako kodearen adibide bat:

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)

Zein da ondorioa?

Kapacitor bikaina da taldekatze mordo batekin monitorizazio-alertak egiteko, dagoeneko grabatutako metriketan oinarritutako kalkulu osagarriak egiteko, ekintza pertsonalizatuak egiteko eta script-ak (udf) exekutatzen.

Sartzeko oztopoa ez da oso altua - proba ezazu grafana edo beste tresna batzuek zure nahiak guztiz asetzen ez baditu.

Iturria: www.habr.com

Gehitu iruzkin berria