الحيل معالجة متري في Kapacitor

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


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

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

دعنا نذهب!

تعويم و كثافة العمليات ، حساب الأخطاء


مشكلة قياسية تمامًا ، يتم حلها من خلال الطبقات:

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) 

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


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

 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 ) - نحن في انتظار الإصلاحات ونعاني قليلاً.

استخدام الشروط في العمليات الحسابية (في حالة lambda)


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

آخر خمس دقائق من خط الأنابيب خلال هذه الفترة


على سبيل المثال ، تحتاج إلى مقارنة قيم الدقائق الخمس الأخيرة مع الأسبوع السابق. يمكنك أخذ رزمتين من البيانات مع دفعتين منفصلتين أو سحب جزء من البيانات من فترة أكبر:

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

يمكن أن يكون بديل الدقائق الخمس الأخيرة هو استخدام عقدة BarrierNode ، التي تقطع البيانات قبل الوقت المحدد:

 |barrier() .period(5m) 

أمثلة على استخدام أنماط Go'sh في الرسالة


تتوافق القوالب مع التنسيق من حزمة text.template ، فيما يلي بعض المهام الشائعة.

إذا-آخر


نرتب الأشياء ، نحن لا نشغل الناس بالنص مرة أخرى:

 |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) - الإبداع والمزيد!

واحدة من الأدوات المخصصة لدينا هو برنامج نصي بيثون صغير لإرسال الإخطارات إلى الركود.
أولاً ، أردنا إرسال صورة من 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 host-or-ip : 9092 logs lvl = error

البديل مع httpOut


يعرض البيانات في خط الأنابيب الحالي:

 |httpOut('something') 

شاهد (الحصول على): host-or-ip : 9092 / kapacitor / v1 /asks / task_name / شيء ما

مخطط التنفيذ




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


الطابع الزمني للتدفق على Writeback


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

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

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

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


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

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

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

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

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

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

انضمام واحد للكثيرين


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

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

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

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

ماذا قررنا؟


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

لماذا لا غرافانا؟


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

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

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



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



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

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





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



كيف فعلت ذلك؟


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

ماذا فعلت كنتيجة:

  • الانضمام إلى حلقتين في بضع ساعات ، تجميع حسب القناة ؛
  • املأ السلسلة بالمجموعات ، إذا لم تكن هناك بيانات ؛
  • مقارنة متوسط ​​الدقائق العشر الأخيرة بالبيانات السابقة ؛
  • نصرخ إذا وجدنا شيئًا ما ؛
  • إرسال المعدلات المحسوبة والتنبيهات التي حدثت في influxdb ؛
  • إرسال رسالة مفيدة إلى الركود.

في رأيي ، لقد نجحنا بشكل جميل قدر الإمكان في كل ما نود الحصول عليه في المخرجات (وحتى أكثر من ذلك بقليل مع معالجات مخصصة).

على github.com ، يمكنك رؤية نموذج التعليمة البرمجية والحد الأدنى للمخطط (graphviz) للبرنامج النصي الناتج.

مثال على الكود الناتج:
 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 أو غيرها من الأدوات قائمة الأمنيات بالكامل.

Source: https://habr.com/ru/post/ar478702/


All Articles