Kapacitor'da metrikleri işlemeye yönelik püf noktaları

Büyük olasılıkla bugün hiç kimse hizmet ölçümlerini toplamanın neden gerekli olduğunu sormuyor. Bir sonraki mantıksal adım, toplanan ölçümler için, sizin için uygun kanallardaki (posta, Slack, Telegram) verilerdeki herhangi bir sapmayı bildirecek bir uyarı ayarlamaktır. Çevrimiçi otel rezervasyon hizmetinde Ostrovok.ru hizmetlerimizin tüm ölçümleri InfluxDB'ye aktarılır ve Grafana'da görüntülenir ve temel uyarılar da burada yapılandırılır. “Bir şeyi hesaplayıp onunla karşılaştırmanız gerekiyor” gibi işler için Kapacitor kullanıyoruz.

Kapacitor'da metrikleri işlemeye yönelik püf noktaları
Kapacitor, InfluxDB'den gelen ölçümleri işleyebilen TICK yığınının bir parçasıdır. Birkaç ölçümü birbirine bağlayabilir (birleştirebilir), alınan verilerden yararlı bir şeyler hesaplayabilir, sonucu InfluxDB'ye yazabilir, Slack/Telegram/mail'e bir uyarı gönderebilir.

Tüm yığın harika ve ayrıntılı belgeleme, ancak kılavuzlarda açıkça belirtilmeyen faydalı şeyler her zaman olacaktır. Bu makalede, bu tür yararlı, açık olmayan ipuçlarını toplamaya karar verdim (TICKscipt'in temel sözdizimi açıklanmıştır) burada) ve sorunlarımızdan birinin çözümüne ilişkin bir örnek kullanarak bunların nasıl uygulanabileceğini gösterin.

Hadi gidelim!

float & int, hesaplama hataları

Kastlar yoluyla çözülen kesinlikle standart bir sorun:

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

Varsayılan()'ı kullanma

Bir etiket/alan doldurulmazsa hesaplama hataları oluşur:

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

birleştirmeyi doldurun (iç vs dış)

Varsayılan olarak join, veri olmayan (iç) noktaları atar.
fill('null') ile bir dış birleştirme gerçekleştirilecektir, ardından bir default() yapmanız ve boş değerleri doldurmanız gerekir:

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

Burada hâlâ bir nüans var. Yukarıdaki örnekte serilerden biri (res1 veya res2) boşsa, ortaya çıkan seri (veri) de boş olacaktır. Github'da bu konuyla ilgili birkaç bilet var (1633, 1871, 6967) – düzeltmeler bekliyoruz ve biraz sıkıntı çekiyoruz.

Hesaplamalarda koşulların kullanılması (lambda ise)

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

Dönem için boru hattından son beş dakika

Örneğin son beş dakikanın değerlerini bir önceki haftayla karşılaştırmanız gerekiyor. İki ayrı grupta iki veri kümesini alabilir veya daha büyük bir döneme ait verilerin bir kısmını çıkarabilirsiniz:

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

Son beş dakika için bir alternatif, verileri belirtilen süreden önce kesen bir BarrierNode kullanmak olabilir:

|barrier()
        .period(5m)

Mesajda Go şablonlarını kullanma örnekleri

Şablonlar paketteki formata karşılık gelir metin.şablonAşağıda sık karşılaşılan bazı bulmacalar bulunmaktadır.

eğer-değilse

İşleri düzene koyuyoruz ve insanları bir kez daha mesajla tetiklemiyoruz:

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

Mesajda virgülden sonraki iki rakam

Mesajın okunabilirliğini artırmak:

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

Mesajdaki değişkenleri genişletme

"Neden bağırıyor?" sorusunu yanıtlamak için mesajda daha fazla bilgi görüntülüyoruz.

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

Benzersiz uyarı tanımlayıcısı

Verilerde birden fazla grup olduğunda bu gerekli bir şeydir, aksi takdirde yalnızca bir uyarı oluşturulacaktır:

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

Özel işleyiciler

İşleyicilerin geniş listesi, betiğinizi iletilen parametrelerle (stdin) yürütmenize olanak tanıyan exec'i içerir - yaratıcılıktan başka bir şey değil!

Geleneklerimizden biri, Slack'e bildirim göndermek için kullanılan küçük bir Python betiğidir.
İlk başta yetkilendirme korumalı grafana resmini mesajla göndermek istedik. Daha sonra aynı gruptan bir önceki uyarıya ayrı bir mesaj olarak değil, başlıkta Tamam yazın. Biraz sonra - son X dakikadaki en yaygın hatayı mesaja ekleyin.

Ayrı bir konu, diğer hizmetlerle iletişim ve bir uyarıyla başlatılan tüm eylemlerdir (yalnızca izlemeniz yeterince iyi çalışıyorsa).
Bir işleyici açıklaması örneği; burada slack_handler.py bizim kendi yazdığımız betiğimizdir:

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

Nasıl hata ayıklanır?

Günlük çıktılı seçenek

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

İzle (cli): kapasitör -url ana bilgisayar veya ip:9092 günlük düzeyi=hata

httpOut'lu seçenek

Geçerli ardışık düzendeki verileri gösterir:

|httpOut('something')

İzle (al): ana bilgisayar veya ip:9092/kapasitör/v1/görevler/görev_adı/bir şey

Yürütme şeması

  • Her görev, şu formatta yararlı sayılar içeren bir yürütme ağacı döndürür: Graphviz.
  • Bir blok al nokta.
  • Görüntüleyiciye yapıştırın Eğlence.

Başka nereden tırmık alabilirsin?

geri yazma sırasında influxdb'deki zaman damgası

Örneğin, saat başına isteklerin toplamı için bir uyarı ayarladık (groupBy(1h)) ve oluşan uyarıyı influxdb'de kaydetmek istiyoruz (sorunun gerçeğini grafana'daki grafikte güzel bir şekilde göstermek için).

influxDBOut(), uyarıdan gelen zaman değerini zaman damgasına yazacaktır; buna göre grafikteki nokta, uyarının gelmesinden önce/sonra yazılacaktır.

Doğruluk gerektiğinde: verileri influxdb'ye geçerli zaman damgasıyla yazacak özel bir işleyiciyi çağırarak bu sorunu çözeriz.

liman işçisi, derleme ve dağıtım

Başlangıçta kapasitör, [load] bloğundaki yapılandırmada belirtilen dizinden görevleri, şablonları ve işleyicileri yükleyebilir.

Doğru bir görev oluşturmak için aşağıdakilere ihtiyacınız vardır:

  1. Dosya adı – komut dosyası kimliği/adına genişletildi
  2. Tür – akış/toplu
  3. dbrp – betiğin hangi veritabanı + politikada çalıştığını belirten anahtar kelime (dbrp “tedarikçi.”“autogen”)

Bazı toplu görevlerde dbrp içeren bir satır yoksa, hizmetin tamamı başlamayı reddedecek ve bu konuda dürüstçe günlüğe yazacaktır.

Kronografta ise tam tersine bu çizginin olmaması gerekiyor, arayüz üzerinden kabul edilmiyor ve hata veriyor.

Bir konteyner oluştururken hack: //.+dbrp içeren satırlar varsa Dockerfile -1 ile çıkar; bu, yapıyı derlerken hatanın nedenini hemen anlamanıza olanak tanır.

birden çoğuna katıl

Örnek görev: Bir hafta boyunca servisin çalışma süresinin %95'lik dilimini alıp, son 10 dakikanın her dakikasını bu değerle karşılaştırmanız gerekiyor.

Bire çok birleştirme yapamazsınız, bir grup nokta üzerindeki son/ortalama/medyan düğümü bir akışa dönüştürür, "eşleşmeyen alt kenarlar eklenemiyor: toplu -> akış" hatası döndürülür.

Bir lambda ifadesindeki değişken olarak bir toplu işlemin sonucu da değiştirilmez.

İlk gruptan gerekli sayıları udf aracılığıyla bir dosyaya kaydetme ve bu dosyayı sideload yoluyla yükleme seçeneği vardır.

Bununla neyi çözdük?

100'e yakın otel tedarikçimiz var, her birinin birden fazla bağlantısı olabiliyor, buna kanal diyelim. Bu kanallardan yaklaşık 300 adet bulunmaktadır, kanalların her biri düşebilmektedir. Kaydedilen tüm ölçümler arasında hata oranını (istekler ve hatalar) izleyeceğiz.

Neden grafana değil?

Grafana'da yapılandırılan hata uyarılarının çeşitli dezavantajları vardır. Bazıları kritiktir, bazılarına ise duruma göre gözlerinizi kapatabilirsiniz.

Grafana, ölçümler + uyarılar arasında nasıl hesaplama yapılacağını bilmiyor ancak bir orana (istekler-hatalar)/isteklere ihtiyacımız var.

Hatalar kötü görünüyor:

Kapacitor'da metrikleri işlemeye yönelik püf noktaları

Ve başarılı isteklerle bakıldığında daha az kötülük:

Kapacitor'da metrikleri işlemeye yönelik püf noktaları

Tamam, grafanadan önce servisteki oranı önceden hesaplayabiliriz ve bazı durumlarda bu işe yarayacaktır. Ama bizimkinde değil, çünkü... her kanalın kendi oranı “normal” kabul edilir ve uyarılar statik değerlere göre çalışır (gözümüzle ararız, sık uyarı varsa değiştiririz).

Bunlar farklı kanallar için "normal" örnekleridir:

Kapacitor'da metrikleri işlemeye yönelik püf noktaları

Kapacitor'da metrikleri işlemeye yönelik püf noktaları

Önceki noktayı göz ardı ediyoruz ve “normal” tablonun tüm tedarikçiler için benzer olduğunu varsayıyoruz. Artık her şey yolunda ve grafana'daki uyarılarla idare edebilir miyiz?
Yapabiliriz ama aslında istemiyoruz çünkü seçeneklerden birini seçmek zorundayız:
a) her kanal için ayrı ayrı çok sayıda grafik yapın (ve onlara acı verici bir şekilde eşlik edin)
b) tüm kanalları içeren tek bir grafik bırakın (ve renkli çizgiler ve özelleştirilmiş uyarılar arasında kaybolun)

Kapacitor'da metrikleri işlemeye yönelik püf noktaları

Bunu nasıl yaptın?

Yine belgelerde iyi bir başlangıç ​​örneği var (Birleştirilmiş seriler genelinde oranların hesaplanması), benzer problemlerde bakılabilir veya temel alınabilir.

Sonunda ne yaptık:

  • kanallara göre gruplandırarak birkaç saat içinde iki diziye katılın;
  • veri yoksa seriyi gruba göre doldurun;
  • son 10 dakikanın ortalamasını önceki verilerle karşılaştırın;
  • bir şey bulursak bağırırız;
  • influxdb'de hesaplanan oranları ve oluşan uyarıları yazıyoruz;
  • Slack'e faydalı bir mesaj gönderin.

Bana göre, sonunda elde etmek istediğimiz her şeyi (ve hatta özel işleyicilerle biraz daha fazlasını) olabildiğince güzel bir şekilde elde etmeyi başardık.

github.com'a bakabilirsiniz kod örneği и minimum devre (grafviz) ortaya çıkan komut dosyası.

Ortaya çıkan kodun bir örneği:

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)

Ve sonuç nedir?

Kapacitor, bir dizi gruplamayla izleme uyarıları gerçekleştirme, önceden kaydedilmiş ölçümlere dayalı ek hesaplamalar yapma, özel eylemler gerçekleştirme ve komut dosyalarını (udf) çalıştırma konusunda mükemmeldir.

Giriş engeli çok yüksek değil; grafana veya diğer araçlar arzularınızı tam olarak karşılamıyorsa deneyin.

Kaynak: habr.com

Yorum ekle