Kapasitorda ölçüləri idarə etmək üçün fəndlər

Çox güman ki, bu gün heç kim xidmət ölçülərini toplamaq niyə lazım olduğunu soruşmur. Növbəti məntiqi addım toplanmış ölçülər üçün siqnal qurmaqdır ki, bu da sizin üçün əlverişli olan kanallarda (poçt, Slack, Telegram) məlumatlarda hər hansı sapma barədə məlumat verəcəkdir. Onlayn otel bron xidmətində Ostrovok.ru xidmətlərimizin bütün ölçüləri InfluxDB-yə tökülür və Grafana-da göstərilir və əsas xəbərdarlıq da orada konfiqurasiya edilir. “Bir şeyi hesablamaq və onunla müqayisə etmək lazımdır” kimi tapşırıqlar üçün Kapasitordan istifadə edirik.

Kapasitorda ölçüləri idarə etmək üçün fəndlər
Kapacitor InfluxDB-dən ölçüləri emal edə bilən TICK yığınının bir hissəsidir. O, bir neçə ölçməni birləşdirə (qoşula), alınan məlumatdan faydalı bir şey hesablaya, nəticəni yenidən InfluxDB-yə yaza, Slack/Telegram/mail-ə xəbərdarlıq göndərə bilər.

Bütün yığın sərin və ətraflıdır sənədlər, lakin təlimatlarda açıq şəkildə göstərilməyən faydalı şeylər həmişə olacaq. Bu yazıda mən bir sıra belə faydalı, qeyri-aşkar məsləhətlər toplamağa qərar verdim (TICKscipt-in əsas sintaksisi təsvir edilmişdir) burada) və problemlərimizdən birinin həlli nümunəsi ilə onların necə tətbiq oluna biləcəyini göstərin.

Gidelim!

float & int, hesablama xətaları

Kastalar vasitəsilə həll edilən tamamilə standart problem:

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

default() istifadə

Əgər etiket/sahə doldurulmazsa, hesablama xətaları baş verəcək:

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

birləşməni doldurun (daxili və xarici)

Varsayılan olaraq, qoşulma məlumat olmayan nöqtələri (daxili) ləğv edəcək.
Fill('null') ilə xarici birləşmə yerinə yetiriləcək, bundan sonra siz default() etməli və boş dəyərləri doldurmalısınız:

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

Burada hələ də bir nüans var. Yuxarıdakı misalda seriyalardan biri (res1 və ya res2) boşdursa, nəticədə yaranan sıra (məlumatlar) da boş olacaq. Github-da bu mövzuda bir neçə bilet var (1633, 1871, 6967) – düzəlişlər gözləyirik və bir az əziyyət çəkirik.

Hesablamalarda şərtlərdən istifadə (lambda varsa)

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

Dövr üçün boru kəmərindən son beş dəqiqə

Məsələn, son beş dəqiqənin dəyərlərini əvvəlki həftə ilə müqayisə etməlisiniz. Siz iki ayrı partiyada iki məlumat toplusunu götürə və ya daha böyük müddətdən məlumatların bir hissəsini çıxara bilərsiniz:

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

Son beş dəqiqə üçün alternativ, göstərilən vaxtdan əvvəl məlumatları kəsən BarrierNode istifadə etmək olardı:

|barrier()
        .period(5m)

Mesajda Go şablonlarından istifadə nümunələri

Şablonlar paketdəki formata uyğundur mətn.şablonAşağıda tez-tez rast gəlinən bulmacalar var.

Əgər

Biz hər şeyi qaydasına salırıq və insanları bir daha mətnlə təhrik etmirik:

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

Mesajda onluq nöqtədən sonra iki rəqəm

Mesajın oxunaqlılığının yaxşılaşdırılması:

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

Mesajda dəyişənlərin genişləndirilməsi

“Niyə qışqırır” sualına cavab vermək üçün mesajda daha çox məlumat göstərilir.

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

Unikal xəbərdarlıq identifikatoru

Məlumatda birdən çox qrup olduqda bu zəruri bir şeydir, əks halda yalnız bir xəbərdarlıq yaranacaq:

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

Xüsusi idarəedicilər

İşləyicilərin böyük siyahısına keçmiş parametrlər (stdin) ilə skriptinizi icra etməyə imkan verən exec daxildir - yaradıcılıq və başqa heç nə!

Bizim adətlərimizdən biri, bildirişləri gecikdirmək üçün kiçik bir Python skriptidir.
Əvvəlcə mesajda icazə ilə qorunan qrafana şəklini göndərmək istədik. Bundan sonra, eyni qrupdan əvvəlki xəbərdarlığa ayrıca mesaj kimi deyil, mövzuya OK yazın. Bir az sonra - son X dəqiqədə ən çox yayılmış səhvi mesaja əlavə edin.

Ayrı bir mövzu digər xidmətlərlə əlaqə və xəbərdarlıqla başlanan hər hansı hərəkətlərdir (yalnız monitorinqiniz kifayət qədər yaxşı işləyirsə).
Slack_handler.py bizim özümüz tərəfindən yazılmış skriptimiz olduğu işləyici təsvirinin nümunəsi:

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"]

Necə debug etmək olar?

Giriş çıxışı ilə seçim

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

Saat (cli): kapacitor -url host və ya ip:9092 qeyd lvl=xəta

httpOut ilə seçim

Cari boru kəmərindəki məlumatları göstərir:

|httpOut('something')

Baxın (alın): host və ya ip:9092/kapacitor/v1/tasks/task_name/bir şey

İcra diaqramı

  • Hər bir tapşırıq formatda faydalı nömrələri olan icra ağacını qaytarır qrafviz.
  • Bir blok götürün nöqtə.
  • Onu izləyiciyə yapışdırın, həzz alın.

Dırmığı başqa haradan əldə etmək olar?

geri yazma zamanı influxdb-də vaxt damğası

Məsələn, biz saatda sorğuların cəmi üçün xəbərdarlıq qurduq (groupBy(1h)) və influxdb-də baş verən xəbərdarlığı qeyd etmək istəyirik (problemin faktını qrafanadakı qrafikdə gözəl göstərmək üçün).

influxDBOut() xəbərdarlıqdan vaxt möhürünə vaxt dəyərini yazacaq; müvafiq olaraq, diaqramdakı nöqtə xəbərdarlıqdan əvvəl/gec yazılacaq.

Dəqiqlik tələb olunduqda: biz cari vaxt damğası ilə influxdb-ə məlumat yazacaq fərdi işləyiciyə zəng etməklə bu problemi həll edirik.

docker, qurmaq və yerləşdirmə

Başlanğıcda kondansatör [yükləmə] blokunda konfiqurasiyada göstərilən kataloqdan tapşırıqları, şablonları və işləyiciləri yükləyə bilər.

Tapşırığı düzgün yaratmaq üçün sizə aşağıdakılar lazımdır:

  1. Fayl adı – skript identifikatoru/adı ilə genişləndirilir
  2. Növ - axın/top
  3. dbrp – skriptin hansı verilənlər bazası + siyasətində işlədiyini göstərmək üçün açar söz (dbrp “təchizatçı.” “avtojen”)

Bəzi toplu tapşırıqda dbrp ilə bir xətt yoxdursa, bütün xidmət başlamaqdan imtina edəcək və bu barədə jurnalda vicdanla yazacaq.

Xronoqrafda isə əksinə, bu xətt mövcud olmamalıdır, interfeys vasitəsilə qəbul edilmir və xəta yaradır.

Konteyner qurarkən sındırın: //.+dbrp ilə sətirlər varsa, Dockerfile -1 ilə çıxır ki, bu da konstruksiyanın yığılması zamanı uğursuzluğun səbəbini dərhal anlamağa imkan verəcəkdir.

bir çoxuna qoşulun

Nümunə tapşırığı: bir həftə ərzində xidmətin iş vaxtının 95-ci faizini götürməlisiniz, son 10 dəqiqənin hər dəqiqəsini bu dəyərlə müqayisə edin.

Siz birə çox birləşmə edə bilməzsiniz, bir qrup nöqtə üzərində sonuncu/orta/median qovşağı axına çevirir, “uşaq uyğun olmayan kənarları əlavə edə bilməz: toplu -> axın” xətası qaytarılacaq.

Lambda ifadəsindəki dəyişən kimi partiyanın nəticəsi də əvəz edilmir.

Birinci partiyadan lazımi nömrələri udf vasitəsilə fayla saxlamaq və bu faylı sideload vasitəsilə yükləmək imkanı var.

Bununla nəyi həll etdik?

Bizim 100-ə yaxın otel təchizatçımız var, onların hər birinin bir neçə əlaqəsi ola bilər, buna kanal deyək. Bu kanalların təxminən 300-ü var, kanalların hər biri düşə bilər. Bütün qeydə alınmış ölçülərdən biz səhv dərəcəsini (sorğu və səhvlər) izləyəcəyik.

Niyə qrafana deyil?

Grafana-da konfiqurasiya edilmiş səhv xəbərdarlıqlarının bir sıra çatışmazlıqları var. Bəziləri kritikdir, bəziləri vəziyyətdən asılı olaraq gözlərinizi bağlaya bilərsiniz.

Grafana ölçmələr + xəbərdarlıq arasında hesablamağı bilmir, lakin bizə bir dərəcə (istəklər-səhvlər)/istəklər lazımdır.

Səhvlər pis görünür:

Kapasitorda ölçüləri idarə etmək üçün fəndlər

Uğurlu istəklərlə baxıldığında daha az pislik:

Kapasitorda ölçüləri idarə etmək üçün fəndlər

Yaxşı, qrafanadan əvvəl xidmətdə tarifi əvvəlcədən hesablaya bilərik və bəzi hallarda bu işləyəcək. Amma bizdə yox, çünki... hər bir kanal üçün öz nisbəti "normal" hesab olunur və xəbərdarlıqlar statik dəyərlərə uyğun işləyir (biz onları gözümüzlə axtarırıq, tez-tez siqnallar olduqda onları dəyişdiririk).

Bunlar müxtəlif kanallar üçün "normal" nümunələridir:

Kapasitorda ölçüləri idarə etmək üçün fəndlər

Kapasitorda ölçüləri idarə etmək üçün fəndlər

Əvvəlki məqama məhəl qoymuruq və "normal" mənzərənin bütün təchizatçılar üçün oxşar olduğunu güman edirik. İndi hər şey qaydasındadır və biz grafanada xəbərdarlıqlarla öhdəsindən gələ bilərik?
Biz edə bilərik, amma həqiqətən istəmirik, çünki seçimlərdən birini seçməliyik:
a) hər bir kanal üçün ayrıca çoxlu qrafiklər hazırlayın (və onları ağrılı şəkildə müşayiət edin)
b) bütün kanallarla bir diaqram buraxın (və rəngli xətlərdə və fərdi xəbərdarlıqlarda itin)

Kapasitorda ölçüləri idarə etmək üçün fəndlər

Bunu necə etdin?

Yenə də sənədlərdə yaxşı bir başlanğıc nümunəsi var (Birləşdirilmiş seriyalar üzrə dərəcələrin hesablanması), oxşar problemlərə nəzər salmaq və ya əsas götürmək olar.

Sonda nə etdik:

  • bir neçə saat ərzində iki seriyaya qoşulmaq, kanallar üzrə qruplaşdırmaq;
  • məlumat olmadıqda seriyanı qrupa görə doldurun;
  • son 10 dəqiqənin medianı əvvəlki məlumatlarla müqayisə edin;
  • bir şey tapsaq qışqırıq;
  • biz influxdb-də baş verən hesablanmış dərəcələri və xəbərdarlıqları yazırıq;
  • boşluğa faydalı mesaj göndərin.

Fikrimcə, sonda əldə etmək istədiyimiz hər şeyə (və hətta xüsusi işləyicilərlə bir az daha çox) mümkün qədər gözəl şəkildə nail ola bildik.

github.com saytına baxa bilərsiniz kod nümunəsi и minimal dövrə (qrafik) ortaya çıxan skript.

Nəticə koduna bir nümunə:

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)

Nəticə nədir?

Kapacitor çoxlu qruplaşdırma ilə monitorinq xəbərdarlıqlarını yerinə yetirməkdə, artıq qeydə alınmış ölçülərə əsasən əlavə hesablamalar aparmaqda, xüsusi hərəkətlər etməkdə və skriptləri (udf) işlətməkdə əladır.

Giriş maneəsi çox yüksək deyil - qrafana və ya digər alətlər arzularınızı tam təmin etmirsə, cəhd edin.

Mənbə: www.habr.com

Добавить комментарий