ترفندهایی برای پردازش معیارها در Kapacitor

به احتمال زیاد، امروز هیچ کس نمی پرسد که چرا جمع آوری معیارهای خدمات ضروری است. مرحله منطقی بعدی تنظیم یک هشدار برای معیارهای جمع‌آوری‌شده است که در مورد هرگونه انحراف در داده‌ها در کانال‌های مناسب شما (میل، Slack، تلگرام) اطلاع می‌دهد. در سرویس رزرو آنلاین هتل Ostrovok.ru تمام معیارهای خدمات ما در InfluxDB ریخته می شود و در Grafana نمایش داده می شود و هشدار اولیه نیز در آنجا پیکربندی می شود. برای کارهایی مانند "شما باید چیزی را محاسبه کنید و با آن مقایسه کنید" از Kapacitor استفاده می کنیم.

ترفندهایی برای پردازش معیارها در Kapacitor
Kapacitor بخشی از پشته TICK است که می تواند معیارهای InfluxDB را پردازش کند. می‌تواند چندین اندازه‌گیری را به یکدیگر متصل کند (پیوسته)، چیزی مفید را از داده‌های دریافتی محاسبه کند، نتیجه را به InfluxDB بنویسد، هشداری را به Slack/Telegram/mail ارسال کند.

کل پشته سرد و با جزئیات است مستندات، اما همیشه چیزهای مفیدی وجود خواهد داشت که به صراحت در راهنماها ذکر نشده است. در این مقاله تصمیم گرفتم تعدادی از این نکات مفید و غیر بدیهی را جمع آوری کنم (سینتکس اساسی TICKscipt شرح داده شده است. اینجا) و نشان دهید که چگونه می توان آنها را با استفاده از مثالی برای حل یکی از مشکلات ما اعمال کرد.

بریم!

float & int، خطاهای محاسباتی

یک مشکل کاملا استاندارد که از طریق کاست ها حل می شود:

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

استفاده از پیش فرض ()

اگر یک تگ/فیلد پر نشود، خطاهای محاسباتی رخ می دهد:

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

اتصال را پر کنید (داخلی در مقابل بیرونی)

به طور پیش‌فرض، join نقاطی را که هیچ داده‌ای وجود ندارد (داخلی) حذف می‌کند.
با fill('null')، یک اتصال خارجی انجام می شود، پس از آن باید یک پیش فرض () انجام دهید و مقادیر خالی را پر کنید:

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

در اینجا هنوز یک نکته ظریف وجود دارد. در مثال بالا، اگر یکی از سری ها (res1 یا res2) خالی باشد، سری (داده) حاصل نیز خالی خواهد بود. چندین بلیط در مورد این موضوع در Github وجود دارد (1633, 1871, 6967) – منتظر اصلاحات و کمی رنج هستیم.

استفاده از شرایط در محاسبات (اگر در لامبدا باشد)

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

پنج دقیقه گذشته از خط لوله برای این دوره

برای مثال، باید مقادیر پنج دقیقه آخر را با هفته قبل مقایسه کنید. می توانید دو دسته از داده ها را در دو دسته جداگانه بگیرید یا بخشی از داده ها را از یک دوره بزرگتر استخراج کنید:

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

یک جایگزین برای پنج دقیقه آخر استفاده از BarrierNode است که داده ها را قبل از زمان مشخص شده قطع می کند:

|barrier()
        .period(5m)

نمونه هایی از استفاده از الگوهای Go در پیام

قالب ها با فرمت بسته مطابقت دارند متن.قالبدر زیر برخی از پازل هایی که اغلب با آن مواجه می شوند آورده شده است.

اگر دیگری

ما همه چیز را مرتب می کنیم و افراد را یک بار دیگر با متن تحریک نمی کنیم:

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

دو رقم بعد از نقطه اعشار در پیام

بهبود خوانایی پیام:

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

گسترش متغیرها در پیام

برای پاسخ به سوال "چرا فریاد می زند" اطلاعات بیشتری را در پیام نمایش می دهیم؟

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

شناسه هشدار منحصر به فرد

زمانی که بیش از یک گروه در داده ها وجود داشته باشد این امر ضروری است، در غیر این صورت فقط یک هشدار تولید می شود:

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

کنترل کننده سفارشی

لیست بزرگی از هندلرها شامل exec است که به شما امکان می دهد اسکریپت خود را با پارامترهای عبور داده شده (stdin) اجرا کنید - خلاقیت و هیچ چیز بیشتر!

یکی از آداب و رسوم ما یک اسکریپت پایتون کوچک برای ارسال اعلان ها به Slack است.
در ابتدا، ما می‌خواستیم یک تصویر grafana محافظت شده از مجوز را در یک پیام ارسال کنیم. پس از آن، در تاپیک هشدار قبلی از همان گروه، OK را بنویسید، نه به عنوان یک پیام جداگانه. کمی بعد - رایج ترین اشتباه در آخرین X دقیقه را به پیام اضافه کنید.

یک موضوع جداگانه ارتباط با سایر سرویس‌ها و هرگونه اقدامی است که توسط یک هشدار آغاز شده است (فقط در صورتی که نظارت شما به اندازه کافی خوب کار کند).
نمونه‌ای از توضیحات کنترل‌کننده، که در آن slack_handler.py اسکریپت خودنویس ما است:

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

چگونه اشکال زدایی کنیم؟

گزینه با خروجی ورود به سیستم

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

Watch (cli): capacitor -url میزبان یا آی پی:9092 logs lvl=error

گزینه با httpOut

داده ها را در خط لوله فعلی نشان می دهد:

|httpOut('something')

تماشا (دریافت): میزبان یا آی پی:9092/kapacitor/v1/tasks/task_name/something

طرح اجرا

  • هر وظیفه یک درخت اجرا با اعداد مفید در قالب را برمی گرداند گرافیز.
  • یک بلوک بردارید نقطه.
  • آن را در بیننده جایگذاری کنید، لذت ببرید.

کجا دیگر می توانید چنگک جمع کنید؟

مُهر زمانی در influxdb در نوشتن برگشت

به عنوان مثال، ما یک هشدار برای مجموع درخواست‌ها در ساعت تنظیم می‌کنیم (groupBy(1h)) و می‌خواهیم هشداری را که در influxdb رخ داده است ضبط کنیم (تا به زیبایی واقعیت مشکل را در نمودار در grafana نشان دهیم).

influxDBOut() مقدار زمانی را از هشدار تا مهر زمانی می نویسد؛ بر این اساس، نقطه روی نمودار زودتر/دیرتر از رسیدن هشدار نوشته می شود.

وقتی دقت لازم است: ما با فراخوانی یک کنترل کننده سفارشی که داده ها را با مهر زمانی فعلی در influxdb می نویسد، این مشکل را حل می کنیم.

داکر، ساخت و استقرار

هنگام راه‌اندازی، خازن می‌تواند وظایف، قالب‌ها و کنترل‌کننده‌ها را از دایرکتوری مشخص‌شده در پیکربندی در بلوک [load] بارگیری کند.

برای ایجاد صحیح یک کار، به موارد زیر نیاز دارید:

  1. نام فایل - به شناسه/نام اسکریپت گسترش یافته است
  2. نوع - جریان / دسته
  3. dbrp - کلمه کلیدی برای نشان دادن اینکه اسکریپت در کدام پایگاه داده + خط مشی اجرا می شود (dbrp "تامین کننده." "autogen")

اگر برخی از کارهای دسته ای حاوی خطی با dbrp نباشد، کل سرویس از شروع به کار خودداری می کند و صادقانه در مورد آن در گزارش می نویسد.

در کرنوگراف، برعکس، این خط نباید وجود داشته باشد، از طریق رابط پذیرفته نمی شود و خطا ایجاد می کند.

هک هنگام ساخت کانتینر: اگر خطوطی با //.+dbrp وجود داشته باشد، Dockerfile با -1 خارج می شود، که به شما امکان می دهد بلافاصله دلیل شکست را هنگام مونتاژ بیلد درک کنید.

یکی به بسیاری را ملحق کنید

کار مثال: شما باید صدک 95 از زمان کارکرد سرویس را برای یک هفته بگیرید، هر دقیقه از 10 دقیقه آخر را با این مقدار مقایسه کنید.

نمی‌توانید پیوستن یک به چند را انجام دهید، آخرین/میانگین/میانگین بر روی یک گروه از نقاط، گره را به یک جریان تبدیل می‌کند، خطای «نمی‌توان لبه‌های ناهماهنگ فرزند اضافه کرد: دسته -> جریان» برگردانده می‌شود.

نتیجه یک دسته، به عنوان یک متغیر در یک عبارت لامبدا، نیز جایگزین نمی شود.

گزینه ای برای ذخیره اعداد لازم از دسته اول در یک فایل از طریق udf و بارگذاری این فایل از طریق sideload وجود دارد.

با این چی حل کردیم؟

ما حدود 100 تامین کننده هتل داریم، هر کدام از آنها می توانند چندین اتصال داشته باشند، بگذارید آن را یک کانال بنامیم. تقریباً 300 مورد از این کانال ها وجود دارد که هر کدام از کانال ها ممکن است سقوط کنند. از بین تمام معیارهای ثبت شده، میزان خطا (درخواست ها و خطاها) را نظارت خواهیم کرد.

چرا گرانا نه؟

هشدارهای خطای پیکربندی شده در Grafana دارای چندین معایب است. برخی بحرانی هستند، برخی می توانید بسته به موقعیت چشمان خود را روی آنها ببندید.

Grafana نمی داند که چگونه بین اندازه گیری ها + هشدار محاسبه کند، اما ما به یک نرخ (درخواست-خطا) / درخواست نیاز داریم.

خطاها بد به نظر می رسند:

ترفندهایی برای پردازش معیارها در Kapacitor

و وقتی با درخواست های موفق مشاهده می شود، شر کمتری وجود دارد:

ترفندهایی برای پردازش معیارها در Kapacitor

بسیار خوب، ما می توانیم نرخ سرویس را قبل از grafana از قبل محاسبه کنیم، و در برخی موارد این کار می کند. اما در ما نه، زیرا ... برای هر کانال نسبت خود "عادی" در نظر گرفته می شود و هشدارها بر اساس مقادیر استاتیک کار می کنند (ما با چشمان خود به دنبال آنها می گردیم ، در صورت وجود هشدارهای مکرر آنها را تغییر می دهیم).

اینها نمونه هایی از "عادی" برای کانال های مختلف هستند:

ترفندهایی برای پردازش معیارها در Kapacitor

ترفندهایی برای پردازش معیارها در Kapacitor

ما نکته قبلی را نادیده می گیریم و فرض می کنیم که تصویر "عادی" برای همه تامین کنندگان مشابه است. اکنون همه چیز خوب است، و ما می توانیم با هشدار در grafana از پس آن برآییم؟
ما می توانیم، اما واقعاً نمی خواهیم، ​​زیرا باید یکی از گزینه ها را انتخاب کنیم:
الف) نمودارهای زیادی برای هر کانال به طور جداگانه تهیه کنید (و به طرز دردناکی آنها را همراهی کنید)
ب) یک نمودار با همه کانال ها بگذارید (و در خطوط رنگارنگ و هشدارهای سفارشی گم شوید)

ترفندهایی برای پردازش معیارها در Kapacitor

چطور انجامش دادی؟

باز هم، یک مثال شروع خوب در مستندات وجود دارد (محاسبه نرخ‌ها در سری‌های پیوسته) را می توان در مسائل مشابه بررسی کرد یا به عنوان مبنایی در نظر گرفت.

کاری که در نهایت انجام دادیم:

  • پیوستن به دو سریال در چند ساعت، گروه بندی بر اساس کانال.
  • اگر داده ای وجود نداشت، مجموعه را به صورت گروهی پر کنید.
  • میانه 10 دقیقه گذشته را با داده های قبلی مقایسه کنید.
  • اگر چیزی پیدا کنیم فریاد می زنیم.
  • ما نرخ های محاسبه شده و هشدارهایی را که در influxdb رخ داده است می نویسیم.
  • یک پیام مفید به Slack ارسال کنید.

به نظر من، ما موفق شدیم به زیبایی هر چه که می‌خواستیم در پایان (و حتی کمی بیشتر با هندلرهای سفارشی) برسیم.

می توانید به سایت github.com نگاه کنید نمونه کد и مدار حداقل (گرافویز) اسکریپت حاصل

نمونه ای از کد به دست آمده:

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)

و نتیجه چیست؟

Kapacitor در اجرای هشدارهای نظارتی با دسته‌ای از گروه‌بندی‌ها، انجام محاسبات اضافی بر اساس معیارهای از قبل ثبت‌شده، انجام اقدامات سفارشی و اجرای اسکریپت‌ها (udf) عالی است.

مانع ورود خیلی زیاد نیست - اگر گرانا یا ابزارهای دیگر خواسته های شما را به طور کامل برآورده نکرد، آن را امتحان کنید.

منبع: www.habr.com

اضافه کردن نظر