Meast wierskynlik, hjoed freget gjinien wêrom't it nedich is om servicemetriken te sammeljen. De folgjende logyske stap is om in warskôging yn te stellen foar de sammele metriken, dy't sil ynformearje oer alle ôfwikingen yn 'e gegevens yn kanalen dy't jo handich binne (post, Slack, Telegram). Yn de online hotel booking tsjinst alle metriken fan ús tsjinsten wurde yn InfluxDB yngien en werjûn yn Grafana, en basis warskôging is dêr ek konfigureare. Foar taken lykas "jo moatte wat berekkenje en dermei fergelykje," brûke wy Kapacitor.

Kapacitor is diel fan 'e TICK-stapel dy't metriken kin ferwurkje fan InfluxDB. It kin ferskate mjittingen ferbine (meidwaan), wat nuttich berekkenje fan 'e ûntfongen gegevens, it resultaat weromskriuwe nei InfluxDB, in warskôging stjoere nei Slack / Telegram / mail.
De heule stapel is koel en detaillearre , mar d'r sille altyd nuttige dingen wêze dy't net eksplisyt oanjûn binne yn 'e hantliedingen. Yn dit artikel besleat ik in oantal fan sokke nuttige, net-foar de hân lizzende tips te sammeljen (de basissyntaksis fan TICKscipt wurdt beskreaun ) en lit sjen hoe't se kinne wurde tapast mei in foarbyld fan it oplossen fan ien fan ús problemen.
Lit ús gean!
float & int, berekkeningsflaters
In absolút standert probleem, oplost troch kasten:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
It brûken fan standert()
As in tag/fjild net ynfolle is, sille berekkeningsflaters foarkomme:
|default()
.tag('status', 'empty')
.field('value', 0)
ynfolje join (innerlik vs uterlik)
Standert sil join punten ferwiderje wêr't gjin gegevens binne (ynderlik).
Mei fill('null'), sil in eksterne join útfierd wurde, wêrnei't jo in standert () moatte dwaan en de lege wearden ynfolje:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Hjir is noch in nuânse. Yn it foarbyld hjirboppe, as ien fan 'e searjes (res1 of res2) leech is, sil de resultearjende searje (gegevens) ek leech wêze. D'r binne ferskate kaartsjes oer dit ûnderwerp op Github (, , ) - wy wachtsje op fixes en lijen in bytsje.
Betingsten brûke yn berekkeningen (as yn lambda)
|eval(lambda: if("value" > 0, true, false)
Lêste fiif minuten fan de pipeline foar de perioade
Jo moatte bygelyks de wearden fan 'e lêste fiif minuten fergelykje mei de foarige wike. Jo kinne twa batches gegevens yn twa aparte batches nimme of in diel fan 'e gegevens út in gruttere perioade ekstrahearje:
|where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)
In alternatyf foar de lêste fiif minuten soe wêze om in BarrierNode te brûken, dy't gegevens foar de opjûne tiid ôfsnijt:
|barrier()
.period(5m)
Foarbylden fan it brûken fan Go-sjabloanen yn berjocht
Sjabloanen oerienkomme mei it formaat fan it pakket Hjirûnder binne guon faak tsjinkaam puzels.
as oars
Wy sette dingen yn oarder en triggerje minsken net wer mei tekst:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Twa sifers nei it desimaal punt yn it berjocht
De lêsberens fan it berjocht ferbetterje:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
It útwreidzjen fan fariabelen yn berjocht
Wy litte mear ynformaasje sjen yn it berjocht om de fraach "Wêrom is it te roppen" te beantwurdzjen?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Unike alert identifier
Dit is in needsaaklik ding as d'r mear dan ien groep yn 'e gegevens is, oars sil mar ien warskôging wurde generearre:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Oanpaste handlers
De grutte list fan handlers omfettet exec, wêrtroch jo jo skript kinne útfiere mei de trochjûne parameters (stdin) - kreativiteit en neat mear!
Ien fan ús gewoanten is in lyts Python-skript foar it ferstjoeren fan notifikaasjes nei slack.
Earst woene wy in autorisaasje-beskerme grafana-ôfbylding yn in berjocht stjoere. Skriuw dêrnei OK yn de tried nei de foarige warskôging fan deselde groep, en net as in apart berjocht. In bytsje letter - tafoegje oan it berjocht de meast foarkommende flater yn 'e lêste X minuten.
In apart ûnderwerp is kommunikaasje mei oare tsjinsten en alle aksjes inisjearre troch in warskôging (allinich as jo kontrôle goed genôch wurket).
In foarbyld fan in handlerbeskriuwing, wêrby't slack_handler.py ús selsskreaune skript 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?
Opsje mei logútfier
|log()
.level("error")
.prefix("something")
Sjoch (cli): kapacitor -url :9092 logs lvl=flater
Opsje mei httpOut
Toant gegevens yn 'e hjoeddeistige pipeline:
|httpOut('something')
Sjoch (krije): :9092/kapacitor/v1/tasks/task_name/something
Útfiering skema
- Elke taak jout in útfieringsbeam werom mei nuttige nûmers yn it formaat .
- Nim in blokje .
- Plak it yn viewer .
Wêr oars kinne jo krije in rake?
tiidstempel yn influxdb op weromskriuwing
Bygelyks, wy sette in warskôging foar de som fan fersiken per oere (groupBy(1h)) en wolle opnimme de warskôging dy't barde yn influxdb (om moai sjen litte it feit fan it probleem op 'e grafyk yn grafana).
influxDBOut () sil de tiidwearde fan 'e warskôging nei it tiidstempel skriuwe; dêrom sil it punt op 'e kaart earder / letter skreaun wurde as de warskôging oankaam.
Wannear't krektens is fereaske: wy wurkje om dit probleem troch in belje in oanpaste handler, dy't sil skriuwe gegevens nei influxdb mei de hjoeddeiske tiidstempel.
docker, bouwe en ynset
By it opstarten kin kapacitor taken, sjabloanen en handlers lade út 'e map oantsjutte yn' e konfiguraasje yn it blok [load].
Om in taak korrekt te meitsjen, hawwe jo de folgjende dingen nedich:
- Bestânsnamme - útwreide yn skript id / namme
- Type - stream / batch
- dbrp - kaaiwurd om oan te jaan yn hokker databank + belied it skript rint (dbrp "leveransier." "autogen")
As guon batchtaak gjin line mei dbrp befettet, sil de heule tsjinst wegerje om te begjinnen en sil der earlik oer skriuwe yn it log.
Yn chronograf, krekt oarsom, dizze line soe net moatte bestean; it wurdt net akseptearre fia de ynterface en genereart in flater.
Hack by it bouwen fan in kontener: Dockerfile giet út mei -1 as d'r rigels binne mei //.+dbrp, wêrtroch jo de reden foar it mislearjen fuortendaliks kinne begripe by it gearstallen fan de bou.
join ien nei in protte
Foarbyld taak: jo moatte de 95e percentile fan 'e operaasjetiid fan' e tsjinst nimme foar in wike, fergelykje elke minút fan 'e lêste 10 mei dizze wearde.
Jo kinne gjin ien-nei-mannich join dwaan, lêste / gemiddelde / mediaan oer in groep punten feroaret de knooppunt yn in stream, de flater "kin net tafoegje bern net oerienkommende rânen: batch -> stream" sil weromjûn wurde.
It resultaat fan in batch, as fariabele yn in lambda-ekspresje, wurdt ek net ferfongen.
D'r is in opsje om de nedige nûmers fan 'e earste batch te bewarjen nei in bestân fia udf en dizze bestân fia sideload te laden.
Wat hawwe wy hjirmei oplost?
Wy hawwe sawat 100 hotelleveransiers, elk fan har kin ferskate ferbiningen hawwe, litte wy it in kanaal neame. D'r binne sawat 300 fan dizze kanalen, elk fan 'e kanalen kin falle. Fan alle opnommen metriken sille wy it flaterrate (fersiken en flaters) kontrolearje.
Wêrom net grafana?
Flater warskôgings konfigurearre yn Grafana hawwe ferskate neidielen. Guon binne kritysk, guon kinne jo de eagen slute, ôfhinklik fan 'e situaasje.
Grafana wit net hoe te berekkenjen tusken mjittingen + warskôging, mar wy moatte in taryf (fersiken-flaters) / fersiken.
De flaters lykje ferfelend:

En minder kwea as besjoen mei suksesfolle oanfragen:

Okee, wy kinne it taryf yn 'e tsjinst foarôf berekkenje foar grafana, en yn guon gefallen sil dit wurkje. Mar net yn ús, want... foar elk kanaal wurdt syn eigen ferhâlding beskôge as "normaal", en warskôgings wurkje neffens statyske wearden (wy sykje se mei ús eagen, feroarje se as d'r faak warskôgings binne).
Dit binne foarbylden fan "normaal" foar ferskate kanalen:


Wy negearje it foarige punt en gean der fan út dat it "normale" byld fergelykber is foar alle leveransiers. No alles is goed, en wy kinne krije troch mei warskôgings yn grafana?
Wy kinne, mar wy wolle echt net, om't wy ien fan 'e opsjes moatte kieze:
a) meitsje in protte grafiken foar elk kanaal apart (en begeliede se pynlik)
b) lit ien kaart litte mei alle kanalen (en ferdwale yn 'e kleurige rigels en oanpaste warskôgings)

Hoe hasto it dien?
Nochris is d'r in goed begjinfoarbyld yn 'e dokumintaasje (), kin yn ferlykbere problemen besjoen wurde of as basis nommen wurde.
Wat wy op it lêst dien hawwe:
- meidwaan oan twa searjes yn in pear oeren, groepearje troch kanalen;
- folje de rige per groep yn as der gjin gegevens wiene;
- fergelykje de mediaan fan 'e lêste 10 minuten mei eardere gegevens;
- wy roppe as wy wat fine;
- wy skriuwe de berekkene tariven en warskôgings dy't barde yn influxdb;
- stjoer in nuttich berjocht nei slack.
Neffens my is it ús slagge om alles wat wy oan 'e ein krije woene (en noch wat mear mei oanpaste handlers) sa moai mooglik te berikken.
Jo kinne sjen op github.com и it resultearjende skript.
In foarbyld fan de resultearjende koade:
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 de konklúzje?
Kapacitor is geweldich by it útfieren fan tafersjoch-alerts mei in boskje groepearrings, it útfieren fan ekstra berekkeningen basearre op al opnommen metriken, it útfieren fan oanpaste aksjes en it útfieren fan skripts (udf).
De barriêre foar yngong is net heul heech - besykje it as grafana of oare ark jo winsken net folslein befredigje.
Boarne: www.habr.com
