Trik untuk memproses metrik dalam Kapacitor

Kemungkinan besar, hari ini tiada siapa yang bertanya mengapa perlu mengumpul metrik perkhidmatan. Langkah logik seterusnya ialah menyediakan makluman untuk metrik yang dikumpul, yang akan memberitahu tentang sebarang penyelewengan dalam data dalam saluran yang sesuai untuk anda (mel, Slack, Telegram). Dalam perkhidmatan tempahan hotel dalam talian Ostrovok.ru semua metrik perkhidmatan kami dituangkan ke dalam InfluxDB dan dipaparkan dalam Grafana, dan amaran asas juga dikonfigurasikan di sana. Untuk tugasan seperti "anda perlu mengira sesuatu dan bandingkan dengannya," kami menggunakan Kapacitor.

Trik untuk memproses metrik dalam Kapacitor
Kapacitor ialah sebahagian daripada timbunan TICK yang boleh memproses metrik daripada InfluxDB. Ia boleh menyambung beberapa ukuran bersama-sama (bergabung), mengira sesuatu yang berguna daripada data yang diterima, menulis keputusan kembali ke InfluxDB, menghantar makluman kepada Slack/Telegram/mel.

Keseluruhan timbunan adalah sejuk dan terperinci dokumentasi, tetapi akan sentiasa ada perkara berguna yang tidak dinyatakan secara jelas dalam manual. Dalam artikel ini, saya memutuskan untuk mengumpulkan beberapa petua yang berguna dan tidak jelas (sintaks asas TICKscipt diterangkan di sini) dan tunjukkan bagaimana ia boleh digunakan menggunakan contoh penyelesaian salah satu masalah kita.

Mari kita pergi!

float & int, ralat pengiraan

Masalah yang benar-benar standard, diselesaikan melalui kasta:

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

Menggunakan lalai()

Jika teg/medan tidak diisi, ralat pengiraan akan berlaku:

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

isikan sertai (dalam vs luar)

Secara lalai, join akan membuang titik yang tiada data (dalam).
Dengan fill('null'), gabungan luar akan dilakukan, selepas itu anda perlu melakukan default() dan isikan nilai kosong:

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

Masih ada nuansa di sini. Dalam contoh di atas, jika salah satu siri (res1 atau res2) kosong, siri (data) yang terhasil juga akan kosong. Terdapat beberapa tiket mengenai topik ini di Github (1633, 1871, 6967) – kami sedang menunggu pembetulan dan menderita sedikit.

Menggunakan syarat dalam pengiraan (jika dalam lambda)

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

Lima minit terakhir dari saluran paip untuk tempoh tersebut

Sebagai contoh, anda perlu membandingkan nilai lima minit terakhir dengan minggu sebelumnya. Anda boleh mengambil dua kelompok data dalam dua kelompok berasingan atau mengekstrak sebahagian daripada data daripada tempoh yang lebih besar:

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

Alternatif untuk lima minit terakhir ialah menggunakan BarrierNode, yang memotong data sebelum masa yang ditentukan:

|barrier()
        .period(5m)

Contoh penggunaan templat Go dalam mesej

Templat sepadan dengan format dari pakej teks.templatDi bawah adalah beberapa teka-teki yang sering ditemui.

jika tidak

Kami menyusun perkara dan tidak mencetuskan orang dengan teks sekali lagi:

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

Dua digit selepas titik perpuluhan dalam mesej

Meningkatkan kebolehbacaan mesej:

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

Mengembangkan pembolehubah dalam mesej

Kami memaparkan lebih banyak maklumat dalam mesej untuk menjawab soalan "Mengapa ia menjerit"?

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

Pengecam amaran unik

Ini adalah perkara yang perlu apabila terdapat lebih daripada satu kumpulan dalam data, jika tidak, hanya satu makluman akan dijana:

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

Pengendali tersuai

Senarai besar pengendali termasuk exec, yang membolehkan anda melaksanakan skrip anda dengan parameter yang diluluskan (stdin) - kreativiti dan tidak lebih!

Salah satu adat kami ialah skrip Python kecil untuk menghantar pemberitahuan kepada kendur.
Pada mulanya, kami ingin menghantar gambar grafana yang dilindungi kebenaran dalam mesej. Selepas itu, tulis OK dalam urutan kepada makluman sebelumnya daripada kumpulan yang sama, dan bukan sebagai mesej berasingan. Tidak lama kemudian - tambahkan pada mesej kesilapan yang paling biasa dalam X minit terakhir.

Topik yang berasingan ialah komunikasi dengan perkhidmatan lain dan sebarang tindakan yang dimulakan oleh makluman (hanya jika pemantauan anda berfungsi dengan baik).
Contoh penerangan pengendali, dengan slack_handler.py ialah skrip tulisan sendiri kami:

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

Bagaimana untuk nyahpepijat?

Pilihan dengan output log

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

Tonton (cli): kapasitor -url hos-atau-ip:9092 log lvl=error

Pilihan dengan httpOut

Menunjukkan data dalam saluran paip semasa:

|httpOut('something')

Tonton (dapatkan): hos-atau-ip:9092/kapacitor/v1/tasks/task_name/sesuatu

Skim pelaksanaan

  • Setiap tugasan mengembalikan pokok pelaksanaan dengan nombor berguna dalam format grafikviz.
  • Ambil satu blok dot.
  • Tampalkannya ke dalam pemapar, nikmatilah.

Di mana lagi anda boleh mendapatkan garu?

cap masa dalam influxdb pada writeback

Sebagai contoh, kami menyediakan makluman untuk jumlah permintaan sejam (groupBy(1j)) dan ingin merekodkan makluman yang berlaku dalam influxdb (untuk menunjukkan dengan cantik fakta masalah pada graf dalam grafana).

influxDBOut() akan menulis nilai masa daripada makluman kepada cap masa; oleh itu, titik pada carta akan ditulis lebih awal/lewat daripada makluman tiba.

Apabila ketepatan diperlukan: kami menyelesaikan masalah ini dengan memanggil pengendali tersuai, yang akan menulis data kepada influxdb dengan cap masa semasa.

docker, bina dan penempatan

Pada permulaan, kapasitor boleh memuatkan tugas, templat dan pengendali daripada direktori yang dinyatakan dalam konfigurasi dalam blok [load].

Untuk membuat tugasan dengan betul, anda memerlukan perkara berikut:

  1. Nama fail – dikembangkan menjadi id/nama skrip
  2. Jenis – strim/kelompok
  3. dbrp – kata kunci untuk menunjukkan pangkalan data + dasar mana skrip dijalankan (dbrp β€œpembekal.” β€œautogen”)

Jika beberapa tugas kelompok tidak mengandungi baris dengan dbrp, keseluruhan perkhidmatan akan menolak untuk dimulakan dan secara jujur ​​akan menulis mengenainya dalam log.

Dalam chronograf, sebaliknya, baris ini tidak sepatutnya wujud; ia tidak diterima melalui antara muka dan menghasilkan ralat.

Hack semasa membina bekas: Dockerfile keluar dengan -1 jika terdapat baris dengan //.+dbrp, yang akan membolehkan anda memahami dengan segera sebab kegagalan semasa memasang binaan.

sertai satu kepada banyak

Contoh tugas: anda perlu mengambil persentil ke-95 masa operasi perkhidmatan selama seminggu, bandingkan setiap minit daripada 10 minit terakhir dengan nilai ini.

Anda tidak boleh melakukan gabungan satu-ke-banyak, terakhir/min/median ke atas sekumpulan mata menukar nod menjadi strim, ralat "tidak boleh menambah tepi anak yang tidak sepadan: kelompok -> strim" akan dikembalikan.

Hasil kumpulan, sebagai pembolehubah dalam ungkapan lambda, juga tidak diganti.

Terdapat pilihan untuk menyimpan nombor yang diperlukan dari kumpulan pertama ke fail melalui udf dan memuatkan fail ini melalui sideload.

Apa yang kami selesaikan dengan ini?

Kami mempunyai kira-kira 100 pembekal hotel, setiap daripada mereka boleh mempunyai beberapa sambungan, mari kita panggil ia saluran. Terdapat kira-kira 300 saluran ini, setiap saluran boleh jatuh. Daripada semua metrik yang direkodkan, kami akan memantau kadar ralat (permintaan dan ralat).

Mengapa tidak grafana?

Makluman ralat yang dikonfigurasikan dalam Grafana mempunyai beberapa kelemahan. Ada yang kritikal, ada yang anda boleh tutup mata, bergantung pada keadaan.

Grafana tidak tahu cara mengira antara ukuran + amaran, tetapi kami memerlukan kadar (permintaan-ralat)/permintaan.

Ralat kelihatan buruk:

Trik untuk memproses metrik dalam Kapacitor

Dan kurang kejahatan apabila dilihat dengan permintaan yang berjaya:

Trik untuk memproses metrik dalam Kapacitor

Okey, kita boleh mengira kadar dalam perkhidmatan sebelum grafana, dan dalam beberapa kes ini akan berfungsi. Tetapi tidak pada kita, kerana... untuk setiap saluran nisbahnya sendiri dianggap "normal", dan amaran berfungsi mengikut nilai statik (kita melihat dengan mata kita, tukar jika terdapat amaran yang kerap).

Ini adalah contoh "biasa" untuk saluran yang berbeza:

Trik untuk memproses metrik dalam Kapacitor

Trik untuk memproses metrik dalam Kapacitor

Kami mengabaikan perkara sebelumnya dan menganggap bahawa gambar "biasa" adalah serupa untuk semua pembekal. Sekarang semuanya baik-baik saja dan kita boleh bertahan dengan makluman dalam grafana?
Kita boleh, tetapi kita benar-benar tidak mahu, kerana kita perlu memilih salah satu daripada pilihan:
a) membuat banyak graf untuk setiap saluran secara berasingan (dan mengiringinya dengan susah payah)
b) tinggalkan satu carta dengan semua saluran (dan tersesat dalam baris berwarna-warni dan makluman tersuai)

Trik untuk memproses metrik dalam Kapacitor

Bagaimana awak melakukannya?

Sekali lagi, terdapat contoh permulaan yang baik dalam dokumentasi (Mengira kadar merentas siri gabungan), boleh dilihat atau diambil sebagai asas dalam masalah yang sama.

Apa yang kami lakukan pada akhirnya:

  • sertai dua siri dalam beberapa jam, kumpulkan mengikut saluran;
  • isikan siri mengikut kumpulan jika tiada data;
  • bandingkan median 10 minit terakhir dengan data sebelumnya;
  • kita menjerit jika kita menjumpai sesuatu;
  • kami menulis kadar yang dikira dan makluman yang berlaku dalam influxdb;
  • menghantar mesej yang berguna kepada kendur.

Pada pendapat saya, kami berjaya mencapai semua yang kami ingin dapatkan pada akhirnya (dan lebih sedikit lagi dengan pengendali tersuai) secantik mungkin.

Anda boleh melihat di github.com contoh kod ΠΈ litar minimum (graphviz) skrip yang terhasil.

Contoh kod yang terhasil:

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)

Apakah kesimpulannya?

Kapacitor hebat dalam melaksanakan amaran pemantauan dengan sekumpulan pengelompokan, melakukan pengiraan tambahan berdasarkan metrik yang telah direkodkan, melakukan tindakan tersuai dan menjalankan skrip (udf).

Halangan untuk masuk tidak terlalu tinggi - cuba jika grafana atau alat lain tidak memenuhi keinginan anda sepenuhnya.

Sumber: www.habr.com

Tambah komen