Trik memproses metrik di Kapacitor

Kemungkinan besar, saat ini tidak ada yang bertanya mengapa perlu mengumpulkan metrik layanan. Langkah logis berikutnya adalah menyiapkan peringatan untuk metrik yang dikumpulkan, yang akan memberi tahu tentang segala penyimpangan data di saluran yang nyaman bagi Anda (mail, Slack, Telegram). Dalam layanan pemesanan hotel online Ostrovok.ru semua metrik layanan kami dituangkan ke InfluxDB dan ditampilkan di Grafana, dan peringatan dasar juga dikonfigurasi di sana. Untuk tugas seperti β€œAnda perlu menghitung sesuatu dan membandingkannya”, kami menggunakan Kapacitor.

Trik memproses metrik di Kapacitor
Kapacitor adalah bagian dari tumpukan TICK yang dapat memproses metrik dari InfluxDB. Itu dapat menghubungkan beberapa pengukuran bersama-sama (bergabung), menghitung sesuatu yang berguna dari data yang diterima, menulis hasilnya kembali ke InfluxDB, mengirim peringatan ke Slack/Telegram/mail.

Seluruh tumpukannya keren dan detail dokumentasi, tetapi akan selalu ada hal-hal berguna yang tidak disebutkan secara eksplisit dalam manual. Dalam artikel ini, saya memutuskan untuk mengumpulkan sejumlah tip yang berguna dan tidak jelas (sintaks dasar TICKscipt dijelaskan di sini) dan tunjukkan bagaimana penerapannya dengan menggunakan contoh penyelesaian salah satu masalah kita.

Mari kita pergi!

float & int, kesalahan perhitungan

Masalah yang benar-benar 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 bawaan()

Jika tag/field tidak diisi maka akan terjadi kesalahan perhitungan:

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

isi gabung (dalam vs luar)

Secara default, join akan membuang titik-titik yang tidak ada datanya (batin).
Dengan fill('null'), gabungan luar akan dilakukan, setelah itu Anda perlu melakukan default() dan mengisi nilai yang 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. Pada contoh di atas, jika salah satu rangkaian (res1 atau res2) kosong, maka rangkaian (data) yang dihasilkan juga akan kosong. Ada beberapa tiket tentang topik ini di Github (1633, 1871, 6967) – kami menunggu perbaikan dan sedikit menderita.

Menggunakan kondisi dalam perhitungan (jika dalam lambda)

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

Lima menit terakhir dari jalur pipa untuk periode tersebut

Misalnya, Anda perlu membandingkan nilai lima menit terakhir dengan minggu sebelumnya. Anda dapat mengambil dua kumpulan data dalam dua kumpulan terpisah atau mengekstrak sebagian data dari periode yang lebih besar:

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

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

|barrier()
        .period(5m)

Contoh penggunaan template Go dalam pesan

Template sesuai dengan format dari paket teks.templateDi bawah ini adalah beberapa teka-teki yang sering ditemui.

jika ada

Kami membereskan segala sesuatunya dan tidak memicu orang dengan teks sekali lagi:

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

Dua digit setelah koma 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 ia berteriak”?

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

Pengidentifikasi peringatan unik

Ini adalah hal yang diperlukan 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 khusus

Daftar besar penangan mencakup exec, yang memungkinkan Anda menjalankan skrip dengan parameter yang diteruskan (stdin) - kreativitas dan tidak lebih!

Salah satu kebiasaan kami adalah skrip Python kecil untuk mengirimkan notifikasi ke slack.
Pada awalnya, kami ingin mengirim gambar grafana yang dilindungi otorisasi dalam sebuah pesan. Setelah itu, tulis OK di thread peringatan sebelumnya dari grup yang sama, dan bukan sebagai pesan terpisah. Beberapa saat kemudian - tambahkan ke pesan kesalahan paling umum dalam X menit terakhir.

Topik terpisah adalah komunikasi dengan layanan lain dan tindakan apa pun yang dimulai oleh peringatan (hanya jika pemantauan Anda berfungsi dengan cukup baik).
Contoh deskripsi handler, dimana slack_handler.py adalah skrip yang kita tulis 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 men-debug?

Opsi dengan keluaran log

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

Tonton (cli): kapasitor -url host-atau-ip:9092 log lvl=kesalahan

Opsi dengan httpOut

Menampilkan data dalam alur saat ini:

|httpOut('something')

Tonton (dapatkan): host-atau-ip:9092/kapasitor/v1/tasks/task_name/something

Skema eksekusi

  • Setiap tugas mengembalikan pohon eksekusi dengan nomor berguna dalam formatnya graphviz.dll.
  • Ambil satu blok dot.
  • Tempelkan ke penampil, menikmati.

Di mana lagi Anda bisa mendapatkan penggaruk?

stempel waktu di influxdb saat menulis balik

Misalnya, kami menyiapkan peringatan untuk jumlah permintaan per jam (groupBy(1h)) dan ingin mencatat peringatan yang terjadi di influxdb (untuk menunjukkan dengan indah fakta masalah pada grafik di grafana).

influxDBOut() akan menulis nilai waktu dari peringatan ke stempel waktu; oleh karena itu, titik pada grafik akan ditulis lebih awal/lambat dari peringatan tiba.

Ketika akurasi diperlukan: kami mengatasi masalah ini dengan memanggil pengendali khusus, yang akan menulis data ke influxdb dengan stempel waktu saat ini.

buruh pelabuhan, pembangunan dan penerapan

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

Untuk membuat tugas dengan benar, Anda memerlukan hal-hal berikut:

  1. Nama file – diperluas menjadi id/nama skrip
  2. Ketik – aliran/batch
  3. dbrp – kata kunci untuk menunjukkan database + kebijakan mana yang menjalankan skrip (dbrp β€œpemasok.” β€œautogen”)

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

Sebaliknya, dalam kronograf, baris ini seharusnya tidak ada; tidak diterima melalui antarmuka dan menghasilkan kesalahan.

Meretas saat membuat container: Dockerfile keluar dengan -1 jika ada baris dengan //.+dbrp, yang akan memungkinkan Anda untuk segera memahami alasan kegagalan saat merakit build.

bergabung satu ke banyak

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

Anda tidak dapat melakukan penggabungan satu-ke-banyak, terakhir/rata-rata/median pada sekelompok titik mengubah simpul menjadi aliran, kesalahan β€œtidak dapat menambahkan tepi anak yang tidak cocok: batch -> aliran” akan dikembalikan.

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

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

Apa yang kami pecahkan dengan ini?

Kita punya sekitar 100 supplier hotel yang masing-masing bisa punya beberapa koneksi, sebut saja channel. Channel ini berjumlah kurang lebih 300, setiap channelnya bisa putus. Dari semua metrik yang tercatat, kami akan memantau tingkat kesalahan (permintaan dan kesalahan).

Mengapa tidak Grafana?

Peringatan kesalahan yang dikonfigurasi di Grafana memiliki beberapa kelemahan. Ada yang kritis, ada yang bisa Anda tutup mata, tergantung situasinya.

Grafana tidak tahu cara menghitung antara pengukuran + peringatan, tetapi kita membutuhkan rate (permintaan-kesalahan)/permintaan.

Kesalahannya terlihat buruk:

Trik memproses metrik di Kapacitor

Dan tidak terlalu jahat jika dilihat dengan permintaan yang berhasil:

Trik memproses metrik di Kapacitor

Oke, kita bisa menghitung terlebih dahulu tarif di layanan sebelum grafana, dan dalam beberapa kasus ini akan berhasil. Tapi tidak di negara kita, karena... untuk setiap saluran, rasionya sendiri dianggap "normal", dan peringatan bekerja sesuai dengan nilai statis (kami mencarinya dengan mata kami, mengubahnya jika sering ada peringatan).

Ini adalah contoh β€œnormal” untuk saluran yang berbeda:

Trik memproses metrik di Kapacitor

Trik memproses metrik di Kapacitor

Kami mengabaikan poin sebelumnya dan berasumsi bahwa gambaran β€œnormal” serupa untuk semua pemasok. Sekarang semuanya baik-baik saja, dan kita bisa bertahan dengan peringatan di grafana?
Bisa, tapi sebenarnya tidak mau, karena harus memilih salah satu pilihan:
a) membuat banyak grafik untuk setiap saluran secara terpisah (dan menemaninya dengan susah payah)
b) tinggalkan satu grafik dengan semua saluran (dan tersesat dalam garis warna-warni dan peringatan yang disesuaikan)

Trik memproses metrik di Kapacitor

Bagaimana kamu melakukannya?

Sekali lagi, ada contoh awal yang bagus di dokumentasi (Menghitung tarif di seluruh seri yang digabungkan), dapat diintip atau dijadikan dasar dalam permasalahan serupa.

Apa yang kami lakukan pada akhirnya:

  • bergabung dengan dua serial dalam beberapa jam, mengelompokkan berdasarkan saluran;
  • isi seri per kelompok bila belum ada datanya;
  • bandingkan median 10 menit terakhir dengan data sebelumnya;
  • kami berteriak jika kami menemukan sesuatu;
  • kami menulis tarif yang dihitung dan peringatan yang terjadi di influxdb;
  • kirim pesan berguna ke slack.

Menurut pendapat saya, kami berhasil mencapai semua yang kami ingin dapatkan pada akhirnya (dan bahkan lebih banyak lagi dengan penangan khusus) seindah mungkin.

Anda dapat melihat di github.com contoh kode ΠΈ sirkuit minimal (graphviz) 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)

Dan apa kesimpulannya?

Kapacitor hebat dalam melakukan peringatan pemantauan dengan banyak pengelompokan, melakukan penghitungan tambahan berdasarkan metrik yang sudah dicatat, melakukan tindakan khusus, dan menjalankan skrip (udf).

Hambatan untuk masuk tidak terlalu tinggi - cobalah jika grafana atau alat lain tidak sepenuhnya memuaskan keinginan Anda.

Sumber: www.habr.com

Tambah komentar