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.
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:
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:
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:
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:
Nama fail β dikembangkan menjadi id/nama skrip
Jenis β strim/kelompok
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:
Dan kurang kejahatan apabila dilihat dengan permintaan yang berjaya:
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:
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)
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.
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.