Metrikų apdorojimo „Kapacitor“ gudrybės

Greičiausiai šiandien niekas neklausia, kodėl reikia rinkti paslaugų metriką. Kitas logiškas žingsnis yra surinktos metrikos perspėjimas, kuris praneš apie bet kokius duomenų nukrypimus jums patogiuose kanaluose (paštas, Slack, Telegram). Internetinėje viešbučių rezervavimo tarnyboje Ostrovok.ru visa mūsų paslaugų metrika perkeliama į „InfluxDB“ ir rodoma „Grafana“, ten taip pat sukonfigūruojamas pagrindinis įspėjimas. Užduotims, tokioms kaip „reikia ką nors apskaičiuoti ir su tuo palyginti“, naudojame „Kapacitor“.

Metrikų apdorojimo „Kapacitor“ gudrybės
Kapacitor yra TICK kamino dalis, kuri gali apdoroti metriką iš InfluxDB. Gali sujungti kelis matavimus (sujungti), iš gautų duomenų paskaičiuoti ką nors naudingo, surašyti rezultatą atgal į InfluxDB, išsiųsti perspėjimą į Slack/Telegram/mail.

Visas krūvas yra šaunus ir detalus dokumentacija, tačiau visada bus naudingų dalykų, kurie nėra aiškiai nurodyti vadovuose. Šiame straipsnyje nusprendžiau surinkti keletą tokių naudingų, neakivaizdžių patarimų (apibūdinta pagrindinė TICKscipt sintaksė čia) ir parodykite, kaip juos galima pritaikyti, naudodamiesi vienos iš mūsų problemų sprendimo pavyzdžiu.

Eikime!

float & int, skaičiavimo klaidos

Visiškai standartinė problema, išspręsta per kastas:

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

Naudojamas numatytasis ()

Jei žyma / laukas neužpildytas, atsiras skaičiavimo klaidų:

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

užpildyti sujungimą (vidinis ir išorinis)

Pagal numatytuosius nustatymus prisijungus bus atmesti taškai, kuriuose nėra duomenų (vidiniai).
Su fill('null') bus atliktas išorinis sujungimas, po kurio turėsite atlikti numatytąjį () ir užpildyti tuščias reikšmes:

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

Čia vis dar yra niuansas. Aukščiau pateiktame pavyzdyje, jei viena iš serijų (res1 arba res2) yra tuščia, gauta serija (duomenys) taip pat bus tuščia. Yra keli bilietai šia tema „Github“ (1633, 1871, 6967) – laukiame pataisymų ir šiek tiek kankinamės.

Sąlygų naudojimas skaičiavimuose (jei lambda)

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

Paskutinės penkios to laikotarpio minutės nuo dujotiekio

Pavyzdžiui, turite palyginti pastarųjų penkių minučių vertes su praėjusios savaitės vertes. Galite paimti dvi duomenų paketus į dvi atskiras paketas arba ištraukti dalį duomenų iš didesnio laikotarpio:

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

Alternatyva pastarosioms penkioms minutėms būtų naudoti BarrierNode, kuris nutraukia duomenis prieš nurodytą laiką:

|barrier()
        .period(5m)

Go šablonų naudojimo žinutėje pavyzdžiai

Šablonai atitinka pakuotės formatą tekstas.šablonasŽemiau yra keletas dažnai sutinkamų galvosūkių.

jei-dar

Mes sutvarkome dalykus ir dar kartą nesuaktyviname žmonių teksto:

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

Du skaitmenys po kablelio pranešime

Pranešimo skaitomumo gerinimas:

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

Išplečiami kintamieji pranešime

Norėdami atsakyti į klausimą „Kodėl šaukia“, pranešime pateikiame daugiau informacijos?

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

Unikalus įspėjimo identifikatorius

Tai būtina, kai duomenyse yra daugiau nei viena grupė, kitaip bus sugeneruotas tik vienas įspėjimas:

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

Pasirinktinis tvarkytojas

Dideliame tvarkyklių sąraše yra exec, kuris leidžia vykdyti savo scenarijų su perduotais parametrais (stdin) – kūrybiškumas ir nieko daugiau!

Vienas iš mūsų papročių yra mažas Python scenarijus, skirtas pranešimų siuntimui.
Iš pradžių norėjome žinutėje išsiųsti autorizacija apsaugotą grafanos paveikslėlį. Tada rašykite Gerai ankstesnio tos pačios grupės įspėjimo gijoje, o ne kaip atskirą pranešimą. Šiek tiek vėliau – pridėkite prie pranešimo dažniausiai pasitaikančią klaidą per pastarąsias X minutes.

Atskira tema – bendravimas su kitomis tarnybomis ir bet kokie veiksmai, inicijuoti per įspėjimą (tik tuo atveju, jei jūsų stebėjimas veikia pakankamai gerai).
Dankytojo aprašo pavyzdys, kur slack_handler.py yra mūsų pačių parašytas scenarijus:

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

Kaip derinti?

Galimybė su žurnalo išvestimi

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

Žiūrėti (cli): kapacitor -url host-or-ip:9092 žurnalai lvl=klaida

Parinktis su httpOut

Rodo duomenis dabartiniame dujotiekyje:

|httpOut('something')

Žiūrėti (gauti): host-or-ip:9092/kapacitor/v1/tasks/task_name/something

Vykdymo schema

  • Kiekviena užduotis grąžina vykdymo medį su naudingais skaičiais tokiu formatu grafviz.
  • Paimkite bloką taškas.
  • Įklijuokite jį į peržiūros programą, mėgautis.

Kur dar galima gauti grėblį?

laiko žyma influxdb atrakinimo metu

Pavyzdžiui, nustatome įspėjimą užklausų sumai per valandą (groupBy(1h)) ir norime įrašyti įspėjimą, įvykusį influxdb (kad grafana diagramoje būtų gražiai parodytas problemos faktas).

influxDBOut() atitinkamai įrašys laiko reikšmę nuo įspėjimo iki laiko žymos, taškas diagramoje bus parašytas anksčiau/vėliau nei buvo gautas įspėjimas.

Kai reikalingas tikslumas: išsprendžiame šią problemą iškviesdami pasirinktinį tvarkyklę, kuri įrašys duomenis į influxdb su esama laiko žyma.

dokas, kūrimas ir diegimas

Paleidžiant, kapacitor gali įkelti užduotis, šablonus ir tvarkykles iš katalogo, nurodyto konfigūracijose bloke [load].

Norėdami teisingai sukurti užduotį, jums reikia šių dalykų:

  1. Failo pavadinimas – išplėstas į scenarijaus ID / pavadinimą
  2. Tipas – srautas/partija
  3. dbrp – raktinis žodis, nurodantis, kurioje duomenų bazėje + strategijoje veikia scenarijus (dbrp „tiekėjas“.“autogen“)

Jei kurioje nors paketinėje užduotyje nėra eilutės su dbrp, visa paslauga atsisakys paleisti ir sąžiningai apie tai įrašys žurnale.

Priešingai, chronografe ši eilutė neturėtų būti priimta per sąsają ir sukuria klaidą.

Nulaužimas statant konteinerį: „Dockerfile“ išeina su -1, jei yra eilutės su //.+dbrp, o tai leis iš karto suprasti gedimo priežastį surenkant statybą.

prisijunk vienas prie daugelio

Pavyzdinė užduotis: reikia paimti 95-ąjį savaitės paslaugos veikimo laiko procentilį, kiekvieną minutę iš paskutinių 10 palyginti su šia verte.

Negalite atlikti sujungimo „vienas su daugeliu“, paskutinis / vidurkis / vidurkis per taškų grupę paverčia mazgą srautu, bus grąžinta klaida „negalima pridėti antrinių nesutampančių briaunų: paketas -> srautas“.

Paketo rezultatas, kaip kintamasis lambda išraiškoje, taip pat nepakeičiamas.

Yra galimybė įrašyti reikiamus numerius iš pirmosios partijos į failą per udf ir įkelti šį failą per sideload.

Ką mes su tuo išsprendėme?

Turime apie 100 viešbučių tiekėjų, kiekvienas iš jų gali turėti keletą jungčių, pavadinkime tai kanalu. Šių kanalų yra apie 300, kiekvienas iš jų gali nukristi. Iš visų įrašytų metrikų stebėsime klaidų dažnį (užklausas ir klaidas).

Kodėl ne grafana?

„Grafana“ sukonfigūruoti klaidų įspėjimai turi keletą trūkumų. Kai kurie yra kritiški, prieš kai kuriuos galite užmerkti akis, priklausomai nuo situacijos.

Grafana nemoka skaičiuoti tarp matavimų + perspėjimo, bet mums reikia rodiklio (užklausos-klaidos)/užklausos.

Klaidos atrodo bjauriai:

Metrikų apdorojimo „Kapacitor“ gudrybės

Ir mažiau blogis, kai žiūrima su sėkmingomis užklausomis:

Metrikų apdorojimo „Kapacitor“ gudrybės

Gerai, mes galime iš anksto apskaičiuoti tarifą paslaugoje prieš grafaną ir kai kuriais atvejais tai veiks. Bet ne pas mus, nes... kiekvienam kanalui jo santykis laikomas „normaliu“, o įspėjimai veikia pagal statines reikšmes (jų ieškome akimis, keičiame, jei yra dažni įspėjimai).

Tai yra „įprastų“ skirtingų kanalų pavyzdžiai:

Metrikų apdorojimo „Kapacitor“ gudrybės

Metrikų apdorojimo „Kapacitor“ gudrybės

Mes ignoruojame ankstesnį punktą ir darome prielaidą, kad „įprastas“ vaizdas yra panašus visiems tiekėjams. Dabar viskas gerai, o mes galime išsiversti su įspėjimais grafana?
Galime, bet tikrai nenorime, nes turime pasirinkti vieną iš variantų:
a) sudaryti daug grafikų kiekvienam kanalui atskirai (ir skausmingai juos lydėti)
b) palikti vieną diagramą su visais kanalais (ir pasiklysti spalvingose ​​linijose bei pritaikytuose įspėjimuose)

Metrikų apdorojimo „Kapacitor“ gudrybės

Kaip tu tai padarei?

Vėlgi, dokumentacijoje yra geras pradžios pavyzdys (Įkainių apskaičiavimas visose sujungtose serijose), galima pažvelgti į panašias problemas arba remtis jos pagrindu.

Ką mes padarėme pabaigoje:

  • sujungti dvi serijas per kelias valandas, grupuojant pagal kanalus;
  • užpildykite eilutę pagal grupes, jei nebuvo duomenų;
  • palyginti pastarųjų 10 minučių medianą su ankstesniais duomenimis;
  • šaukiame, jei ką randame;
  • rašome apskaičiuotus tarifus ir įspėjimus, kurie įvyko influxdb;
  • nusiųskite naudingą žinutę slack.

Mano nuomone, mums pavyko pasiekti viską, ko norėjome pabaigoje (ir net šiek tiek daugiau su individualiais tvarkytojais) kuo gražiau.

Jį galite pamatyti github.com kodo pavyzdys и minimali grandinė (grafvizas) gautas scenarijus.

Gauto kodo pavyzdys:

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)

Kokia išvada?

„Kapacitor“ puikiai atlieka stebėjimo įspėjimus su daugybe grupių, atlieka papildomus skaičiavimus pagal jau įrašytą metriką, atlieka pasirinktinius veiksmus ir paleidžia scenarijus (udf).

Įėjimo barjeras nėra labai didelis – išbandykite, jei grafana ar kitos priemonės visiškai nepatenkina jūsų norų.

Šaltinis: www.habr.com

Pirkite patikimą prieglobą svetainėms su DDoS apsauga, VPS VDS serveriais 🔥 Įsigykite patikimą svetainių talpinimą su DDoS apsauga, VPS VDS serveriais | ProHoster