Truket për përpunimin e metrikës në Kapacitor

Me shumë mundësi, sot askush nuk pyet pse është e nevojshme të mblidhen matjet e shërbimit. Hapi tjetër logjik është të vendosni një alarm për matjet e mbledhura, i cili do të njoftojë për çdo devijim në të dhënat në kanalet e përshtatshme për ju (mail, Slack, Telegram). Në shërbimin e rezervimit të hoteleve online Ostrovok.ru të gjitha metrikat e shërbimeve tona derdhen në InfluxDB dhe shfaqen në Grafana, dhe alarmi bazë është konfiguruar gjithashtu atje. Për detyra të tilla si "ju duhet të llogaritni diçka dhe të krahasoni me të", ne përdorim Kapacitor.

Truket për përpunimin e metrikës në Kapacitor
Kapacitori është pjesë e grupit TICK që mund të përpunojë metrikë nga InfluxDB. Mund të lidhë disa matje së bashku (të bashkohet), të llogarisë diçka të dobishme nga të dhënat e marra, të shkruajë rezultatin në InfluxDB, të dërgojë një alarm në Slack/Telegram/mail.

E gjithë pirgja është e lezetshme dhe e detajuar dokumentacionin, por gjithmonë do të ketë gjëra të dobishme që nuk tregohen në mënyrë eksplicite në manuale. Në këtë artikull, vendosa të mbledh një numër këshillash të tilla të dobishme, jo të dukshme (sintaksa bazë e TICKscipt është përshkruar këtu) dhe tregoni se si ato mund të zbatohen duke përdorur një shembull të zgjidhjes së një prej problemeve tona.

Le të shkojë!

float & int, gabime në llogaritje

Një problem absolutisht standard, i zgjidhur përmes kastave:

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

Përdorimi i parazgjedhur ()

Nëse një etiketë/fushë nuk plotësohet, do të ndodhin gabime në llogaritje:

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

plotësoni bashkimin (e brendshme kundër e jashtme)

Si parazgjedhje, bashkimi do të heqë pikat ku nuk ka të dhëna (të brendshme).
Me fill('null'), do të kryhet një bashkim i jashtëm, pas së cilës duhet të bëni një default() dhe të plotësoni vlerat boshe:

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

Këtu ka ende një nuancë. Në shembullin e mësipërm, nëse një nga seritë (res1 ose res2) është bosh, seria (të dhënat) që rezulton gjithashtu do të jetë bosh. Ka disa bileta për këtë temë në Github (1633, 1871, 6967) – presim rregullime dhe vuajtje pak.

Përdorimi i kushteve në llogaritjet (nëse është në lambda)

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

Pesë minutat e fundit nga gazsjellësi për periudhën

Për shembull, duhet të krahasoni vlerat e pesë minutave të fundit me javën e kaluar. Ju mund të merrni dy grupe të dhënash në dy grupe të veçanta ose të nxirrni një pjesë të të dhënave nga një periudhë më e madhe:

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

Një alternativë për pesë minutat e fundit do të ishte përdorimi i një BarrierNode, i cili ndërpret të dhënat përpara kohës së caktuar:

|barrier()
        .period(5m)

Shembuj të përdorimit të modeleve Go në mesazh

Modelet korrespondojnë me formatin nga paketa teksti.shabllonMë poshtë janë disa enigma që hasen shpesh.

nese Tjeter

Ne i vendosim gjërat në rregull dhe nuk i nxisim njerëzit me tekst edhe një herë:

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

Dy shifra pas presjes dhjetore në mesazh

Përmirësimi i lexueshmërisë së mesazhit:

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

Zgjerimi i variablave në mesazh

Ne shfaqim më shumë informacion në mesazh për t'iu përgjigjur pyetjes "Pse po bërtet"?

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

Identifikues unik i alarmit

Kjo është një gjë e nevojshme kur ka më shumë se një grup në të dhëna, përndryshe do të gjenerohet vetëm një alarm:

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

Trajtues me porosi

Lista e madhe e mbajtësve përfshin exec, i cili ju lejon të ekzekutoni skriptin tuaj me parametrat e kaluar (stdin) - kreativitet dhe asgjë më shumë!

Një nga zakonet tona është një skrip i vogël Python për dërgimin e njoftimeve në Slack.
Në fillim, ne donim të dërgonim një fotografi grafana të mbrojtur nga autorizimi në një mesazh. Më pas, shkruani OK në temën e sinjalizimit të mëparshëm nga i njëjti grup, dhe jo si një mesazh i veçantë. Pak më vonë - shtoni mesazhit gabimin më të zakonshëm në X minutat e fundit.

Një temë më vete është komunikimi me shërbime të tjera dhe çdo veprim i nisur nga një alarm (vetëm nëse monitorimi juaj funksionon mjaft mirë).
Një shembull i një përshkrimi të mbajtësit, ku slack_handler.py është skripti ynë i shkruar vetë:

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

Si të korrigjoni gabimet?

Opsioni me dalje regjistri

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

Watch (cli): kapacitor -url host-ose-ip:9092 logs lvl=gabim

Opsioni me httpOut

Shfaq të dhënat në tubacionin aktual:

|httpOut('something')

Shiko (merr): host-ose-ip:9092/kapacitor/v1/tasks/task_name/something

Skema e ekzekutimit

  • Çdo detyrë kthen një pemë ekzekutimi me numra të dobishëm në format grafviz.
  • Merrni një bllok pikë.
  • Ngjite atë në shikues, shijoni.

Ku tjetër mund të merrni një grabujë?

vula kohore në influxdb në kthimin e kthimit

Për shembull, ne vendosim një alarm për shumën e kërkesave në orë (groupBy(1h)) dhe duam të regjistrojmë alarmin që ka ndodhur në influxdb (për të treguar bukur faktin e problemit në grafikun në grafana).

influxDBOut() do të shkruajë vlerën e kohës nga sinjalizimi në vulën kohore; në përputhje me rrethanat, pika në grafik do të shkruhet më herët/më vonë se sinjalizimi i mbërritur.

Kur kërkohet saktësi: ne punojmë rreth këtij problemi duke thirrur një mbajtës të personalizuar, i cili do të shkruajë të dhëna në influxdb me vulën kohore aktuale.

doker, ndërtimi dhe vendosja

Në nisje, kapacitori mund të ngarkojë detyrat, shabllonet dhe mbajtësit nga drejtoria e specifikuar në konfigurimin në bllokun [load].

Për të krijuar saktë një detyrë, ju nevojiten gjërat e mëposhtme:

  1. Emri i skedarit – zgjerohet në id/emër të skriptit
  2. Lloji – transmetim/batch
  3. dbrp – fjalë kyçe për të treguar se në cilën bazë të dhënash + politikë funksionon skripti (dbrp “furnizuesi.” “autogjen”)

Nëse disa detyra grupore nuk përmbajnë një linjë me dbrp, i gjithë shërbimi do të refuzojë të fillojë dhe do të shkruajë sinqerisht për të në regjistër.

Në kronografi, përkundrazi, kjo linjë nuk duhet të ekzistojë; ajo nuk pranohet përmes ndërfaqes dhe gjeneron një gabim.

Hack gjatë ndërtimit të një kontejneri: Dockerfile del me -1 nëse ka linja me //.+dbrp, gjë që do t'ju lejojë të kuptoni menjëherë arsyen e dështimit kur montoni ndërtimin.

bashkohu një me shumë

Shembull i detyrës: duhet të merrni përqindjen e 95-të të kohës së funksionimit të shërbimit për një javë, krahasoni çdo minutë të 10 të fundit me këtë vlerë.

Nuk mund të bësh një bashkim një-me-shumë, e fundit/mesatarja/mediana mbi një grup pikash e kthen nyjen në një transmetim, gabimi "nuk mund të shtohen skajet e mospërputhshme të fëmijëve: grup -> transmetim" do të kthehet.

Rezultati i një grupi, si një variabël në një shprehje lambda, gjithashtu nuk zëvendësohet.

Ekziston një mundësi për të ruajtur numrat e nevojshëm nga grupi i parë në një skedar nëpërmjet udf-së dhe për të ngarkuar këtë skedar përmes ngarkesës anësore.

Çfarë zgjidhëm me këtë?

Kemi rreth 100 furnizues hotelesh, secili prej tyre mund të ketë disa lidhje, le ta quajmë kanal. Janë afërsisht 300 nga këto kanale, secili prej kanaleve mund të bjerë. Nga të gjitha metrikat e regjistruara, ne do të monitorojmë shkallën e gabimit (kërkesat dhe gabimet).

Pse jo grafana?

Sinjalizimet e gabimeve të konfiguruara në Grafana kanë disa disavantazhe. Disa janë kritike, disa mund t'i mbyllni sytë, në varësi të situatës.

Grafana nuk di te llogarise mes matjeve + alarmit, por na duhet nje norme (kerkesa-gabime)/kerkesa.

Gabimet duken të pakëndshme:

Truket për përpunimin e metrikës në Kapacitor

Dhe më pak e keqe kur shikohet me kërkesa të suksesshme:

Truket për përpunimin e metrikës në Kapacitor

Mirë, ne mund të parallogaritim tarifën në shërbim para grafana, dhe në disa raste kjo do të funksionojë. Por jo tek ne, sepse... për secilin kanal, raporti i tij konsiderohet "normal", dhe sinjalizimet funksionojnë sipas vlerave statike (i kërkojmë me sytë tanë, i ndryshojmë nëse ka sinjalizime të shpeshta).

Këta janë shembuj të "normales" për kanale të ndryshme:

Truket për përpunimin e metrikës në Kapacitor

Truket për përpunimin e metrikës në Kapacitor

Ne e injorojmë pikën e mëparshme dhe supozojmë se tabloja "normale" është e ngjashme për të gjithë furnizuesit. Tani çdo gjë është në rregull, dhe ne mund të kalojmë me alarme në grafana?
Ne mundemi, por vërtet nuk duam, sepse duhet të zgjedhim një nga opsionet:
a) bëni shumë grafikë për secilin kanal veç e veç (dhe shoqëroni me dhimbje)
b) lini një tabelë me të gjitha kanalet (dhe humbni në linjat shumëngjyrëshe dhe sinjalizimet e personalizuara)

Truket për përpunimin e metrikës në Kapacitor

Si e keni bërë atë?

Përsëri, ekziston një shembull i mirë fillestar në dokumentacion (Llogaritja e tarifave për seritë e bashkuara), mund të shikohet ose merret si bazë në probleme të ngjashme.

Çfarë bëmë në fund:

  • bashkohu në dy seriale në pak orë, duke u grupuar sipas kanaleve;
  • plotësoni serinë sipas grupit nëse nuk kishte të dhëna;
  • krahasoni mesataren e 10 minutave të fundit me të dhënat e mëparshme;
  • bërtasim nëse gjejmë diçka;
  • ne shkruajmë tarifat e llogaritura dhe sinjalizimet që kanë ndodhur në influxdb;
  • dërgoni një mesazh të dobishëm për Slack.

Sipas mendimit tim, ne arritëm të arrijmë gjithçka që donim të arrinim në fund (dhe edhe pak më shumë me mbajtës të personalizuar) sa më bukur që të ishte e mundur.

Mund të shikoni në github.com shembull kodi и qark minimal (grafviz) skenari që rezulton.

Një shembull i kodit që rezulton:

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)

Cili është përfundimi?

Kapacitori është i shkëlqyeshëm në kryerjen e sinjalizimeve monitoruese me një sërë grupimesh, kryerjen e llogaritjeve shtesë bazuar në metrikat e regjistruara tashmë, kryerjen e veprimeve të personalizuara dhe ekzekutimin e skripteve (udf).

Pengesa e hyrjes nuk është shumë e lartë - provoni nëse grafana ose mjete të tjera nuk i plotësojnë plotësisht dëshirat tuaja.

Burimi: www.habr.com

Shto një koment