Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар

Сірә, бүгінде ешкім сервистік көрсеткіштерді жинаудың не үшін қажет екенін сұрамайды. Келесі логикалық қадам сізге ыңғайлы арналардағы (пошта, Slack, Telegram) деректердегі кез келген ауытқулар туралы хабарлайтын жиналған көрсеткіштер үшін ескертуді орнату болып табылады. Қонақүйді онлайн брондау қызметінде Ostrovok.ru біздің қызметтеріміздің барлық көрсеткіштері InfluxDB ішіне құйылады және Grafana ішінде көрсетіледі және негізгі ескертулер де сонда конфигурацияланады. «Сіз бір нәрсені есептеп, онымен салыстыруыңыз керек» сияқты тапсырмалар үшін біз Kapacitor пайдаланамыз.

Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар
Конденсатор InfluxDB деректерін өңдей алатын TICK стек бөлігі болып табылады. Ол бірнеше өлшемдерді біріктіре алады (қосылу), алынған деректерден пайдалы нәрсені есептей алады, нәтижені 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') арқылы сыртқы біріктіру орындалады, одан кейін әдепкі() орындап, бос мәндерді толтыру керек:

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

Бұл жерде әлі де бір нюанс бар. Жоғарыдағы мысалда қатарлардың біреуі (res1 немесе res2) бос болса, нәтижесінде алынған қатар (деректер) де бос болады. Github сайтында осы тақырып бойынша бірнеше билеттер бар (1633, 1871, 6967) – жөндеуді күтіп, аздап қиналып жатырмыз.

Есептерде шарттарды пайдалану (лямбда болса)

|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" }}')

Арнайы өңдеушілер

Өңдеушілердің үлкен тізімінде сценарийді берілген параметрлермен (stdin) орындауға мүмкіндік беретін exec кіреді - шығармашылық және басқа ештеңе жоқ!

Біздің әдет-ғұрыптарымыздың бірі - хабарландыруларды босаңсуға жіберуге арналған шағын Python сценарийі.
Алдымен біз хабарламада авторизациямен қорғалған графана суретін жібергіміз келді. Одан кейін бөлек хабар ретінде емес, сол топтың алдыңғы ескертуіне ағында OK деп жазыңыз. Біраз уақыттан кейін - хабарламаға соңғы 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): конденсатор -url хост-немесе-ip:9092 журналдар lvl=қате

httpOut көмегімен опция

Ағымдағы құбырдағы деректерді көрсетеді:

|httpOut('something')

Қарау (алу): хост-немесе-ip:9092/kapacitor/v1/tasks/task_name/bir нәрсе

Орындау схемасы

  • Әрбір тапсырма пішімде пайдалы сандары бар орындау тармағын қайтарады графвиз.
  • Блок алыңыз нүкте.
  • Оны көру құралына қойыңыз, ләззат алыңыз.

Тырмақты тағы қайдан алуға болады?

кері жазу кезінде influxdb ішіндегі уақыт белгісі

Мысалы, біз сағатына сұраулар сомасына ескерту орнаттық (groupBy(1h)) және influxdb ішінде орын алған ескертуді жазғымыз келеді (графанадағы графикте мәселенің фактісін әдемі көрсету үшін).

influxDBOut() ескертуден уақыт белгісіне уақыт мәнін жазады; сәйкесінше, диаграммадағы нүкте ескерту келгеннен ерте/кеш жазылады.

Дәлдік қажет болғанда: біз ағымдағы уақыт белгісімен influxdb деректерін жазатын реттелетін өңдеушіге қоңырау шалу арқылы осы мәселені шешеміз.

докер, құрастыру және орналастыру

Іске қосу кезінде конденсатор тапсырмаларды, үлгілерді және өңдеушілерді [жүктеу] блогындағы конфигурацияда көрсетілген каталогтан жүктей алады.

Тапсырманы дұрыс жасау үшін сізге мыналар қажет:

  1. Файл атауы – сценарий идентификаторына/атауға кеңейтілген
  2. Түрі – ағын/топтама
  3. dbrp – сценарий қай дерекқорда + саясатта жұмыс істейтінін көрсететін кілт сөз (dbrp “жабдықтаушы.” “автоген”)

Егер кейбір пакеттік тапсырмада dbrp бар жол болмаса, бүкіл қызмет бастаудан бас тартады және ол туралы журналға шынайы жазады.

Хронографта, керісінше, бұл сызық болмауы керек, ол интерфейс арқылы қабылданбайды және қатені тудырады.

Контейнерді құру кезінде бұзу: //.+dbrp бар жолдар болса, Dockerfile -1 арқылы шығады, бұл құрастыруды құрастыру кезінде сәтсіздіктің себебін бірден түсінуге мүмкіндік береді.

көпке бірігу

Тапсырманың мысалы: бір апта ішінде қызметтің жұмыс уақытының 95-процентильін алуыңыз керек, соңғы 10 минуттың әрбір минутын осы мәнмен салыстырыңыз.

Бірден көпке біріктіруді орындай алмайсыз, нүктелер тобы бойынша соңғы/орташа/медиана түйінді ағынға айналдырады, «бала сәйкес келмейтін жиектерді қосу мүмкін емес: топтама -> ағын» қатесі қайтарылады.

Ламбда өрнегіндегі айнымалы ретінде топтаманың нәтижесі де ауыстырылмайды.

Бірінші топтамадан қажетті нөмірлерді udf арқылы файлға сақтау және бұл файлды бүйірлік жүктеу арқылы жүктеу мүмкіндігі бар.

Мұнымен не шештік?

Бізде 100-ге жуық қонақүй жеткізушілері бар, олардың әрқайсысында бірнеше байланыс болуы мүмкін, оны арна деп атаймыз. Бұл арналардың шамамен 300-і бар, олардың әрқайсысы құлап кетуі мүмкін. Барлық жазылған көрсеткіштердің ішінен біз қателік деңгейін бақылаймыз (сұраулар мен қателер).

Неге графана емес?

Grafana бағдарламасында конфигурацияланған қате ескертулерінің бірнеше кемшіліктері бар. Кейбіреулері сыни, кейбіреулері жағдайға байланысты көзіңізді жұмып тастай аласыз.

Grafana өлшеулер + ескертулер арасында қалай есептеу керектігін білмейді, бірақ бізге мөлшерлеме (сұраныс-қателер)/сұраулар қажет.

Қателер жағымсыз көрінеді:

Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар

Сәтті сұраулармен қараған кезде зұлымдық азырақ:

Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар

Жарайды, біз графанаға дейін қызметтегі тарифті алдын ала есептей аламыз және кейбір жағдайларда бұл жұмыс істейді. Бірақ біздікі емес, өйткені... әрбір арна үшін оның жеке қатынасы «қалыпты» болып саналады және ескертулер статикалық мәндерге сәйкес жұмыс істейді (біз оларды өз көзімізбен іздейміз, ескертулер жиі болса, оларды өзгертеміз).

Бұл әртүрлі арналар үшін «қалыпты» мысалдар:

Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар

Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар

Біз алдыңғы тармақты елемейміз және «қалыпты» сурет барлық жеткізушілер үшін ұқсас деп есептейміз. Енді бәрі жақсы, біз графанадағы ескертулермен жұмыс істей аламыз ба?
Біз жасай аламыз, бірақ біз шынымен де қаламаймыз, өйткені біз опциялардың бірін таңдауымыз керек:
а) әр арна үшін жеке-жеке көптеген графиктер жасаңыз (және олармен бірге ауырады)
б) барлық арналармен бір диаграмма қалдырыңыз (және түрлі-түсті сызықтар мен теңшелген ескертулерде жоғалыңыз)

Kapacitor ішіндегі метрикаларды өңдеуге арналған трюктар

Сіз мұны қалай жасадыңыз?

Тағы да, құжаттамада жақсы бастапқы мысал бар (Қосылған қатарлар бойынша мөлшерлемелерді есептеу), ұқсас мәселелерде қарауға немесе негіз ретінде алуға болады.

Соңында не істедік:

  • арналар бойынша топтастыру, бірнеше сағат ішінде екі серияға қосылу;
  • деректер болмаған жағдайда топ бойынша қатарды толтыру;
  • соңғы 10 минуттың медианасын алдыңғы деректермен салыстыру;
  • бірдеңе тапсақ айғайлаймыз;
  • біз influxdb-де болған есептелген мөлшерлемелерді және ескертулерді жазамыз;
  • босқа пайдалы хабар жіберіңіз.

Менің ойымша, біз ең соңында қол жеткізгіміз келгеннің бәріне (және одан да көп пайдаланушы өңдеушілерімен) мүмкіндігінше әдемі қол жеткізе алдық.

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)

Қорытынды қандай?

Kapacitor көптеген топтармен бақылау-ескертулерді орындауда, бұрыннан жазылған метрикаға негізделген қосымша есептеулерді орындауда, реттелетін әрекеттерді орындауда және сценарийлерді (udf) іске қосуда тамаша.

Кіру кедергісі өте жоғары емес - егер графана немесе басқа құралдар сіздің қалауыңызды толығымен қанағаттандырмаса, оны көріңіз.

Ақпарат көзі: www.habr.com

пікір қалдыру