เคล็ดลับในการประมวลผลเมตริกใน Kapacitor

เป็นไปได้มากว่าทุกวันนี้ไม่มีใครถามว่าทำไมจึงจำเป็นต้องรวบรวมตัวชี้วัดการบริการ ขั้นตอนตรรกะถัดไปคือการตั้งค่าการแจ้งเตือนสำหรับเมตริกที่รวบรวม ซึ่งจะแจ้งเตือนเกี่ยวกับการเบี่ยงเบนของข้อมูลในช่องที่สะดวกสำหรับคุณ (เมล, Slack, Telegram) ในบริการจองโรงแรมออนไลน์ Ostrovok.ru ตัวชี้วัดทั้งหมดของบริการของเราถูกเทลงใน InfluxDB และแสดงใน Grafana และการแจ้งเตือนพื้นฐานก็ได้รับการกำหนดค่าที่นั่นด้วย สำหรับงานเช่น “คุณต้องคำนวณบางอย่างและเปรียบเทียบกับมัน” เราใช้ Kapacitor

เคล็ดลับในการประมวลผลเมตริกใน Kapacitor
Kapacitor เป็นส่วนหนึ่งของสแต็ก TICK ที่สามารถประมวลผลเมตริกจาก InfluxDB สามารถเชื่อมโยงการวัดต่างๆ เข้าด้วยกัน (รวม) คำนวณสิ่งที่มีประโยชน์จากข้อมูลที่ได้รับ เขียนผลลัพธ์กลับไปยัง InfluxDB ส่งการแจ้งเตือนไปยัง Slack/Telegram/mail

กองทั้งหมดนั้นเจ๋งและมีรายละเอียด เอกสารแต่จะมีสิ่งที่เป็นประโยชน์ที่ไม่ได้ระบุไว้อย่างชัดเจนในคู่มือเสมอ ในบทความนี้ ฉันตัดสินใจรวบรวมเคล็ดลับที่มีประโยชน์และไม่ชัดเจนจำนวนหนึ่ง (อธิบายไวยากรณ์พื้นฐานของ TICKscipt แล้ว ที่นี่) และแสดงให้เห็นว่าสามารถนำไปใช้ได้อย่างไรโดยใช้ตัวอย่างการแก้ปัญหาข้อใดข้อหนึ่งของเรา

Here we go!

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)

กรอกเข้าร่วม (ภายใน vs ภายนอก)

ตามค่าเริ่มต้น การเข้าร่วมจะละทิ้งจุดที่ไม่มีข้อมูล (ภายใน)
ด้วยการเติม ('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) - ความคิดสร้างสรรค์และไม่มีอะไรเพิ่มเติม!

ธรรมเนียมอย่างหนึ่งของเราคือสคริปต์ Python ขนาดเล็กสำหรับส่งการแจ้งเตือนไปยังจุดหย่อน
ในตอนแรก เราต้องการส่งรูปภาพกราฟาน่าที่มีการป้องกันการอนุญาตทางข้อความ หลังจากนั้น ให้เขียน 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")

ดู (cli): kapacitor -url โฮสต์หรือ ip:9092 บันทึก lvl=error

ตัวเลือกที่มี httpOut

แสดงข้อมูลในไปป์ไลน์ปัจจุบัน:

|httpOut('something')

ดู (รับ): โฮสต์หรือ ip:9092/kapacitor/v1/tasks/task_name/something

รูปแบบการดำเนินการ

  • แต่ละงานส่งคืนแผนผังการดำเนินการพร้อมตัวเลขที่มีประโยชน์ในรูปแบบ กราฟวิซ.
  • ใช้บล็อก จุด.
  • วางลงในโปรแกรมดู สนุก.

คุณสามารถหาคราดได้ที่ไหนอีก?

การประทับเวลาใน influxdb ในการเขียนกลับ

ตัวอย่างเช่น เราตั้งค่าการแจ้งเตือนสำหรับผลรวมของคำขอต่อชั่วโมง (groupBy(1h)) และต้องการบันทึกการแจ้งเตือนที่เกิดขึ้นใน influxdb (เพื่อแสดงข้อเท็จจริงของปัญหาอย่างสวยงามบนกราฟใน grafana)

influxDBOut() จะเขียนค่าเวลาจากการแจ้งเตือนไปยังการประทับเวลา ดังนั้น จุดบนแผนภูมิจะถูกเขียนก่อนหรือหลังกว่าที่การแจ้งเตือนมาถึง

เมื่อจำเป็นต้องมีความแม่นยำ: เราจะแก้ไขปัญหานี้โดยการเรียกตัวจัดการแบบกำหนดเอง ซึ่งจะเขียนข้อมูลไปยัง influxdb ด้วยการประทับเวลาปัจจุบัน

นักเทียบท่า การสร้าง และการปรับใช้

เมื่อเริ่มต้น kapacitor สามารถโหลดงาน เทมเพลต และตัวจัดการจากไดเร็กทอรีที่ระบุในการกำหนดค่าในบล็อก [load]

หากต้องการสร้างงานอย่างถูกต้อง คุณต้องมีสิ่งต่อไปนี้:

  1. ชื่อไฟล์ – ขยายเป็นรหัส/ชื่อสคริปต์
  2. ประเภท – สตรีม/แบทช์
  3. dbrp – คีย์เวิร์ดเพื่อระบุว่าฐานข้อมูล + นโยบายใดที่สคริปต์ทำงาน (dbrp “ซัพพลายเออร์” “autogen”)

หากงานแบตช์บางงานไม่มีบรรทัดที่มี dbrp บริการทั้งหมดจะปฏิเสธที่จะเริ่มต้นและจะเขียนเกี่ยวกับงานดังกล่าวในบันทึกโดยสุจริต

ในทางกลับกัน chronograf ไม่ควรมีบรรทัดนี้และไม่ได้รับการยอมรับผ่านอินเทอร์เฟซและทำให้เกิดข้อผิดพลาด

แฮ็กเมื่อสร้างคอนเทนเนอร์: Dockerfile ออกด้วย -1 หากมีบรรทัดด้วย //.+dbrp ซึ่งจะช่วยให้คุณเข้าใจสาเหตุของความล้มเหลวได้ทันทีเมื่อประกอบบิวด์

เข้าร่วมหนึ่งต่อหลาย

งานตัวอย่าง: คุณต้องใช้เวลาในการให้บริการเป็นเปอร์เซ็นไทล์ที่ 95 เป็นเวลาหนึ่งสัปดาห์ เปรียบเทียบแต่ละนาทีของ 10 นาทีสุดท้ายด้วยค่านี้

คุณไม่สามารถทำการเข้าร่วมแบบหนึ่งต่อกลุ่มได้ สุดท้าย/เฉลี่ย/มัธยฐานเหนือกลุ่มจุดจะเปลี่ยนโหนดให้เป็นสตรีม ข้อผิดพลาด “ไม่สามารถเพิ่มขอบที่ไม่ตรงกันลูก: ชุด -> สตรีม” จะถูกส่งกลับ

ผลลัพธ์ของแบตช์ซึ่งเป็นตัวแปรในนิพจน์แลมบ์ดาจะไม่ถูกแทนที่เช่นกัน

มีตัวเลือกในการบันทึกหมายเลขที่จำเป็นจากชุดแรกลงในไฟล์ผ่าน udf และโหลดไฟล์นี้ผ่านไซด์โหลด

เราแก้ปัญหาอะไรกับสิ่งนี้?

เรามีซัพพลายเออร์โรงแรมประมาณ 100 ราย แต่ละรายสามารถมีการเชื่อมต่อได้หลายทาง เรียกได้ว่าเป็นช่องทางกันเลยทีเดียว มีช่องประมาณ 300 ช่อง แต่ละช่องอาจหลุดได้ จากตัวชี้วัดที่บันทึกไว้ทั้งหมด เราจะตรวจสอบอัตราข้อผิดพลาด (คำขอและข้อผิดพลาด)

ทำไมไม่กราฟาน่าล่ะ?

การแจ้งเตือนข้อผิดพลาดที่กำหนดค่าใน Grafana มีข้อเสียหลายประการ บางอย่างก็สำคัญ บางอย่างคุณสามารถหลับตาได้ ขึ้นอยู่กับสถานการณ์

Grafana ไม่รู้วิธีคำนวณระหว่างการวัด + การแจ้งเตือน แต่เราต้องการอัตรา (คำขอ-ข้อผิดพลาด)/คำขอ

ข้อผิดพลาดดูน่ารังเกียจ:

เคล็ดลับในการประมวลผลเมตริกใน Kapacitor

และความชั่วร้ายน้อยลงเมื่อดูด้วยการร้องขอที่ประสบความสำเร็จ:

เคล็ดลับในการประมวลผลเมตริกใน Kapacitor

โอเค เราสามารถคำนวณอัตราล่วงหน้าในบริการก่อนกราฟาน่าได้ และในบางกรณีก็จะได้ผล แต่ไม่ใช่ของเราเพราะว่า... สำหรับแต่ละช่องอัตราส่วนของตัวเองถือว่า "ปกติ" และการแจ้งเตือนทำงานตามค่าคงที่ (เรามองด้วยตาเปล่าเปลี่ยนหากมีการแจ้งเตือนบ่อยครั้ง)

นี่คือตัวอย่าง "ปกติ" สำหรับช่องต่างๆ:

เคล็ดลับในการประมวลผลเมตริกใน Kapacitor

เคล็ดลับในการประมวลผลเมตริกใน Kapacitor

เราเพิกเฉยต่อประเด็นก่อนหน้าและถือว่าภาพ "ปกติ" นั้นคล้ายคลึงกันสำหรับซัพพลายเออร์ทุกราย ตอนนี้ทุกอย่างเรียบร้อยดี แล้วเราจะผ่านการแจ้งเตือนในกราฟาน่าไปได้หรือยัง?
เราทำได้ แต่เราไม่ต้องการจริงๆ เพราะเราต้องเลือกหนึ่งในตัวเลือก:
ก) สร้างกราฟจำนวนมากสำหรับแต่ละช่องแยกกัน (และติดตามอย่างเจ็บปวด)
b) ออกจากแผนภูมิเดียวพร้อมทุกช่อง (และหลงไปกับเส้นสีสันสดใสและการแจ้งเตือนที่ปรับแต่งเอง)

เคล็ดลับในการประมวลผลเมตริกใน Kapacitor

คุณทำได้อย่างไร?

มีตัวอย่างการเริ่มต้นที่ดีอีกครั้งในเอกสารประกอบ (กำลังคำนวณอัตราข้ามซีรี่ส์ที่เข้าร่วม) สามารถมองหรือนำมาเป็นพื้นฐานในปัญหาที่คล้ายกันได้

สิ่งที่เราทำในท้ายที่สุด:

  • เข้าร่วมสองซีรีส์ในเวลาไม่กี่ชั่วโมง โดยจัดกลุ่มตามช่อง
  • กรอกข้อมูลตามลำดับกลุ่มหากไม่มีข้อมูล
  • เปรียบเทียบค่ามัธยฐานของ 10 นาทีที่ผ่านมากับข้อมูลก่อนหน้า
  • เราตะโกนหากพบบางสิ่ง
  • เราเขียนอัตราที่คำนวณได้และการแจ้งเตือนที่เกิดขึ้นใน influxdb
  • ส่งข้อความที่เป็นประโยชน์ถึงหย่อน

ในความคิดของฉัน เราสามารถบรรลุทุกสิ่งที่เราต้องการได้ในตอนท้าย (และมากกว่านั้นอีกเล็กน้อยด้วยตัวจัดการแบบกำหนดเอง) ให้สวยงามที่สุดเท่าที่จะเป็นไปได้

คุณสามารถดูได้ที่ 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)

อุปสรรคในการเข้าไม่สูงมาก - ลองใช้หากกราฟาน่าหรือเครื่องมืออื่น ๆ ไม่ตรงตามความต้องการของคุณ

ที่มา: will.com

เพิ่มความคิดเห็น