Trucs voor het verwerken van metrische gegevens in Kapacitor

Waarschijnlijk vraagt ​​niemand zich tegenwoordig nog af waarom het verzamelen van servicestatistieken noodzakelijk is. De volgende logische stap is het instellen van een waarschuwing voor de verzamelde statistieken, die u op de hoogte stelt van eventuele afwijkingen in de gegevens via de kanalen die u het beste uitkomen (mail, Slack, Telegram). Ostrovok.ru Alle statistieken van onze services worden in InfluxDB ingevoerd en weergegeven in Grafana, waar ook basiswaarschuwingen zijn geconfigureerd. Voor taken zoals "iets berekenen en hiermee vergelijken" gebruiken we Kapacitor.

Trucs voor het verwerken van metrische gegevens in Kapacitor
Kapacitor is onderdeel van de TICK-stack en kan gegevens uit InfluxDB verwerken. Het kan verschillende metingen samenvoegen, bruikbare berekeningen uitvoeren op basis van de ontvangen gegevens, het resultaat terugsturen naar InfluxDB en een melding sturen naar Slack/Telegram/mail.

De hele stapel is cool en gedetailleerd de documentatie, maar er zijn altijd nuttige dingen die niet expliciet in de handleidingen worden 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 door te casten:

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

Gebruik van default()

Als de tag/het veld niet is ingevuld, ontstaan ​​er rekenfouten:

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

vul de verbinding in (binnen vs. buiten)

Standaard worden bij join punten verwijderd waar geen gegevens (inner) zijn.
Wanneer fill('null') wordt uitgevoerd, wordt er een outer join uitgevoerd. Hierna moet u een default() uitvoeren en de lege waarden invullen:

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

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

Voorwaarden gebruiken in berekeningen (indien in lambda)

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

De laatste vijf minuten van de pijpleiding voor de periode

U moet bijvoorbeeld de waarden van de laatste vijf minuten vergelijken met die van de vorige week. U kunt twee datareeksen in twee aparte reeksen verzamelen of gegevens uit een langere periode halen:

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

Een alternatief voor de laatste vijf minuten is het gebruik van een BarrierNode-knooppunt, dat de gegevens vóór de opgegeven tijd afsnijdt:

|barrier()
        .period(5m)

Voorbeelden van het gebruik van Go-sjablonen in berichten

De sjablonen komen overeen met het formaat uit het pakket tekst.sjabloonHieronder vindt u enkele veelvoorkomende problemen.

als-anders

Laten we de zaken op orde brengen en mensen niet onnodig met tekst triggeren:

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

Twee cijfers achter de komma in bericht

Verbeter de leesbaarheid van het bericht:

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

Uitvouwende variabelen in een bericht

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

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

Unieke waarschuwings-ID

Noodzakelijk wanneer er meer dan één groep in de gegevens aanwezig is, anders wordt er slechts één waarschuwing gegenereerd:

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

Aangepaste handlers

De grote lijst met handlers bevat exec, waarmee u uw script kunt uitvoeren met de meegegeven parameters (stdin) – niets meer dan creativiteit!

Een van onze gebruiken is een klein Python-script voor het versturen van meldingen naar Slack.
Aanvankelijk wilden we een afbeelding van Grafana, beveiligd met autorisatie, in een bericht versturen. Daarna - schrijf OK in de thread bij de vorige melding van dezelfde groep, en niet in een apart bericht. Iets later - voeg aan het bericht de meest voorkomende fout in de afgelopen X minuten toe.

Een apart onderwerp is de verbinding met andere diensten en de acties die door de waarschuwing worden geïnitieerd (alleen als uw monitoring goed genoeg werkt).
Een voorbeeld van een handlerbeschrijving, waarbij slack_handler.py ons aangepaste 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 kan ik debuggen?

Optie met uitvoer naar log

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

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

Optie met httpOut

Toont gegevens in de huidige pijplijn:

|httpOut('something')

Bekijk (krijg): host-of-ip:9092/kapacitor/v1/taken/taaknaam/iets

Uitvoeringsschema

  • Elke taak retourneert een uitvoeringsboom met nuttige getallen in de indeling grafviz.
  • Wij nemen een blok stip.
  • Plak in viewer, laten we genieten.

Waar anders kun je geharkt worden?

tijdstempel in influxdb bij terugschrijven

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

influxDBOut() schrijft de tijdswaarde van de waarschuwing naar het tijdstempel, zodat het punt op de grafiek eerder/later wordt geschreven dan de waarschuwing binnenkwam.

Wanneer precisie vereist is, omzeilen we dit probleem door een aangepaste handler aan te roepen, die gegevens naar influxdb schrijft met de huidige tijdstempel.

docker, bouwen en implementeren

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

Om een ​​taak correct aan te maken, hebt u het volgende nodig:

  1. Bestandsnaam - wordt uitgebreid naar id/scriptnaam
  2. Type – stream/batch
  3. dbrp – trefwoord voor het specificeren in welke database + beleid het script werkt (dbrp "leverancier"."autogen")

Als een batchtaak geen regel met dbrp heeft, zal de hele service weigeren te starten en dit eerlijk in het logboek beschrijven.

In Chronograf daarentegen zou deze regel niet aanwezig mogen zijn; deze wordt niet geaccepteerd door de interface en geeft een foutmelding.

Container build-hack: Dockerfile eindigt met -1 als er regels met //.+dbrp staan, waardoor u meteen de reden ziet waarom de build is mislukt.

voeg één tot velen samen

Voorbeeldtaak: bepaal het 95e percentiel van de uptime van de service gedurende een week en vergelijk elke minuut van de laatste 10 met deze waarde.

Je kunt geen one-to-many join uitvoeren. Last/mean/median op een groep punten verandert het knooppunt in een stroom. De foutmelding "kan niet-overeenkomende randen van het onderliggende punt niet toevoegen: batch -> stroom" verschijnt.

Het resultaat van de batch, als variabele in de lambda-expressie, wordt ook niet gesubstitueerd.

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

Wat losten we hiermee op?

We hebben ongeveer 100 hotelleveranciers, elk met meerdere connecties, laten we het een kanaal noemen. Er zijn ongeveer 300 van deze kanalen, elk kanaal kan uitvallen. Van alle geregistreerde statistieken monitoren we het foutpercentage (aanvragen en fouten).

Waarom Grafana niet?

Foutmeldingen die in Grafana zijn geconfigureerd, hebben verschillende nadelen. Sommige zijn kritiek, andere kunnen worden genegeerd, afhankelijk van de situatie.

Grafana kan geen interdimensionale berekeningen + waarschuwingen uitvoeren, maar we hebben een tarief (aanvragen-fouten)/aanvragen nodig.

De fouten zien er vervelend uit:

Trucs voor het verwerken van metrische gegevens in Kapacitor

En minder slecht als je kijkt naar succesvolle zoekopdrachten:

Trucs voor het verwerken van metrische gegevens in Kapacitor

Oké, we kunnen de snelheid in de service vóór Grafana vooraf berekenen, en in sommige gevallen werkt dat. Maar niet in onze service, omdat voor elk kanaal de eigen verhouding als "normaal" wordt beschouwd en meldingen werken op statische waarden (we kijken met onze ogen en veranderen deze als er vaak meldingen zijn).

Dit zijn voorbeelden van 'normaal' voor verschillende kanalen:

Trucs voor het verwerken van metrische gegevens in Kapacitor

Trucs voor het verwerken van metrische gegevens in Kapacitor

Laten we het vorige punt even buiten beschouwing laten en aannemen dat alle leveranciers een vergelijkbaar "normaal" beeld hebben. Nu is alles in orde en kunnen we volstaan ​​met meldingen in Grafana?
Dat kunnen we wel, maar dat willen we eigenlijk niet, omdat we een van de volgende opties moeten kiezen:
a) maak een groot aantal grafieken voor elk kanaal apart (en onderhoud ze moeizaam)
b) één grafiek met alle kanalen laten staan ​​(en verdwaal in kleurrijke lijnen en aangepaste waarschuwingen)

Trucs voor het verwerken van metrische gegevens in Kapacitor

Hoe heb je dat gedaan?

Ook hier is een goed startvoorbeeld te vinden in de documentatie (Berekening van tarieven over gekoppelde reeksen), kunt u er eens naar kijken of het gebruiken als basis voor soortgelijke problemen.

Wat we uiteindelijk deden:

  • bekijk twee afleveringen in een paar uur, gegroepeerd per kanaal;
  • we vullen de reeks per groep in als er geen gegevens zijn;
  • vergelijk de mediaan van de laatste 10 minuten met de voorgaande gegevens;
  • laat het ons weten als we iets vinden;
  • we schrijven de berekende tarieven en de waarschuwingen die zijn opgetreden naar influxdb;
  • Stuur een nuttig bericht naar Slack.

Naar mijn mening hebben we uiteindelijk alles wat we wilden bereiken op een prachtige manier (en met de custom handlers zelfs nog iets meer) bereikt.

Je kunt een kijkje nemen op github.com codevoorbeeld и minimaal schema (graphviz) het ontvangen script.

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)

Wat is nu de conclusie?

Kapacitor is zeer geschikt voor het monitoren en waarschuwen van meerdere groepen, het uitvoeren van extra berekeningen op basis van reeds vastgelegde statistieken, het uitvoeren van aangepaste acties en het uitvoeren van scripts (udf).

De instapdrempel is niet zo hoog: probeer het eens als Grafana of andere tools niet geheel aan uw wensen voldoen.

Bron: www.habr.com

Koop betrouwbare hosting voor sites met DDoS-bescherming, VPS VDS-servers 🔥 Koop betrouwbare websitehosting met DDoS-bescherming, VPS- en VDS-servers | ProHoster