Најверојатно, денес никој не прашува зошто е неопходно да се соберат показатели за услуги. Следниот логичен чекор е да поставите предупредување за собраните метрики, кои ќе известуваат за какви било отстапувања во податоците во каналите погодни за вас (mail, Slack, Telegram). Во услугата за онлајн резервации на хотели сите показатели на нашите услуги се влеваат во InfluxDB и се прикажуваат во Grafana, а основното предупредување е исто така конфигурирано таму. За задачи како „треба да пресметате нешто и да споредите со него“, ние користиме Kapacitor.

Капациторот е дел од стекот TICK што може да обработува метрика од InfluxDB. Може да поврзе неколку мерења заедно (да се приклучи), да пресмета нешто корисно од примените податоци, да го напише резултатот назад во InfluxDB, да испрати предупредување до Slack/Telegram/mail.
Целиот куп е кул и детален , но секогаш ќе има корисни работи кои не се експлицитно наведени во прирачниците. Во оваа статија, решив да соберам голем број такви корисни, неочигледни совети (опишана е основната синтакса на TICKscipt ) и покажете како тие можат да се применат користејќи пример за решавање на еден од нашите проблеми.
Ајде да одиме!
float & int, грешки во пресметката
Апсолутно стандарден проблем, решен преку касти:
var alert_float = 5.0
var alert_int = 10
data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int"))
Користење на стандардно()
Ако ознаката/полето не е пополнето, ќе се појават грешки во пресметката:
|default()
.tag('status', 'empty')
.field('value', 0)
пополнете спој (внатрешен наспроти надворешен)
Стандардно, приклучувањето ќе ги отфрли точките каде што нема податоци (внатрешни).
Со fill('null'), ќе се изврши надворешно спојување, по што треба да направите default() и да ги пополните празните вредности:
var data = res1
|join(res2)
.as('res1', 'res2)
.fill('null')
|default()
.field('res1.value', 0.0)
.field('res2.value', 100.0)
Овде сè уште има нијанса. Во примерот погоре, ако една од сериите (res1 или res2) е празна, добиената серија (податоци) исто така ќе биде празна. Има неколку билети на оваа тема на Github (, , ) – чекаме поправки и малку страдаме.
Користење на услови во пресметките (ако е во ламбда)
|eval(lambda: if("value" > 0, true, false)
Последните пет минути од гасоводот за периодот
На пример, треба да ги споредите вредностите на последните пет минути со претходната недела. Може да земете две серии на податоци во две посебни серии или да извлечете дел од податоците од поголем период:
|where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m)
Алтернатива за последните пет минути би била да се користи BarrierNode, кој ги отсекува податоците пред одреденото време:
|barrier()
.period(5m)
Примери за користење Go шаблони во пораката
Шаблоните одговараат на форматот од пакетот Подолу се дадени некои често сретнувани загатки.
ако-друго
Ги ставаме работите во ред и не ги поттикнуваме луѓето со текст уште еднаш:
|alert()
...
.message(
'{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}'
)
Две цифри по децималната точка во пораката
Подобрување на читливоста на пораката:
|alert()
...
.message(
'now value is {{ index .Fields "value" | printf "%0.2f" }}'
)
Проширување на променливите во пораката
Прикажуваме повеќе информации во пораката за да одговориме на прашањето „Зошто вика“?
var warnAlert = 10
|alert()
...
.message(
'Today value less then '+string(warnAlert)+'%'
)
Единствен идентификатор за предупредување
Ова е неопходна работа кога има повеќе од една група во податоците, во спротивно ќе се генерира само едно предупредување:
|alert()
...
.id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}')
Прилагодено ракувач
Големата листа на ракувачи вклучува exec, што ви овозможува да ја извршите вашата скрипта со поминатите параметри (stdin) - креативност и ништо повеќе!
Еден од нашите обичаи е мала Python скрипта за испраќање известувања до Slack.
Најпрво сакавме да испратиме графана слика заштитена со овластување во порака. После, напиши ОК во темата на претходното предупредување од истата група, а не како посебна порака. Малку подоцна - додајте ја во пораката најчестата грешка во последните X минути.
Посебна тема е комуникацијата со други услуги и какви било дејства иницирани со предупредување (само ако вашето следење работи доволно добро).
Пример за опис на управувачот, каде што slack_handler.py е нашата самонапишана скрипта:
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"]
Како да се дебагира?
Опција со излез на дневник
|log()
.level("error")
.prefix("something")
Гледајте (cli): capacitor -url :9092 логови lvl=грешка
Опција со httpOut
Ги прикажува податоците во тековниот гасовод:
|httpOut('something')
Гледајте (добијте): :9092/kapacitor/v1/tasks/task_name/something
Дијаграм за извршување
- Секоја задача враќа дрво за извршување со корисни броеви во форматот .
- Земете блок .
- Залепете го во гледачот, .
Каде на друго место можете да добиете гребло?
временски печат во influxdb при запишување
На пример, поставивме предупредување за збирот на барања на час (groupBy(1h)) и сакаме да го снимиме предупредувањето што се случи во influxdb (за убаво да го прикажеме фактот на проблемот на графиконот во grafana).
influxDBOut() ќе ја запише временската вредност од предупредувањето до временскиот печат, соодветно, точката на табелата ќе биде напишана порано/подоцна од пристигнувањето на предупредувањето;
Кога е потребна точност: работиме околу овој проблем со повикување на приспособен управувач, кој ќе запише податоци во influxdb со тековниот временски печат.
докер, изградба и распоредување
При стартување, капациторот може да вчита задачи, шаблони и управувачи од директориумот наведен во конфигурацијата во блокот [load].
За правилно да креирате задача, потребни ви се следниве работи:
- Име на датотека - проширено во id/име на скриптата
- Тип – поток/серија
- dbrp – клучен збор за означување во која база на податоци + политика работи скриптата (dbrp „добавувач“ „автоген“)
Ако некоја серија задача не содржи линија со dbrp, целата услуга ќе одбие да започне и искрено ќе пишува за тоа во дневникот.
Во хронограф, напротив, оваа линија не треба да постои, таа не се прифаќа преку интерфејсот и генерира грешка;
Хак при изградба на контејнер: Dockerfile излегува со -1 ако има линии со //.+dbrp, што ќе ви овозможи веднаш да ја разберете причината за неуспехот при составување на изградбата.
придружете се еден на многу
Пример задача: треба да го земете 95-от перцентил од времето на работа на услугата за една недела, споредете ја секоја минута од последните 10 со оваа вредност.
Не можете да направите приклучување еден-на-многу, последното/средното/средината преку група точки го претвора јазолот во поток, ќе се врати грешката „не може да се додадат дете неусогласени рабови: серија -> поток“.
Резултатот од серија, како променлива во ламбда изразот, исто така не е заменет.
Постои опција да ги зачувате потребните броеви од првата серија во датотека преку udf и да ја вчитате оваа датотека преку странично оптоварување.
Што решивме со ова?
Имаме околу 100 добавувачи на хотели, секој од нив може да има неколку врски, да го наречеме канал. Има приближно 300 од овие канали, секој од каналите може да падне. Од сите снимени метрики, ќе ја следиме стапката на грешки (барања и грешки).
Зошто да не и графана?
Известувањата за грешки конфигурирани во Grafana имаат неколку недостатоци. Некои се критични, на некои можете да ги затворите очите, во зависност од ситуацијата.
Графана не знае да пресметува помеѓу мерења + алармирање, но ни треба стапка (барања-грешки)/барања.
Грешките изгледаат непријатно:

И помалку зло кога се гледа со успешни барања:

Во ред, можеме однапред да ја пресметаме стапката во услугата пред grafana, и во некои случаи тоа ќе функционира. Но, не во нашата, бидејќи ... за секој канал, неговиот сооднос се смета за „нормален“, а предупредувањата работат според статички вредности (ги бараме со нашите очи, ги менуваме ако има чести предупредувања).
Ова се примери за „нормално“ за различни канали:


Ја игнорираме претходната точка и претпоставуваме дека „нормалната“ слика е слична за сите добавувачи. Сега се е во ред, а можеме да поминеме со аларми во графана?
Можеме, но навистина не сакаме, затоа што треба да избереме една од опциите:
а) направете многу графикони за секој канал посебно (и болно придружете ги)
б) оставете еден графикон со сите канали (и изгубете се во шарените линии и приспособените предупредувања)

Како го направи тоа?
Повторно, има добар почетен пример во документацијата (), може да се ѕирне или земе како основа во слични проблеми.
Што направивме на крајот:
- придружете се на две серии за неколку часа, групирајќи по канали;
- пополнете ја серијата по група ако немаше податоци;
- споредете ја медијаната од последните 10 минути со претходните податоци;
- викаме ако најдеме нешто;
- ги пишуваме пресметаните стапки и предупредувањата што се случија во influxdb;
- испратете корисна порака до slack.
Според мене, успеавме да постигнеме сè што сакавме да добиеме на крајот (па дури и малку повеќе со сопствени ракувачи) што е можно поубаво.
Можете да го видите на github.com и добиената скрипта.
Пример за добиениот код:
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)
Кој е заклучокот?
Капациторот е одличен во извршувањето на следење-предупредувања со еден куп групирања, вршење дополнителни пресметки врз основа на веќе снимени метрики, извршување на сопствени дејства и извршување скрипти (udf).
Бариерата за влез не е многу висока - обидете се ако графана или други алатки не ги задоволуваат целосно вашите желби.
Извор: www.habr.com
