Trucs voor het verwerken van statistieken in Kapacitor

Hoogstwaarschijnlijk vraagt ​​niemand tegenwoordig waarom het nodig is om servicestatistieken te verzamelen. De volgende logische stap is het instellen van een waarschuwing voor de verzamelde statistieken, die eventuele afwijkingen in de gegevens meldt in kanalen die voor u geschikt zijn (mail, Slack, Telegram). In de online hotelboekingsservice Ostrovok.ru alle statistieken van onze services worden in InfluxDB gegoten en weergegeven in Grafana, en daar worden ook basiswaarschuwingen geconfigureerd. Voor taken als “je moet iets berekenen en ermee vergelijken” gebruiken we Kapacitor.

Trucs voor het verwerken van statistieken in Kapacitor
Kapacitor maakt deel uit van de TICK-stack die statistieken uit InfluxDB kan verwerken. Het kan meerdere metingen met elkaar verbinden (samenvoegen), iets nuttigs berekenen uit de ontvangen gegevens, het resultaat terugschrijven naar InfluxDB, een waarschuwing sturen naar Slack/Telegram/mail.

De hele stapel is cool en gedetailleerd de documentatie, maar er zullen altijd nuttige zaken zijn die niet expliciet in de handleidingen staan ​​aangegeven. In dit artikel heb ik besloten een aantal van dergelijke nuttige, niet voor de hand liggende tips te verzamelen (de basissyntaxis van TICKscipt wordt beschreven hier) en laat zien hoe ze kunnen worden toegepast aan de hand van een voorbeeld van het oplossen van een van onze problemen.

Laten we gaan!

float & int, rekenfouten

Een absoluut standaardprobleem, opgelost via kasten:

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

Standaard() gebruiken

Als een tag/veld niet is ingevuld, zullen er rekenfouten optreden:

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

vul join in (binnen versus buiten)

Standaard worden bij join punten verwijderd waar er geen gegevens zijn (binnen).
Met fill('null') wordt een outside join uitgevoerd, waarna je een default() moet doen en de lege waarden moet invullen:

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

Hier is nog steeds sprake van een nuance. Als in het bovenstaande voorbeeld een van de reeksen (res1 of res2) leeg is, zal de resulterende reeks (data) ook leeg zijn. Er zijn verschillende tickets over dit onderwerp op Github (1633, 1871, 6967) – we wachten op oplossingen en lijden een beetje.

Voorwaarden gebruiken in berekeningen (indien in lambda)

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

Laatste vijf minuten van de pijplijn voor de periode

U moet bijvoorbeeld de waarden van de afgelopen vijf minuten vergelijken met de week ervoor. U kunt twee batches met gegevens in twee afzonderlijke batches nemen of een deel van de gegevens uit een grotere periode extraheren:

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

Een alternatief voor de laatste vijf minuten zou zijn om een ​​BarrierNode te gebruiken, die de gegevens vóór de opgegeven tijd afsluit:

|barrier()
        .period(5m)

Voorbeelden van het gebruik van Go-sjablonen in berichten

Sjablonen komen overeen met het formaat uit het pakket tekst.sjabloonHieronder staan ​​enkele veel voorkomende puzzels.

als-anders

Wij brengen orde op zaken en triggeren mensen niet nog een keer met tekst:

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

Twee cijfers achter de komma in bericht

Verbetering van de leesbaarheid van het bericht:

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

Variabelen in bericht uitbreiden

We geven meer informatie weer in het bericht om de vraag “Waarom schreeuwt het” te beantwoorden?

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

Unieke waarschuwings-ID

Dit is noodzakelijk als er meer dan één groep in de gegevens voorkomt, anders wordt er slechts één waarschuwing gegenereerd:

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

Aangepaste behandelaar

De grote lijst met handlers bevat exec, waarmee u uw script kunt uitvoeren met de doorgegeven parameters (stdin) - creativiteit en meer niet!

Een van onze gewoontes is een klein Python-script voor het verzenden van meldingen naar slack.
In eerste instantie wilden we een met autorisatie beveiligde grafana-afbeelding in een bericht sturen. Schrijf daarna OK in de thread naar de vorige waarschuwing uit dezelfde groep, en niet als een afzonderlijk bericht. Even later: voeg aan het bericht de meest voorkomende fout van de afgelopen X minuten toe.

Een apart onderwerp is de communicatie met andere diensten en eventuele acties die door een alert worden geïnitieerd (alleen als uw monitoring goed genoeg werkt).
Een voorbeeld van een handlerbeschrijving, waarbij slack_handler.py ons zelfgeschreven script is:

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

Hoe debuggen?

Optie met loguitvoer

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

Bekijk (cli): kapacitor -url host-of-ip:9092 registreert lvl=fout

Optie met httpOut

Toont gegevens in de huidige pijplijn:

|httpOut('something')

Kijk (krijg): host-of-ip:9092/kapacitor/v1/tasks/task_name/iets

Uitvoeringsschema

  • Elke taak retourneert een uitvoeringsboom met nuttige getallen in het formaat grafviz.
  • Neem een ​​blok stip.
  • Plak het in de viewer, genieten.

Waar kun je nog meer een hark krijgen?

tijdstempel in influxdb bij terugschrijven

We hebben bijvoorbeeld een waarschuwing ingesteld voor de som van de verzoeken per uur (groupBy(1h)) en willen de waarschuwing die is opgetreden vastleggen in influxdb (om het feit van het probleem mooi weer te geven in de grafiek in grafana).

influxDBOut() schrijft de tijdswaarde van de waarschuwing naar de tijdstempel; dienovereenkomstig zal het punt op de grafiek eerder/later worden geschreven dan de waarschuwing arriveerde.

Wanneer nauwkeurigheid vereist is: we kunnen dit probleem omzeilen door een aangepaste handler aan te roepen, die gegevens naar influxdb zal schrijven met de huidige tijdstempel.

docker, bouwen en implementeren

Bij het opstarten kan kapacitor taken, sjablonen en handlers laden vanuit de map die is opgegeven in de configuratie in het [load]-blok.

Om een ​​taak correct aan te maken, heb je de volgende zaken nodig:

  1. Bestandsnaam – uitgebreid naar script-id/naam
  2. Type – stream/batch
  3. dbrp – trefwoord om aan te geven in welke database + beleid het script draait (dbrp “supplier.” “autogen”)

Als een batchtaak geen regel met dbrp bevat, zal de hele service weigeren te starten en er eerlijk over schrijven in het logboek.

In chronograaf daarentegen zou deze regel niet mogen bestaan; hij wordt niet geaccepteerd via de interface en genereert een fout.

Hack bij het bouwen van een container: Dockerfile wordt afgesloten met -1 als er regels zijn met //.+dbrp, waardoor u onmiddellijk de reden voor de fout kunt begrijpen bij het samenstellen van de build.

sluit je aan bij één van velen

Voorbeeldtaak: u moet het 95e percentiel van de bedrijfstijd van de dienst voor een week nemen en elke minuut van de laatste 10 met deze waarde vergelijken.

Je kunt geen één-op-veel-join doen, laatste/gemiddelde/mediaan over een groep punten verandert het knooppunt in een stream, de fout "kan niet-overeenkomende onderliggende randen toevoegen: batch -> stream" zal worden geretourneerd.

Het resultaat van een batch, als variabele in een lambda-expressie, wordt ook niet vervangen.

Er is een optie om de benodigde nummers uit de eerste batch op te slaan in een bestand via udf en dit bestand via sideload te laden.

Wat hebben we hiermee opgelost?

We hebben ongeveer 100 hotelleveranciers, elk van hen kan meerdere aansluitingen hebben, laten we het een kanaal noemen. Er zijn ongeveer 300 van deze kanalen, elk van de kanalen kan eraf vallen. Van alle geregistreerde statistieken zullen we het foutenpercentage (verzoeken en fouten) monitoren.

Waarom geen grafana?

Foutmeldingen geconfigureerd in Grafana hebben verschillende nadelen. Sommige zijn van cruciaal belang, voor andere kun je je ogen sluiten, afhankelijk van de situatie.

Grafana weet niet hoe te berekenen tussen metingen + alarmering, maar we hebben een tarief (verzoeken-fouten)/verzoeken nodig.

De fouten zien er vervelend uit:

Trucs voor het verwerken van statistieken in Kapacitor

En minder kwaadaardig als het wordt bekeken met succesvolle verzoeken:

Trucs voor het verwerken van statistieken in Kapacitor

Oké, we kunnen het tarief vooraf berekenen in de service vóór grafana, en in sommige gevallen zal dit werken. Maar niet bij ons, want... voor elk kanaal wordt zijn eigen verhouding als “normaal” beschouwd, en waarschuwingen werken volgens statische waarden (we zoeken ze met onze ogen, veranderen ze als er frequente waarschuwingen zijn).

Dit zijn voorbeelden van ‘normaal’ voor verschillende kanalen:

Trucs voor het verwerken van statistieken in Kapacitor

Trucs voor het verwerken van statistieken in Kapacitor

We negeren het vorige punt en gaan ervan uit dat het “normale” beeld voor alle leveranciers hetzelfde is. Nu is alles in orde en kunnen we rondkomen met waarschuwingen in Grafana?
Dat kan wel, maar dat willen we eigenlijk niet, omdat we één van de volgende opties moeten kiezen:
a) maak voor elk kanaal afzonderlijk een heleboel grafieken (en begeleid ze pijnlijk)
b) laat één diagram achter met alle kanalen (en verdwaal in de kleurrijke lijnen en aangepaste waarschuwingen)

Trucs voor het verwerken van statistieken in Kapacitor

Hoe heb je het gedaan?

Nogmaals, er is een goed startvoorbeeld in de documentatie (Tarieven berekenen over samengevoegde series), kan worden bekeken of als basis worden genomen bij soortgelijke problemen.

Wat we uiteindelijk hebben gedaan:

  • sluit je binnen een paar uur aan bij twee series, gegroepeerd op kanalen;
  • vul de reeks per groep in als er geen gegevens waren;
  • vergelijk de mediaan van de laatste 10 minuten met eerdere gegevens;
  • we schreeuwen als we iets vinden;
  • we schrijven de berekende tarieven en waarschuwingen die zich hebben voorgedaan in influxdb;
  • Stuur een nuttig bericht naar Slack.

Naar mijn mening zijn we erin geslaagd om alles wat we wilden bereiken uiteindelijk (en zelfs nog iets meer met custom handlers) zo mooi mogelijk te bereiken.

Je kunt kijken op github.com codevoorbeeld и minimaal circuit (graphviz) het resulterende script.

Een voorbeeld van de resulterende code:

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)

En wat is de conclusie?

Kapacitor is geweldig in het uitvoeren van monitoring-waarschuwingen met een heleboel groeperingen, het uitvoeren van aanvullende berekeningen op basis van reeds vastgelegde statistieken, het uitvoeren van aangepaste acties en het uitvoeren van scripts (UDF).

De toegangsdrempel is niet erg hoog - probeer het als grafana of andere hulpmiddelen niet volledig aan je wensen voldoen.

Bron: www.habr.com

Voeg een reactie