Trükkök a metrikák feldolgozásához a Kapacitorban

Valószínűleg ma már senki sem kérdezi, miért van szükség a szolgáltatási mutatók gyűjtésére. A következő logikus lépés egy riasztás beállítása az összegyűjtött metrikákhoz, amely az Ön számára megfelelő csatornákon (mail, Slack, Telegram) értesít az adatok eltéréseiről. Az online szállásfoglalási szolgáltatásban Ostrovok.ru szolgáltatásaink összes mérőszáma az InfluxDB-be kerül, és a Grafana-ban jelenik meg, és az alapvető riasztásokat is ott konfigurálják. Az olyan feladatokhoz, mint a „ki kell számítanod valamit, és összehasonlítanod kell vele”, a Kapacitort használjuk.

Trükkök a metrikák feldolgozásához a Kapacitorban
A Kapacitor a TICK-verem része, amely képes feldolgozni az InfluxDB mérőszámait. Több mérést össze tud kötni (csatlakozni), a kapott adatokból kiszámol valami hasznosat, visszaírja az eredményt az InfluxDB-be, riasztást küld a Slack/Telegram/mail címre.

Az egész halom hűvös és részletes dokumentáció, de mindig lesznek olyan hasznos dolgok, amelyek nincsenek kifejezetten feltüntetve a kézikönyvekben. Ebben a cikkben úgy döntöttem, hogy összegyűjtök néhány ilyen hasznos, nem nyilvánvaló tippet (a TICKscipt alapvető szintaxisát ismertetjük itt).

Gyerünk!

float & int, számítási hibák

Egy teljesen standard probléma, kasztokon keresztül megoldva:

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

default() használata

Ha egy címke/mező nincs kitöltve, számítási hibák lépnek fel:

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

csatlakozás kitöltése (belső vs külső)

Alapértelmezés szerint a csatlakozás elveti azokat a pontokat, ahol nincs adat (belső).
A fill('null') egy külső összekapcsolást hajt végre, amely után meg kell tennie a default()-t, és ki kell töltenie az üres értékeket:

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

Itt még mindig van egy árnyalat. A fenti példában, ha az egyik sorozat (res1 vagy res2) üres, akkor a kapott sorozat (adat) is üres lesz. Ebben a témában több jegy is van a Githubon (1633, 1871, 6967) – várunk a javításokra és egy kis szenvedésre.

Feltételek használata a számításokban (ha lambdában)

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

Az időszak utolsó öt perce a csővezetéktől

Például össze kell hasonlítania az utolsó öt perc értékeit az előző héttel. Két adatköteget vehet két külön kötegbe, vagy kivonhatja az adatok egy részét egy nagyobb időszakból:

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

Az utolsó öt perc alternatívája egy BarrierNode használata, amely levágja az adatokat a megadott idő előtt:

|barrier()
        .period(5m)

Példák a Go sablonok használatára üzenetben

A sablonok megfelelnek a csomagban szereplő formátumnak szöveg.sablonAz alábbiakban felsorolunk néhány gyakran előforduló rejtvényt.

ha más

Rendet teszünk, és nem indítjuk el újra az embereket szöveggel:

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

Két számjegy a tizedesvessző után az üzenetben

Az üzenet olvashatóságának javítása:

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

Változók bővítése az üzenetben

További információkat jelenítünk meg az üzenetben, hogy válaszoljunk a „Miért kiabál” kérdésre?

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

Egyedi riasztási azonosító

Ez akkor szükséges, ha egynél több csoport van az adatokban, különben csak egy riasztás jön létre:

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

Egyedi kezelő

A kezelők nagy listája tartalmazza az exec-et, amely lehetővé teszi a szkript végrehajtását az átadott paraméterekkel (stdin) - kreativitás és semmi több!

Az egyik szokásunk egy kis Python-szkript, amely értesítéseket küld a lazaságnak.
Eleinte szerettünk volna üzenetben küldeni egy jogosultságvédett grafana képet. Utána az ugyanabból a csoportból származó előző figyelmeztetés szálába írja be az OK-t, és ne külön üzenetként. Kicsit később – add hozzá az üzenethez az elmúlt X percben előforduló leggyakoribb hibát.

Külön téma a kommunikáció a többi szolgáltatással és a riasztás által kezdeményezett műveletek (csak akkor, ha a felügyelet elég jól működik).
Példa a kezelő leírására, ahol a slack_handler.py a saját magunk által írt szkriptünk:

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

Hogyan lehet hibakeresni?

Opció naplókimenettel

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

Watch (cli): kapacitor -url host-or-ip:9092 logs lvl=hiba

Opció a httpOut

Az aktuális folyamat adatait jeleníti meg:

|httpOut('something')

Nézze (kapja): host-or-ip:9092/kapacitor/v1/tasks/task_name/something

Kiviteli séma

  • Minden feladat egy végrehajtási fát ad vissza hasznos számokkal a formátumban graphviz.
  • Vegyünk egy blokkot pont.
  • Illessze be a megjelenítőbe, Élvezd.

Hol lehet még kapni gereblyét?

időbélyeg az influxdb-ben visszaíráskor

Például beállítunk egy riasztást az óránkénti kérések összegére (groupBy(1h)), és rögzíteni akarjuk az influxdb-ben bekövetkezett riasztást (hogy gyönyörűen mutassuk meg a probléma tényét a grafana grafikonján).

Az influxDBOut() az időértéket a riasztástól az időbélyegig írja, ennek megfelelően a diagram pontja korábban/később lesz írva, mint a riasztás megérkezett.

Ha pontosságra van szükség: Megkerüljük ezt a problémát egy egyéni kezelő meghívásával, amely az aktuális időbélyeggel írja az adatokat az influxdb-be.

dokkoló, felépítés és telepítés

Indításkor a kapacitor betölthet feladatokat, sablonokat és kezelőket a [load] blokk konfigurációjában megadott könyvtárból.

A feladat helyes létrehozásához a következő dolgokra van szükség:

  1. Fájlnév – szkriptazonosítóra/névre bővítve
  2. Típus – adatfolyam/köteg
  3. dbrp – kulcsszó, amely jelzi, hogy a szkript melyik adatbázisban + házirendben fut (dbrp „supplier.” „autogen”)

Ha egy kötegelt feladat nem tartalmaz egy sort a dbrp-vel, az egész szolgáltatás megtagadja az indulást, és őszintén ír róla a naplóban.

A chronografban éppen ellenkezőleg, ennek a sornak nem szabad léteznie, nem fogadja el az interfészen keresztül, és hibát generál.

Hack konténer építésekor: A Dockerfile -1-gyel lép ki, ha vannak //.+dbrp sorok, ami lehetővé teszi, hogy azonnal megértse a hiba okát a build összeállításakor.

csatlakozzon egy a sokhoz

Példafeladat: ki kell venni a szolgáltatás működési idejének 95. százalékát egy hétre, és az utolsó 10 perc minden percét ezzel az értékkel kell összehasonlítani.

Nem lehet egy-a-többhez csatlakozást végrehajtani, az utolsó/átlag/medián pontcsoport felett a csomópontot folyammá változtatja, a „nem lehet hozzáadni gyermek nem egyező éleket: köteg -> folyam” hibaüzenet jelenik meg.

A köteg eredménye, mint változó a lambda-kifejezésben, szintén nem helyettesíthető.

Lehetőség van a szükséges számok elmentésére az első kötegből egy fájlba udf-en keresztül, és ezt a fájlt oldalbetöltéssel betöltheti.

Mit oldottunk meg ezzel?

Körülbelül 100 szállodai beszállítónk van, mindegyiknek több kapcsolata is lehet, nevezzük csatornának. Körülbelül 300 ilyen csatorna van, mindegyik csatorna leeshet. Az összes rögzített mérőszám közül a hibaarányt (kérések és hibák) figyelni fogjuk.

Miért nem grafana?

A Grafana programban konfigurált hibariasztásoknak számos hátránya van. Némelyik kritikus, van, amely előtt a helyzettől függően becsukhatja a szemét.

A Grafana nem tudja, hogyan kell számolni a mérések + riasztások között, de kell egy arány (kérések-hibák)/kérések.

A hibák csúnyán néznek ki:

Trükkök a metrikák feldolgozásához a Kapacitorban

És kevésbé gonosz, ha sikeres kérésekkel nézzük:

Trükkök a metrikák feldolgozásához a Kapacitorban

Oké, a szolgáltatásban előre kiszámolhatjuk a tarifát a grafana előtt, és bizonyos esetekben ez működni fog. De nem a miénkben, mert... minden csatornánál a saját aránya „normálisnak” számít, és a riasztások statikus értékek szerint működnek (szemünkkel keressük, ha gyakori riasztások vannak, változtatjuk).

Példák a „normál” kifejezésre a különböző csatornákhoz:

Trükkök a metrikák feldolgozásához a Kapacitorban

Trükkök a metrikák feldolgozásához a Kapacitorban

Figyelmen kívül hagyjuk az előző pontot, és feltételezzük, hogy a „normál” kép minden szállítónál hasonló. Most már minden rendben van, és boldogulhatunk a grafana riasztásaival?
Megtehetjük, de nagyon nem akarjuk, mert választanunk kell a lehetőségek közül:
a) készítsen sok grafikont minden csatornához külön-külön (és fájdalmasan kísérje el őket)
b) hagyjon egy diagramot az összes csatornával (és elveszjen a színes vonalakban és a személyre szabott figyelmeztetésekben)

Trükkök a metrikák feldolgozásához a Kapacitorban

Hogy csináltad?

Ismét van egy jó kiindulási példa a dokumentációban (Az egyesített sorozatok díjainak kiszámítása), lesni, vagy alapul venni hasonló problémáknál.

Amit végül csináltunk:

  • csatlakozzon két sorozathoz néhány óra alatt, csatornák szerint csoportosítva;
  • töltse ki a sorozatot csoportonként, ha nem volt adat;
  • hasonlítsa össze az elmúlt 10 perc mediánját a korábbi adatokkal;
  • kiáltunk, ha találunk valamit;
  • megírjuk a kiszámított díjakat és riasztásokat, amelyek az influxdb-ben történtek;
  • küldjön egy hasznos üzenetet a lazának.

Véleményem szerint mindent, amit szerettünk volna a végén (és még egy kicsit többet is egyedi kezelőkkel) sikerült a lehető legszebben elérnünk.

Megnézheti a github.com webhelyet kód példa и minimális áramkör (graphviz) az eredményül kapott szkriptet.

Példa az eredményül kapott kódra:

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)

És mi a következtetés?

A Kapacitor kiválóan alkalmas figyelési riasztások végrehajtására egy csomó csoportosítással, további számítások elvégzésére a már rögzített mérőszámok alapján, egyéni műveletek végrehajtására és szkriptek futtatására (udf).

A belépési korlát nem túl magas – próbálja ki, ha a grafana vagy más eszközök nem elégítik ki teljesen vágyait.

Forrás: will.com

Hozzászólás