Trik Pemrosesan Metrik di Kapacitor

Kemungkinan besar, hari ini tidak ada yang memiliki pertanyaan mengapa mereka perlu mengumpulkan metrik layanan. Langkah logis berikutnya adalah mengonfigurasikan peringatan untuk metrik yang dikumpulkan, yang akan memberi tahu Anda tentang penyimpangan dalam data di saluran yang nyaman bagi Anda (mail, Slack, Telegram). Dalam layanan reservasi online hotel Ostrovok.ru , semua metrik layanan kami dituangkan ke InfluxDB dan ditampilkan di Grafana, peringatan dasar juga ditetapkan di sana. Untuk tugas-tugas seperti "Anda perlu menghitung sesuatu dan membandingkannya," kami menggunakan Kapacitor.


Kapacitor adalah bagian dari TICK stack yang dapat menangani metrik dari InfluxDB. Dia dapat menghubungkan beberapa dimensi satu sama lain (bergabung), menghitung sesuatu yang berguna dari data yang diterima, menulis hasilnya kembali ke InfluxDB, mengirim peringatan ke Slack / Telegram / mail.

Seluruh tumpukan memiliki dokumentasi yang keren dan terperinci, tetapi selalu ada hal-hal berguna yang tidak ditentukan secara eksplisit dalam manual. Dalam artikel ini, saya memutuskan untuk mengumpulkan sejumlah tips yang tidak jelas yang berguna (sintaks TICKscipt dasar dijelaskan di sini ) dan menunjukkan bagaimana mereka dapat diterapkan, menggunakan contoh pemecahan salah satu masalah kita.

Ayo pergi!

float & int, kesalahan perhitungan


Benar-benar masalah standar, 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 default ()


Jika tag / bidang tidak diisi, kesalahan akan terjadi dalam perhitungan:

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

isi gabung (dalam vs luar)


Secara default, bergabung akan menjatuhkan titik di mana tidak ada data (batin).
Dengan fill ('null'), join luar akan dieksekusi, setelah itu Anda perlu melakukan default () dan mengisi nilai-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. Jika dalam contoh di atas salah satu seri (res1 atau res2) kosong, seri terakhir (data) juga akan kosong. Ada beberapa tiket pada topik ini di github ( 1633 , 1871 , 6967 ) - kami sedang menunggu perbaikan dan sedikit menderita.

Menggunakan kondisi dalam perhitungan (jika dalam lambda)


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

Lima menit terakhir dari pipa selama periode tersebut


Misalnya, Anda perlu membandingkan nilai lima menit terakhir dengan minggu sebelumnya. Anda dapat mengambil dua paket data dengan dua batch'ami yang terpisah atau menarik sebagian data dari periode yang lebih besar:

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

Alternatif untuk lima menit terakhir bisa menggunakan simpul BarrierNode, yang memotong data sebelum waktu yang ditentukan:

 |barrier() .period(5m) 

Contoh menggunakan pola Go'sh dalam pesan


Template sesuai dengan format dari paket text.template , di bawah ini adalah beberapa tugas umum.

jika ada


Kami menertibkan, kami tidak memicu orang dengan teks sekali lagi:

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

Dua tempat desimal dalam pesan


Meningkatkan keterbacaan pesan:

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

Memperluas variabel dalam pesan


Kami menampilkan lebih banyak informasi dalam pesan untuk menjawab pertanyaan "Mengapa berteriak?"

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

Pengidentifikasi Tanda Unik


Hal yang benar ketika ada lebih dari satu grup dalam data, jika tidak hanya satu peringatan yang akan dihasilkan:

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

Penangan kustom


Daftar besar penangan memiliki exec, yang memungkinkan Anda untuk mengeksekusi skrip Anda dengan parameter yang dilewati (stdin) - kreativitas dan banyak lagi!

Salah satu alat khusus kami adalah skrip python kecil untuk mengirim notifikasi menjadi kendur.
Pertama, kami ingin mengirim gambar dari graphane yang dilindungi oleh otorisasi dalam pesan. Setelah - tulis OK di utas ke peringatan sebelumnya dari grup yang sama, dan bukan sebagai pesan terpisah. Beberapa saat kemudian - untuk menambahkan kesalahan paling umum pada menit-menit terakhir pesan.

Topik terpisah adalah komunikasi dengan layanan lain dan tindakan apa pun yang diprakarsai oleh peringatan (hanya jika pemantauan Anda cukup baik).
Contoh deskripsi handler, di mana slack_handler.py adalah skrip yang ditulis sendiri:

 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 cara debut?


Opsi keluaran log


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

Tonton (cli): kapacitor -url host-or-ip : 9092 logs lvl = error

Varian dengan httpOut


Menampilkan data dalam pipa saat ini:

 |httpOut('something') 

Tonton (dapatkan): host-atau-ip : 9092 / kapacitor / v1 / task / task_name / something

Skema eksekusi


  • Setiap tugas mengembalikan run tree dengan angka yang berguna dalam format graphviz .
  • Ambil blok dot .
  • Kami masukkan ke dalam viewer, selamat menikmati .


Di mana lagi saya bisa mendapatkan rake


timestamp influxdb pada writeback


Misalnya, kami menyiapkan lansiran untuk jumlah permintaan per jam (grupDengan (1j)) dan ingin merekam lansiran yang terjadi di influxdb (untuk menampilkan fakta masalah pada grafik di grafana) dengan indah.

influxDBOut () akan menulis nilai waktu dari peringatan ke timestamp, masing-masing, titik pada grafik akan dicatat lebih awal / lebih lambat dari peringatan tiba.

Ketika akurasi diperlukan: kami melewati masalah ini dengan memanggil penangan khusus, yang akan menulis data ke influxdb dengan cap waktu saat ini.

buruh pelabuhan, bangun dan gunakan


Saat startup, kapacitor dapat memuat tugas, templat, dan handler dari direktori yang ditentukan dalam konfigurasi di blok [load].

Untuk membuat tugas dengan benar, hal-hal berikut diperlukan:

  1. Nama file - meluas ke id / nama skrip
  2. Jenis - aliran / kumpulan
  3. dbrp - kata kunci untuk menunjukkan di mana basis data + kebijakan skrip bekerja (dbrp "pemasok". "autogen")

Jika tidak ada baris dengan dbrp dalam beberapa tugas batch, seluruh layanan akan menolak untuk memulai dan dengan jujur ​​menuliskannya di log.

Dalam chronograf, sebaliknya, baris ini tidak boleh, tidak diterima melalui antarmuka dan membuat kesalahan.

Meretas ketika membangun wadah: Dockerfile keluar dengan -1 jika ada baris dengan //.+dbrp, yang akan segera memahami alasan file ketika membangun build.

gabung satu ke banyak


Contoh tugas: Anda perlu mengambil persentil ke-95 dari waktu pengoperasian layanan per minggu, bandingkan setiap menit dari 10 menit terakhir dengan nilai ini.

Anda tidak dapat bergabung satu ke banyak, terakhir / rata-rata / median oleh sekelompok titik mengubah node menjadi stream, kesalahan "tidak dapat menambahkan tepi anak tidak cocok: batch -> stream" akan kembali.

Hasil batch, sebagai variabel dalam ekspresi lambda, juga tidak diganti.

Ada opsi untuk menyimpan nomor yang diperlukan dari batch pertama ke file melalui udf dan memuat file ini melalui sideload.

Apa yang kita putuskan?


Kami memiliki sekitar 100 pemasok hotel, masing-masing dari mereka dapat memiliki beberapa koneksi, sebut saja saluran. Ada sekitar 300 saluran ini, masing-masing saluran mungkin jatuh. Dari semua metrik yang direkam, kami akan memantau tingkat kesalahan (permintaan dan kesalahan).

Kenapa tidak grafana?


Peringatan kesalahan yang dikonfigurasi dalam grafan memiliki beberapa kelemahan. Ada yang kritis, ada yang bisa menutup mata, tergantung situasinya.

Grafana tidak tahu bagaimana menghitung antara dimensi + peringatan, tetapi kami membutuhkan tingkat (permintaan-kesalahan) / permintaan.

Kesalahan terlihat ganas:



Dan kurang ganas jika dilihat dengan permintaan yang sukses:



Oke, kita bisa menghitung di dalam layanan sebelum grafana, dan dalam beberapa kasus akan dilakukan. Tapi bukan milik kita, karena untuk setiap saluran, rasionya dianggap “normal”, dan peringatan bekerja sesuai dengan nilai statis (kita melihat dengan mata kita, berubah, jika sering waspada).

Ini adalah contoh "normal" untuk saluran yang berbeda:





Kami mengabaikan paragraf sebelumnya dan menganggap bahwa semua pemasok memiliki gambaran "normal". Sekarang semuanya baik-baik saja, dan bisakah kita bertahan dengan peringatan di grafana?
Kita bisa, tetapi benar-benar tidak mau, karena kita harus memilih salah satu opsi:
a) membuat banyak grafik untuk setiap saluran secara terpisah (dan dengan susah payah menyertai mereka)
b) meninggalkan satu bagan dengan semua saluran (dan tersesat dalam garis berwarna dan peringatan yang disetel)



Bagaimana kamu melakukannya?


Sekali lagi, dokumentasi memiliki contoh awal yang baik ( Menghitung tingkat di seluruh seri bergabung ), Anda dapat mengintip atau mengambil sebagai dasar dalam tugas serupa.

Apa yang Anda lakukan sebagai hasilnya:

  • bergabung dengan dua episode dalam beberapa jam, pengelompokan berdasarkan saluran;
  • isi seri dengan kelompok, jika tidak ada data;
  • bandingkan median 10 menit terakhir dengan data sebelumnya;
  • kita berteriak jika kita menemukan sesuatu;
  • tulis harga yang dihitung dan peringatan yang muncul di influxdb;
  • mengirim pesan yang bermanfaat ke slack.

Menurut pendapat saya, kami mengelola segala hal seindah mungkin yang ingin kami dapatkan pada keluaran (dan bahkan sedikit lagi dengan penangan khusus).

Di github.com Anda dapat melihat kode sampel dan diagram minimal (graphviz) dari skrip yang dihasilkan.

Contoh kode yang dihasilkan:
 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) 


Apa kesimpulannya?


Kapacitor sangat baik dalam memonitor peringatan dengan sekelompok grup, melakukan perhitungan tambahan pada metrik yang sudah direkam, melakukan tindakan kustom dan menjalankan skrip (udf).

Ambang entri tidak terlalu tinggi - coba jika grafana atau alat lain tidak sepenuhnya memenuhi Daftar Keinginan Anda.

Source: https://habr.com/ru/post/id478702/


All Articles