Sannsynligvis er det ingen i dag som lurer på hvorfor det er nødvendig å samle inn servicemålinger. Det neste logiske trinnet er å sette opp et varsel for de innsamlede målingene, som vil varsle deg om eventuelle avvik i dataene i kanalene som er praktiske for deg (post, Slack, Telegram). I den nettbaserte hotellbookingstjenesten Alle målinger av tjenestene våre legges inn i InfluxDB og vises i Grafana, hvor grunnleggende varsling også er konfigurert. For oppgaver som "trenger å beregne noe og sammenligne med dette" bruker vi Kapacitor.

Kapacitor er en del av TICK-stakken som kan behandle målinger fra InfluxDB. Den kan slå sammen flere målinger, beregne noe nyttig fra de mottatte dataene, skrive resultatet tilbake til InfluxDB og sende et varsel til Slack/Telegram/mail.
Hele stabelen har en kul og detaljert , men det finnes alltid nyttige ting som ikke er eksplisitt angitt i manualene. I denne artikkelen bestemte jeg meg for å samle en rekke slike nyttige, ikke-åpenbare tips (den grunnleggende syntaksen til TICKscipt er beskrevet ) og vis hvordan de kan anvendes ved å bruke eksemplet på å løse et av problemene våre.
La oss gå!
flyttall og intall, beregningsfeil
Et helt standardproblem, løst gjennom casting:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Bruker standard()
Hvis taggen/feltet ikke er utfylt, vil det oppstå beregningsfeil:
|default()
.tag('status', 'empty')
.field('value', 0)
fyll inn sammenføyning (indre vs. ytre)
Som standard vil join forkaste punkter der det ikke finnes data (indre).
Når fill('null') utføres en ytre sammenkobling, hvoretter du må gjøre en default() og fylle inn de tomme verdiene:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Det er fortsatt en nyanse her. Hvis en av seriene (res1 eller res2) er tom i eksemplet ovenfor, vil den endelige serien (data) også være tom. Det finnes flere saker om dette emnet på GitHub (, , ) – vi venter på løsninger og lider litt.
Bruk av betingelser i beregninger (hvis i lambda)
|eval(lambda: if("value" > 0, true, false)
De siste fem minuttene fra rørledningen for perioden
For eksempel må du sammenligne verdiene fra de siste fem minuttene med forrige uke. Du kan ta to datagrupper i to separate grupper eller trekke ut data fra en større periode:
|where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)
Et alternativ for de siste fem minuttene er å bruke en BarrierNode-node, som kutter av data før den angitte tiden:
|barrier()
.period(5m)
Eksempler på bruk av Go-maler i meldinger
Malene samsvarer med formatet fra pakken , nedenfor er noen vanlige problemer.
hvis-annet
La oss sette ting i system og ikke trigge folk med tekstmeldinger unødvendig:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
To sifre etter desimaltegnet i meldingen
Forbedre lesbarheten til meldingen:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Utfolder variabler i meldingen
Vi viser mer informasjon i meldingen for å svare på spørsmålet «Hvorfor skriker den?»
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Unik varslingsidentifikator
En nødvendig ting når det er mer enn én gruppe i dataene, ellers vil bare ett varsel genereres:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Tilpassede behandlere
Den store listen over håndteringsprogrammer inkluderer exec, som lar deg kjøre skriptet ditt med de beståtte parameterne (stdin) – kreativitet og ingenting mer!
En av våre spesialtilpassede programmer er et lite Python-skript for å sende varsler til Slack.
Først ville vi sende et bilde fra Grafana, beskyttet av autorisasjon, i en melding. Deretter – skriv OK i tråden til forrige varsel fra samme gruppe, og ikke i en separat melding. Litt senere – legg til i meldingen den hyppigste feilen de siste X minuttene.
Et eget tema er forbindelsen med andre tjenester og eventuelle handlinger igangsatt av varselet (bare hvis overvåkingen din fungerer godt nok).
Et eksempel på en handlerbeskrivelse, der slack_handler.py er vårt tilpassede skript:
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"]
Hvordan feilsøke?
Alternativ med utgang til loggføring
|log()
.level("error")
.prefix("something")
Se (cli): kondensator -url :9092 logger nivå=feil
Alternativ med httpOut
Viser data i gjeldende pipeline:
|httpOut('something')
Se (få): :9092/kondensator/v1/oppgaver/oppgavenavn/noe
Implementeringsordning
- Hver oppgave returnerer et utførelsestre med nyttige tall i formatet .
- Vi tar en blokk .
- Lim inn i visningsprogrammet, .
Hvor ellers kan du bli raket?
tidsstempel i influxdb ved tilbakeskrivning
For eksempel setter vi opp et varsel for summen av forespørsler per time (groupBy(1t)) og ønsker å registrere varselet som oppstod i influxdb (for å tydelig vise problemet på grafen i grafana).
influxDBOut() vil skrive tidsverdien fra varselet til tidsstempelet, slik at punktet på diagrammet vil bli skrevet tidligere/senere enn varselet ankom.
Når presisjon er nødvendig: vi omgår dette problemet ved å kalle en tilpasset behandler, som vil skrive data til influxdb med gjeldende tidsstempel.
docker, bygge og distribuere
Ved oppstart kan kapacitor laste inn oppgaver, maler og behandlere fra katalogen som er spesifisert i konfigurasjonen, i [load]-blokken.
For å opprette en oppgave riktig trenger du følgende:
- Filnavn - utvides til id/skriptnavn
- Type – strøm/batch
- dbrp – nøkkelord for å spesifisere i hvilken database + policy skriptet fungerer (dbrp "leverandør"."autogen")
Hvis en batchoppgave ikke har en linje med dbrp, vil hele tjenesten nekte å starte og ærlig skrive om det i loggen.
I kronografen, derimot, skal ikke denne linjen være der; den aksepteres ikke gjennom grensesnittet og returnerer en feil.
Containerbyggingshack: Dockerfile avsluttes med -1 hvis det er linjer med //.+dbrp, noe som lar deg umiddelbart forstå årsaken til byggefeilen.
bli med én til mange
Eksempeloppgave: du må ta den 95. persentilen av tjenestens oppetid i en uke, og sammenligne hvert minutt av de siste 10 med denne verdien.
Du kan ikke gjøre en én-til-mange-sammenføyning. Hvis last/mean/median på en gruppe punkter gjør noden om til en strøm, vil feilen "kan ikke legge til underordnede, ikke-samsvarende kanter: batch -> stream" returneres.
Resultatet av batchen, som en variabel i lambda-uttrykket, blir heller ikke erstattet.
Det finnes et alternativ for å lagre de nødvendige tallene fra den første batchen til en fil via udf og laste inn denne filen via sideload.
Hva løste vi med dette?
Vi har omtrent 100 hotellleverandører, og hver av dem kan ha flere forbindelser, la oss kalle det en kanal. Det er omtrent 300 av disse kanalene, og hver av kanalene kan falle av. Av alle de registrerte målingene vil vi overvåke feilraten (forespørsler og feil).
Hvorfor ikke Grafana?
Feilvarsler konfigurert i Grafana har flere ulemper. Noen er kritiske, andre kan ignoreres, avhengig av situasjonen.
Grafana kan ikke gjøre interdimensjonale beregninger + varsling, men vi trenger en rate (requests-errors)/requests.
Feilene ser stygge ut:

Og mindre ondt hvis du ser på vellykkede spørringer:

Greit, vi kan forhåndsberegne raten i tjenesten før Grafana, og i noen tilfeller vil det fungere. Men ikke i vår, fordi for hver kanal regnes dens eget forhold som "normalt", og varsler fungerer på statiske verdier (vi ser med øynene våre, endrer hvis den varsler ofte).
Dette er eksempler på «normalt» for forskjellige kanaler:


La oss ignorere det forrige punktet og anta at alle leverandører har et lignende «normalt» bilde. Nå er alt i orden, og vi kan klare oss med varsler i Grafana?
Vi kan, men vi vil egentlig ikke, for vi må velge ett av alternativene:
a) lage mange grafer for hver kanal separat (og vedlikeholde dem på en smertefull måte)
b) behold ett diagram med alle kanaler (og gå deg vill i fargerike linjer og tilpassede varsler)

Hvordan gjorde du det?
Igjen, det er et godt utgangspunkt i dokumentasjonen (), kan du ta en titt på det eller bruke det som grunnlag i lignende problemer.
Det vi gjorde til slutt:
- bli med på to episoder om noen timer, gruppert etter kanaler;
- vi fyller ut serien etter grupper hvis det ikke fantes data;
- sammenlign medianen for de siste 10 minuttene med tidligere data;
- rope hvis vi finner noe;
- vi skriver de beregnede ratene og varslene som oppsto til influxdb;
- Send en nyttig melding til Slack.
Etter min mening klarte vi å oppnå alt vi ønsket å få til slutt på den vakreste måten (og enda litt mer med tilpassede håndtak).
Du kan ta en titt på github.com и det mottatte manuset.
Eksempel på den resulterende koden:
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å hva er konklusjonen?
Kapacitor er flink til å overvåke og varsle med en rekke grupper, utføre tilleggsberegninger basert på allerede registrerte målinger, utføre tilpassede handlinger og kjøre skript (udf).
Inngangsgrensen er ikke veldig høy – prøv det hvis Grafana eller andre verktøy ikke fullt ut tilfredsstiller dine ønsker.
Kilde: www.habr.com
