حيل للتعامل مع المقاييس في Kapacitor

على الأرجح ، اليوم لا أحد لديه سؤال عن سبب ضرورة جمع مقاييس الخدمة. تتمثل الخطوة المنطقية التالية في إعداد تنبيه للمقاييس التي تم جمعها ، والذي سيُعلمك بأي انحرافات في البيانات إلى القنوات المناسبة لك (البريد ، Slack ، Telegram). خدمة حجز الفنادق عبر الإنترنت Ostrovok.ru يتم صب جميع مقاييس خدماتنا في InfluxDB وعرضها في Grafana ، كما يتم تكوين التنبيه الأساسي هناك. لمهام مثل "تحتاج إلى حساب شيء ما ومقارنته به" ، نستخدم Kapacitor.

حيل للتعامل مع المقاييس في Kapacitor
Kapacitor هو جزء من TICK stack الذي يمكنه التعامل مع المقاييس من InfluxDB. يمكنه توصيل عدة قياسات ببعضها البعض (الانضمام) ، وحساب شيء مفيد من البيانات المستلمة ، وكتابة النتيجة مرة أخرى إلى InfluxDB ، وإرسال تنبيه إلى Slack / Telegram / mail.

المكدس كله لديه بارد ومفصل الوثائق، ولكن ستكون هناك دائمًا أشياء مفيدة لم يتم الإشارة إليها صراحة في الأدلة. في هذه المقالة ، قررت أن أجمع عددًا من هذه النصائح المفيدة غير الواضحة (تم وصف الصيغة الرئيسية لـ TICKscipt هنا) وشرح كيف يمكن تطبيقها باستخدام مثال حل إحدى مشكلاتنا.

دعونا نذهب!

تعويم و 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)

ملء صلة (داخلي مقابل خارجي)

بشكل افتراضي ، ستتجاهل الصلة النقاط التي لا توجد بها بيانات (داخلية).
باستخدام التعبئة ('فارغة') ، سيتم تنفيذ صلة خارجية ، وبعد ذلك تحتاج إلى عمل افتراضي () وتعبئة القيم الفارغة:

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

لا يزال هناك فارق بسيط هنا. إذا كانت إحدى السلاسل (res1 أو res2) في المثال أعلاه فارغة ، فستكون السلسلة النهائية (البيانات) فارغة أيضًا. يوجد العديد من تذاكر جيثب حول هذا الموضوع (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" }}')

معالج مخصص

يوجد تنفيذ في قائمة كبيرة من المعالجات ، والذي يسمح لك بتنفيذ البرنامج النصي الخاص بك باستخدام المعلمات التي تم تمريرها (stdin) - الإبداع والمزيد!

أحد عاداتنا هو برنامج نصي صغير بيثون لإرسال إشعارات إلى Slack.
في البداية ، أردنا إرسال صورة من grafana المحمي بموجب ترخيص في رسالة. بعد - اكتب موافق في الموضوع إلى التنبيه السابق من نفس المجموعة ، وليس كرسالة منفصلة. بعد ذلك بقليل ، أضف الخطأ الأكثر شيوعًا في آخر 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")

مشاهدة (CLI): kapacitor -url المضيف أو IP: 9092 سجلات lvl = خطأ

متغير مع httpOut

يظهر البيانات في خط الأنابيب الحالي:

|httpOut('something')

مشاهدة (الحصول): المضيف أو IP: 9092 / kapacitor / v1 / مهام / اسم_المهام / شيء

مخطط التنفيذ

  • تقوم كل مهمة بإرجاع شجرة تنفيذ بأرقام مفيدة في التنسيق graphviz.
  • نأخذ كتلة نقطة.
  • لصق في العارض يتمتع.

في أي مكان آخر يمكنك الحصول على أشعل النار

الطابع الزمني في التدفق على إعادة الكتابة

على سبيل المثال ، قمنا بإعداد تنبيه لمقدار الطلبات في الساعة (groupBy (1h)) ونريد تسجيل التنبيه الذي حدث في influxdb (لإظهار حقيقة وجود مشكلة في الرسم البياني في grafana بشكل جميل).

سيكتب influxDBOut () القيمة الزمنية من التنبيه إلى الطابع الزمني ، على التوالي ، ستتم كتابة النقطة على الرسم البياني في وقت أبكر / متأخرًا عن وقت التنبيه.

عندما تكون الدقة مطلوبة: نتغلب على هذه المشكلة عن طريق استدعاء معالج مخصص ، والذي سيكتب البيانات إلى influxdb بالطابع الزمني الحالي.

عامل البناء والبناء والنشر

عند بدء التشغيل ، يمكن لـ kapacitor تحميل المهام والقوالب والمعالجات من الدليل المحدد في التكوين ، في كتلة [load].

لإنشاء مهمة بشكل صحيح ، تحتاج إلى الأشياء التالية:

  1. اسم الملف - يتم توسيعه إلى معرف / اسم البرنامج النصي
  2. النوع - تيار / دفعة
  3. dbrp - كلمة أساسية لتحديد قاعدة البيانات + السياسة التي يعمل بها البرنامج النصي (dbrp "المورد". "autogen")

إذا لم يكن هناك سطر مع dbrp في بعض المهام المجمعة ، فسوف ترفض الخدمة بأكملها البدء وتكتب عنها بصدق في السجل.

في chronograf ، على العكس من ذلك ، لا ينبغي أن يكون هذا الخط موجودًا ، ولا يتم قبوله من خلال الواجهة ويعطي خطأ.

الاختراق عند إنشاء حاوية: يخرج Dockerfile بـ -1 إذا كانت هناك خطوط بـ //.+dbrp ، مما سيسمح لك بفهم سبب الفشل على الفور عند إنشاء البنية.

انضم واحد إلى العديد

مثال على مهمة: يجب أن تأخذ النسبة المئوية 95 من وقت تشغيل الخدمة لمدة أسبوع ، قارن كل دقيقة من آخر 10 مع هذه القيمة.

لا يمكنك القيام بضم واحد إلى متعدد ، فإن الأخير / المتوسط ​​/ الوسيط عبر مجموعة من النقاط يحول العقدة إلى دفق ، سيتم إرجاع الخطأ "لا يمكن إضافة حواف فرعية غير متطابقة: الدفعة -> تيار".

لا يتم أيضًا استبدال نتيجة الدُفعة ، كمتغير في تعبير لامدا.

هناك خيار لحفظ الأرقام اللازمة من الدفعة الأولى إلى ملف عبر udf وتحميل هذا الملف عبر التحميل الجانبي.

ماذا قررنا؟

لدينا حوالي 100 من مزودي الفنادق ، كل منهم لديه عدة اتصالات ، دعنا نسميها قناة. هناك حوالي 300 من هذه القنوات ، كل قناة يمكن أن تسقط. من بين جميع المقاييس المسجلة ، سنراقب معدل الخطأ (الطلبات والأخطاء).

لماذا ليس graphana؟

تنبيهات الخطأ التي تم تكوينها في برنامج grafana لها عيوب عديدة. بعضها حرج ، والبعض الآخر يمكنك أن تغمض عينيك ، اعتمادًا على الموقف.

لا تستطيع Grafana الحساب بين الأبعاد + التنبيه ، لكننا نحتاج إلى معدل (طلبات - أخطاء) / طلبات.

تبدو الأخطاء شريرة:

حيل للتعامل مع المقاييس في Kapacitor

وأقل شراسة عند النظر إليها مع الطلبات الناجحة:

حيل للتعامل مع المقاييس في Kapacitor

حسنًا ، يمكننا حساب السعر مسبقًا في الخدمة قبل grafana ، وفي بعض الحالات سيعمل هذا. ولكن ليس في بلدنا لأن. بالنسبة لكل قناة ، تعتبر نسبتها "طبيعية" ، وتعمل التنبيهات وفقًا لقيم ثابتة (نبحث عن العيون ، ونتغير إذا كانت تنبيهات في كثير من الأحيان).

هذه أمثلة على "عادي" لقنوات مختلفة:

حيل للتعامل مع المقاييس في Kapacitor

حيل للتعامل مع المقاييس في Kapacitor

دعنا نتجاهل النقطة السابقة ونفترض أن جميع الموردين لديهم صورة "عادية" مماثلة. الآن كل شيء على ما يرام ، ويمكننا الحصول على تنبيهات grafana؟
يمكننا ذلك ، لكننا حقًا لا نريد ذلك ، لأنه يتعين علينا اختيار أحد الخيارات:
أ) عمل الكثير من الرسوم البيانية لكل قناة على حدة (ومرافقتها بشكل مؤلم)
ب) اترك مخططًا واحدًا مع جميع القنوات (وتضيع في الخطوط الملونة والتنبيهات المخصصة)

حيل للتعامل مع المقاييس في Kapacitor

كيف فعلتها؟

مرة أخرى ، هناك مثال بداية جيد في الوثائق (حساب المعدلات عبر السلاسل المنضمة) ، يمكنك إلقاء نظرة خاطفة أو اتخاذ أساس في مهام مماثلة.

ماذا فعلوا في النهاية:

  • انضم إلى سلسلتين في غضون ساعات قليلة ، مجمعة حسب القنوات ؛
  • املأ المتسلسلة حسب المجموعات إذا لم تكن هناك بيانات ؛
  • قارن متوسط ​​الدقائق العشر الأخيرة مع البيانات السابقة ؛
  • يصرخون إذا تم العثور على شيء ؛
  • كتابة المعدلات المحسوبة والتنبيهات التي حدثت أثناء التدفق ؛
  • إرسال رسالة مفيدة إلى 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).

عتبة الدخول ليست عالية جدًا - جربها إذا كانت grafana أو غيرها من الأدوات لا تفي تمامًا بقائمة الرغبات الخاصة بك.

المصدر: www.habr.com

شراء استضافة موثوقة للمواقع مع حماية DDoS وخوادم VPS VDS 🔥 اشترِ استضافة مواقع ويب موثوقة مع حماية من هجمات DDoS، وخوادم VPS وVDS | ProHoster