Trukoj por prilaborado de metrikoj en Kapacitor

Plej verŝajne, hodiaŭ neniu demandas kial necesas kolekti servajn metrikojn. La sekva logika paŝo estas agordi alarmon por la kolektitaj metrikoj, kiu sciigos pri ajnaj devioj en la datumoj en kanaloj konvenaj por vi (poŝto, Slack, Telegramo). En la reta hotela rezerva servo Ostrovok.ru ĉiuj metrikoj de niaj servoj estas verŝitaj en InfluxDB kaj montrataj en Grafana, kaj baza atentigo ankaŭ estas agordita tie. Por taskoj kiel "vi devas kalkuli ion kaj kompari kun ĝi", ni uzas Kapacitor.

Trukoj por prilaborado de metrikoj en Kapacitor
Kapacitor estas parto de la TICK-stako, kiu povas prilabori metrikojn de InfluxDB. Ĝi povas kunligi plurajn mezuradojn kune (kuniĝi), kalkuli ion utilan el la ricevitaj datumoj, skribi la rezulton reen al InfluxDB, sendi atentigon al Slack/Telegram/mail.

La tuta stako estas malvarmeta kaj detala dokumentado, sed ĉiam estos utilaj aferoj, kiuj ne estas eksplicite indikitaj en la manlibroj. En ĉi tiu artikolo, mi decidis kolekti kelkajn tiajn utilajn, neevidentajn konsiletojn (la baza sintakso de TICKscipt estas priskribita tie) kaj montru kiel ili povas esti aplikataj uzante ekzemplon de solvado de unu el niaj problemoj.

Ni iru!

float & int, kalkuleraroj

Absolute norma problemo, solvita per kastoj:

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

Uzante defaŭlton ()

Se etikedo/kampo ne estas plenigita, kalkuleraroj okazos:

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

plenigi kunigon (interna vs ekstera)

Defaŭlte, aliĝi forĵetos punktojn kie ne estas datumoj (interna).
Kun fill('null'), ekstera kunigo estos farita, post kio vi devas fari defaŭltan () kaj plenigi la malplenajn valorojn:

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

Ĉi tie ankoraŭ estas nuanco. En la supra ekzemplo, se unu el la serioj (res1 aŭ res2) estas malplena, la rezulta serio (datenoj) ankaŭ estos malplena. Estas pluraj biletoj pri ĉi tiu temo ĉe Github (1633, 1871, 6967) – ni atendas korektojn kaj iom suferas.

Uzante kondiĉojn en kalkuloj (se en lambda)

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

Lastaj kvin minutoj de la dukto por la periodo

Ekzemple, vi devas kompari la valorojn de la lastaj kvin minutoj kun la antaŭa semajno. Vi povas preni du arojn da datumoj en du apartaj aroj aŭ eltiri parton de la datumoj de pli granda periodo:

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

Alternativo por la lastaj kvin minutoj estus uzi BarrierNode, kiu fortranĉas datumojn antaŭ la specifita tempo:

|barrier()
        .period(5m)

Ekzemploj de uzado de Go-ŝablonoj en mesaĝo

Ŝablonoj respondas al la formato de la pakaĵo teksto.ŝablonoMalsupre estas kelkaj ofte renkontitaj enigmoj.

se-alie

Ni ordigas aferojn kaj ne deĉenigas homojn kun teksto denove:

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

Du ciferoj post la dekuma punkto en mesaĝo

Plibonigante la legeblecon de la mesaĝo:

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

Vastigante variablojn en mesaĝo

Ni montras pli da informoj en la mesaĝo por respondi la demandon "Kial ĝi krias"?

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

Unika atentiga identigilo

Ĉi tio estas necesa, kiam estas pli ol unu grupo en la datumoj, alie nur unu atentigo estos generita:

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

Propra prizorganto

La granda listo de pritraktantoj inkluzivas exec, kiu ebligas al vi ekzekuti vian skripton kun la pasitaj parametroj (stdin) - kreemo kaj nenio pli!

Unu el niaj kutimoj estas malgranda Python-skripto por sendi sciigojn al slack.
Komence, ni volis sendi rajtige protektitan grafana bildon en mesaĝo. Poste, skribu OK en la fadeno al la antaŭa atentigo de la sama grupo, kaj ne kiel aparta mesaĝo. Iom poste - aldonu al la mesaĝo la plej oftan eraron en la lastaj X minutoj.

Aparta temo estas komunikado kun aliaj servoj kaj ajnaj agoj komencitaj per atentigo (nur se via monitorado funkcias sufiĉe bone).
Ekzemplo de priskribo de pritraktilo, kie slack_handler.py estas nia memskribita skripto:

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

Kiel sencimigi?

Opcio kun protokolo eligo

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

Watch (cli): kapacitor -url gastiganto-aŭ-ip:9092 protokoloj lvl=eraro

Opcio kun httpOut

Montras datumojn en la nuna dukto:

|httpOut('something')

Rigardu (akiri): gastiganto-aŭ-ip:9092/kapacitor/v1/tasks/task_name/something

Skemo de ekzekuto

  • Ĉiu tasko resendas ekzekut-arbon kun utilaj nombroj en la formato graphviz.
  • Prenu blokon skalara.
  • Algluu ĝin en spektilon, ĝui.

Kie alie vi povas akiri rastilon?

tempomarko en influxdb sur reskribo

Ekzemple, ni starigas alarmon por la sumo de petoj por horo (groupBy(1h)) kaj volas registri la atentigon, kiu okazis en influxdb (por bele montri la fakton de la problemo sur la grafikaĵo en grafana).

influxDBOut() skribos la tempovaloron de la atentigo al la tempomarko; sekve, la punkto sur la diagramo estos skribita pli frue/poste ol la atentigo alvenis.

Kiam precizeco estas postulata: ni traktas ĉi tiun problemon nomante kutiman prizorganton, kiu skribos datumojn al influxdb kun la nuna tempomarko.

docker, konstruo kaj deplojo

Ĉe ekfunkciigo, kapacitor povas ŝargi taskojn, ŝablonojn kaj prizorgilojn de la dosierujo specifita en la agordo en la [ŝarĝo] bloko.

Por ĝuste krei taskon, vi bezonas la jenajn aferojn:

  1. Dosiernomo - vastigita al skripto id/nomo
  2. Tipo - rivereto / aro
  3. dbrp - ŝlosilvorto por indiki en kiu datumbazo + politiko funkcias la skripto (dbrp "provizanto." "aŭtogen")

Se iu grupa tasko ne enhavas linion kun dbrp, la tuta servo rifuzos komenci kaj honeste skribos pri ĝi en la protokolo.

En kronografo, male, ĉi tiu linio ne devus ekzisti; ĝi ne estas akceptita per la interfaco kaj generas eraron.

Haki dum konstruado de ujo: Dockerfile eliras per -1 se estas linioj kun //.+dbrp, kio permesos al vi tuj kompreni la kialon de la malsukceso dum kunigo de la konstruo.

kunigi unu al multaj

Ekzempla tasko: vi devas preni la 95-an procenton de la funkciada tempo de la servo dum semajno, komparu ĉiun minuton de la lastaj 10 kun ĉi tiu valoro.

Vi ne povas fari unu-al-multajn aliĝojn, lasta/mezana/mediano super grupo de punktoj igas la nodon en fluon, la eraro "ne povas aldoni infanajn malkongruajn randojn: batch -> rivereto" estos resendita.

La rezulto de aro, kiel variablo en lambda esprimo, ankaŭ ne estas anstataŭigita.

Estas eblo konservi la necesajn nombrojn de la unua aro al dosiero per udf kaj ŝargi ĉi tiun dosieron per flankŝarĝo.

Kion ni solvis per ĉi tio?

Ni havas ĉirkaŭ 100 hotelprovizantoj, ĉiu el ili povas havi plurajn rilatojn, ni nomu ĝin kanalo. Estas proksimume 300 el ĉi tiuj kanaloj, ĉiu el la kanaloj povas defali. El ĉiuj registritaj metrikoj, ni kontrolos la erarprocenton (petoj kaj eraroj).

Kial ne grafana?

Erar-atentigoj agorditaj en Grafana havas plurajn malavantaĝojn. Iuj estas kritikaj, iuj vi povas fermi viajn okulojn, depende de la situacio.

Grafana ne scias kiel kalkuli inter mezuradoj + atentigo, sed ni bezonas tarifon (petoj-eraroj)/petoj.

La eraroj aspektas malbonaj:

Trukoj por prilaborado de metrikoj en Kapacitor

Kaj malpli malbona se rigardite kun sukcesaj petoj:

Trukoj por prilaborado de metrikoj en Kapacitor

Bone, ni povas antaŭkalkuli la tarifon en la servo antaŭ grafana, kaj en iuj kazoj ĉi tio funkcios. Sed ne en la nia, ĉar... por ĉiu kanalo ĝia propra proporcio estas konsiderata "normala", kaj atentigoj funkcias laŭ statikaj valoroj (ni serĉas ilin per niaj okuloj, ŝanĝas ilin se estas oftaj atentigoj).

Ĉi tiuj estas ekzemploj de "normala" por malsamaj kanaloj:

Trukoj por prilaborado de metrikoj en Kapacitor

Trukoj por prilaborado de metrikoj en Kapacitor

Ni ignoras la antaŭan punkton kaj supozas, ke la "normala" bildo estas simila por ĉiuj provizantoj. Nun ĉio estas en ordo, kaj ni povas elteni per atentigoj en grafana?
Ni povas, sed ni vere ne volas, ĉar ni devas elekti unu el la ebloj:
a) faru multajn grafikaĵojn por ĉiu kanalo aparte (kaj dolore akompanu ilin)
b) lasu unu diagramon kun ĉiuj kanaloj (kaj perdiĝu en la buntaj linioj kaj personigitaj atentigoj)

Trukoj por prilaborado de metrikoj en Kapacitor

Kiel vi faris ĝin?

Denove, estas bona komenca ekzemplo en la dokumentado (Kalkulado de tarifoj tra kunigitaj serioj), povas esti rigardita aŭ prenita kiel bazo en similaj problemoj.

Kion ni faris finfine:

  • aliĝu du serioj en kelkaj horoj, grupiĝante laŭ kanaloj;
  • plenigu la serion laŭ grupo se ne estis datumoj;
  • komparu la medianon de la lastaj 10 minutoj kun antaŭaj datumoj;
  • ni krias, se ni trovas ion;
  • ni skribas la kalkulitajn tarifojn kaj atentigojn, kiuj okazis en influxdb;
  • sendu utilan mesaĝon al slack.

Laŭ mi, ni sukcesis atingi ĉion, kion ni volis atingi fine (kaj eĉ iom pli per kutimaj pritraktiloj) kiel eble plej bele.

Vi povas vidi ĝin ĉe github.com ekzemplo de kodo и minimuma cirkvito (grafviz) la rezulta skripto.

Ekzemplo de la rezulta kodo:

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)

Kio estas la konkludo?

Kapacitor bonege plenumas monitorado-atentigojn kun amaso da grupiĝoj, plenumante pliajn kalkulojn bazitajn sur jam registritaj metrikoj, plenumante kutimajn agojn kaj ruli skriptojn (udf).

La baro al eniro ne estas tre alta - provu ĝin se grafana aŭ aliaj iloj ne plene kontentigas viajn dezirojn.

fonto: www.habr.com

Aldoni komenton