Bragðarefur til að vinna úr mæligildum í Kapacitor

Líklegast spyr enginn í dag hvers vegna nauðsynlegt sé að safna þjónustumælingum. Næsta rökrétta skref er að setja upp viðvörun fyrir söfnuðu mæligildi, sem mun tilkynna um öll frávik í gögnum á rásum sem henta þér (póstur, Slack, Telegram). Í hótelbókunarþjónustu á netinu Ostrovok.ru öllum mæligildum þjónustu okkar er hellt inn í InfluxDB og birt í Grafana og grunnviðvörun er einnig stillt þar. Fyrir verkefni eins og „þú þarft að reikna eitthvað og bera saman við það,“ notum við Kapacitor.

Bragðarefur til að vinna úr mæligildum í Kapacitor
Kapacitor er hluti af TICK staflanum sem getur unnið úr mælingum frá InfluxDB. Það getur tengt nokkrar mælingar saman (join), reiknað eitthvað gagnlegt út frá mótteknum gögnum, skrifað niðurstöðuna aftur í InfluxDB, sent viðvörun til Slack/Telegram/mail.

Allur staflinn er flottur og ítarlegur skjöl, en það verða alltaf gagnlegir hlutir sem eru ekki sérstaklega tilgreindir í handbókunum. Í þessari grein ákvað ég að safna mörgum slíkum gagnlegum, óaugljósum ráðum (grunnsetningafræði TICKscipt er lýst hér) og sýndu hvernig hægt er að beita þeim með því að nota dæmi um að leysa eitt af vandamálum okkar.

Við skulum fara!

flot & int, reikningsvillur

Algerlega staðlað vandamál, leyst í gegnum kasta:

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

Að nota sjálfgefið()

Ef merki/reitur er ekki fyllt út munu reiknivillur eiga sér stað:

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

fylltu inn join (innri vs ytri)

Sjálfgefið er að join fleygir punktum þar sem engin gögn eru til (innri).
Með fill('null') verður ytri tenging framkvæmd, eftir það þarftu að gera sjálfgefið() og fylla út tóm gildin:

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

Hér er enn blæbrigði. Í dæminu hér að ofan, ef ein af röðunum (res1 eða res2) er tóm, verður röðin (gögnin) sem myndast einnig tóm. Það eru nokkrir miðar um þetta efni á Github (1633, 1871, 6967) – við bíðum eftir lagfæringum og þjáumst aðeins.

Að nota skilyrði í útreikningum (ef í lambda)

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

Síðustu fimm mínútur frá leiðslu fyrir tímabilið

Til dæmis þarftu að bera saman gildi síðustu fimm mínútna við fyrri viku. Þú getur tekið tvær lotur af gögnum í tveimur aðskildum lotum eða dregið út hluta af gögnum frá stærra tímabili:

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

Annar valkostur síðustu fimm mínúturnar væri að nota BarrierNode, sem slekkur á gögnum fyrir tiltekinn tíma:

|barrier()
        .period(5m)

Dæmi um notkun Go sniðmát í skilaboðum

Sniðmát samsvara sniðinu úr pakkanum textasniðmátHér að neðan eru nokkrar þrautir sem oft koma upp.

ef annað

Við setjum hlutina í röð og kveikjum ekki á fólki með texta aftur:

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

Tveir tölustafir á eftir aukastaf í skilaboðum

Að bæta læsileika skilaboðanna:

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

Stækkandi breytur í skilaboðum

Við birtum frekari upplýsingar í skilaboðunum til að svara spurningunni „Af hverju er það að öskra“?

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

Einstakt viðvörunarauðkenni

Þetta er nauðsynlegt þegar það eru fleiri en einn hópur í gögnunum, annars verður aðeins ein viðvörun búin til:

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

Sérsniðin meðhöndlun

Stóri listinn yfir stjórnendur inniheldur exec, sem gerir þér kleift að framkvæma handritið þitt með samþykktum breytum (stdin) - sköpunargáfu og ekkert annað!

Einn af siðum okkar er lítið Python forskrift til að senda tilkynningar til að slaka á.
Í fyrstu vildum við senda leyfisverndaða grafana mynd í skilaboðum. Síðan skaltu skrifa OK í þráðinn við fyrri viðvörun frá sama hópi, en ekki sem sérstök skilaboð. Stuttu seinna - bættu við skilaboðin algengustu mistökunum á síðustu X mínútum.

Sérstakt umræðuefni er samskipti við aðra þjónustu og allar aðgerðir sem viðvörun hefur hafið (aðeins ef eftirlit þitt virkar nógu vel).
Dæmi um meðhöndlunarlýsingu, þar sem slack_handler.py er sjálfskrifað handrit okkar:

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

Hvernig á að kemba?

Valkostur með log úttak

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

Horfa (cli): kapacitor -url host-eða-ip:9092 logs lvl=villa

Valkostur með httpOut

Sýnir gögn í núverandi leiðslu:

|httpOut('something')

Horfðu á (fáðu): host-eða-ip:9092/kapacitor/v1/tasks/task_name/eitthvað

Framkvæmdarkerfi

  • Hvert verkefni skilar framkvæmdatré með gagnlegum tölum á sniðinu grafviz.
  • Taktu blokk punktur.
  • Límdu það í áhorfanda, njóta.

Hvar er annars hægt að fá hrífu?

tímastimpill í influxdb á afritun

Til dæmis setjum við upp viðvörun fyrir summan af beiðnum á klukkustund (groupBy(1h)) og viljum skrá viðvörunina sem kom í influxdb (til að sýna fallega staðreynd vandamálsins á grafinu í grafana).

influxDBOut() mun skrifa tímagildið frá viðvöruninni til tímastimpilsins; í samræmi við það verður punkturinn á töflunni skrifaður fyrr/síðar en viðvörunin barst.

Þegar nákvæmni er krafist: við vinnum í kringum þetta vandamál með því að hringja í sérsniðna meðhöndlun, sem mun skrifa gögn í influxdb með núverandi tímastimpli.

hafnarverkamaður, smíði og dreifing

Við ræsingu getur kapacitor hlaðið verkefnum, sniðmátum og meðhöndlum úr möppunni sem tilgreind er í stillingunni í [load] blokkinni.

Til að búa til verkefni rétt þarftu eftirfarandi hluti:

  1. Skráarnafn - stækkað í handritaauðkenni/nafn
  2. Tegund – straumur/lota
  3. dbrp – lykilorð til að gefa til kynna í hvaða gagnagrunni + stefnu handritið keyrir (dbrp “birgir.” “autogen”)

Ef eitthvert lotuverkefni inniheldur ekki línu með dbrp, mun öll þjónustan neita að byrja og skrifa heiðarlega um það í annálinn.

Í chronograf, þvert á móti, ætti þessi lína ekki að vera til; hún er ekki samþykkt í gegnum viðmótið og myndar villu.

Hakk þegar gámur er byggður: Dockerfile fer út með -1 ef það eru línur með //.+dbrp, sem gerir þér kleift að skilja strax ástæðuna fyrir biluninni þegar þú setur smíðina saman.

sameinast einum á móti mörgum

Dæmi um verkefni: þú þarft að taka 95. hundraðshluta af rekstrartíma þjónustunnar í viku, berðu saman hverja mínútu af síðustu 10 við þetta gildi.

Þú getur ekki gert einn-á-marga sameiningu, síðasti/meðaltal/miðgildi yfir hóp af punktum breytir hnútnum í straum, villan „getur ekki bætt við barni sem ekki passar: hópur -> straumur“ verður skilað.

Niðurstaðan úr lotu, sem breytu í lambda tjáningu, kemur heldur ekki í staðinn.

Það er möguleiki að vista nauðsynlegar tölur frá fyrstu lotu í skrá í gegnum udf og hlaða þessari skrá með sideload.

Hvað leystum við með þessu?

Við erum með um 100 hótelbirgja, hver þeirra getur haft nokkrar tengingar, við skulum kalla það rás. Það eru um það bil 300 af þessum rásum, hver rásin getur dottið af. Af öllum skráðum mælingum munum við fylgjast með villuhlutfallinu (beiðnir og villur).

Af hverju ekki grafana?

Villuviðvaranir sem eru stilltar í Grafana hafa nokkra ókosti. Sumt er gagnrýnivert, annað er hægt að loka augunum fyrir, allt eftir aðstæðum.

Grafana kann ekki að reikna á milli mælinga + viðvörunar en við þurfum hlutfall (requests-errors)/requests.

Villurnar líta illa út:

Bragðarefur til að vinna úr mæligildum í Kapacitor

Og minna illt þegar það er skoðað með árangursríkum beiðnum:

Bragðarefur til að vinna úr mæligildum í Kapacitor

Allt í lagi, við getum fyrirfram reiknað út hlutfallið í þjónustunni fyrir grafana, og í sumum tilfellum mun þetta virka. En ekki hjá okkur, því... fyrir hverja rás er hlutfall hennar talið „eðlilegt“ og viðvaranir virka samkvæmt kyrrstæðum gildum (við leitum að þeim með augunum, breytum þeim ef það eru tíðar viðvaranir).

Þetta eru dæmi um „venjulegt“ fyrir mismunandi rásir:

Bragðarefur til að vinna úr mæligildum í Kapacitor

Bragðarefur til að vinna úr mæligildum í Kapacitor

Við hunsum fyrra atriðið og gerum ráð fyrir að „venjuleg“ myndin sé svipuð fyrir alla birgja. Nú er allt í lagi og við getum komist af með viðvaranir í Grafana?
Við getum það, en við viljum það ekki, vegna þess að við verðum að velja einn af kostunum:
a) búa til fullt af línuritum fyrir hverja rás fyrir sig (og fylgja þeim sársaukafullt)
b) skildu eftir eitt kort með öllum rásum (og týndu þér í litríkum línum og sérsniðnum viðvörunum)

Bragðarefur til að vinna úr mæligildum í Kapacitor

Hvernig gerðirðu það?

Aftur, það er gott upphafsdæmi í skjölunum (Útreikningur á gengi yfir sameinuðum röðum), er hægt að kíkja á eða taka til grundvallar í svipuðum vandamálum.

Það sem við gerðum á endanum:

  • taka þátt í tveimur seríum á nokkrum klukkustundum, flokkað eftir rásum;
  • fylltu út röð eftir hópum ef engin gögn voru til;
  • bera saman miðgildi síðustu 10 mínútna við fyrri gögn;
  • við hrópum ef við finnum eitthvað;
  • við skrifum reiknaða vexti og viðvaranir sem áttu sér stað í influxdb;
  • senda gagnleg skilaboð til slaka.

Að mínu mati tókst okkur að ná öllu sem við vildum fá í lokin (og jafnvel aðeins meira með sérsniðnum meðhöndlum) eins fallega og hægt var.

Þú getur skoðað github.com kóða dæmi и lágmarks hringrás (graphviz) handritið sem varð til.

Dæmi um kóðann sem myndast:

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)

Hver er niðurstaðan?

Kapacitor er frábært í að framkvæma vöktunarviðvaranir með fullt af hópum, framkvæma viðbótarútreikninga byggða á þegar skráðum mæligildum, framkvæma sérsniðnar aðgerðir og keyra forskriftir (udf).

Aðgangshindrunin er ekki mjög mikil - reyndu það ef grafana eða önnur verkfæri fullnægja ekki óskum þínum að fullu.

Heimild: www.habr.com

Bæta við athugasemd