เกริ่นนำ

เมื่อบริการเติบโตขึ้น สัญญาณที่ต้องตรวจสอบระหว่างการปฏิบัติการก็เพิ่มขึ้นตามไปด้วย บทความนี้จะแนะนำ กระบวนการจัดการ Alert เป็นโค้ด และ การทำให้ขั้นตอนการตอบสนองที่เชื่อมต่อไปยัง Slack และ PagerDuty เป็นมาตรฐาน

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


แรงจูงใจ

มีหลายวิธีในการเพิ่มความพร้อมใช้งานของบริการและลดผลกระทบต่อผู้ใช้

ในบรรดาวิธีเหล่านั้น งานครั้งนี้มุ่งเน้นไปที่ การปรับปรุงระบบ Alert

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

ทิศทางที่อยากปรับปรุงในตอนนั้นก็ชัดเจนเช่นกัน คือ จับสัญญาณความเสี่ยงให้ดีขึ้น, ทำให้ผู้รับผิดชอบสังเกตเห็นได้เร็วขึ้น, เชื่อมต่อไปยังการตรวจสอบและการตอบสนองได้ทันที, และ ลดขั้นตอนการตอบสนองที่เกิดซ้ำ ๆ

แม้จะไม่ได้เริ่มต้นจากการวัดผลทุกอย่างเชิงปริมาณตั้งแต่แรก แต่ความตระหนักว่า Alert ไม่ใช่แค่การแจ้งเตือนธรรมดา หากต้องเป็น ระบบปฏิบัติการที่ช่วยป้องกันเหตุขัดข้องและเชื่อมต่อไปสู่การตอบสนอง นั้นชัดเจนมาก


บทบาทของ Alert

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

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

กล่าวอีกอย่างคือ Alert ไม่ใช่แค่การแจ้งว่า "มีปัญหาเกิดขึ้น" แต่ใกล้เคียงกับ อินเทอร์เฟซด้านปฏิบัติการที่เชื่อมระหว่างการป้องกันเหตุขัดข้องและการตอบสนอง


จุดที่น่าเสียดายของ Alert แบบเดิม

จุดที่น่าเสียดายหลัก ๆ ของ Alert แบบเดิมมีอยู่สามอย่าง คือ สร้างยาก, ถึงจะได้รับก็เข้าใจได้ไม่ทันที, และ ไม่ชัดเจนว่าใครต้องเป็นผู้ตอบสนองและดูแลจัดการ

สร้าง Alert ได้ยาก

ในเวลานั้น การสร้างและการส่ง Alert เกี่ยวข้องกับหลายระบบ เช่น Grafana, Slack, PagerDuty, CloudWatch, EventBridge และ Lambda ขณะเดียวกัน data source ก็หลากหลาย ไม่ว่าจะเป็น NewRelic, VictoriaMetrics, Steampipe, OpenSearch, Druid และ MySQL

แต่ละ Alert ก็มีวิธีทำงานต่างกันไป บางตัวส่งจาก Grafana ไปยัง Slack โดยตรง บางตัวผูก Lambda ไว้หลัง CloudWatch Alarm บางตัวใช้ Steampipe เพื่อตรวจสอบสถานะรีซอร์สบน AWS แล้วตัดสินผล และหากต้องเชื่อมกับ PagerDuty ก็ยังต้องพิจารณาการตั้งค่าเพิ่มเติมแยกต่างหาก

ปัญหาคือ ยังขาด convention ในระดับองค์กร ว่าควรจัดการ Alert แบบใดไว้ที่ไหน จะส่งแค่ Slack หรือเชื่อมไปถึง PagerDuty ด้วย ในข้อความควรใส่คำอธิบายและลิงก์แบบใด และจะจัดการทีมผู้รับผิดชอบกับเส้นทางการส่งต่อจากที่ใด ทั้งหมดยังไม่ได้ถูกจัดระเบียบมากพอ

ผลลัพธ์คือ แม้ Alert จะถูกสร้างขึ้นตามความจำเป็น แต่เมื่อเวลาผ่านไป วิธีการสร้างและวิธีการจัดการก็ยิ่ง กระจัดกระจาย มากขึ้นเรื่อย ๆ

ดู Alert ได้ยาก

ต่อให้สร้าง Alert ขึ้นมาแล้ว ข้อความที่ส่งเข้า Slack ก็ไม่ได้อยู่ในรูปแบบที่อ่านง่ายเสมอไป รูปแบบและคุณภาพของข้อมูลแตกต่างกันไปตามผู้สร้างและระบบ บาง Alert มีชื่อยาวและซับซ้อนเกินไป และบางครั้งค่าภายในอย่าง Value หรือ Labels ก็ถูกแสดงออกมาตรง ๆ

แม้จะมีลิงก์อยู่ แต่บางครั้งก็ไม่ชัดเจนว่าควรดูอะไรเป็นอย่างแรก และถึงจะมีปุ่ม Dashboard หรือ Log ก็มีกรณีที่ยังไม่ได้เชื่อมต่อใช้งานจริง นอกจากนี้ หาก context ของ Alert มีไม่เพียงพอ ผู้รับผิดชอบก็มักต้องกลับไปค้นหาบริการ คลัสเตอร์ รีซอร์ส หรือช่วงเวลาเองอีกครั้ง

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

จัดการความรับผิดชอบในการตอบสนองต่อ Alert ได้ยาก

เมื่อ Alert ดังขึ้น ก็มีหลายกรณีที่ยังคลุมเครือว่าใครควรเป็นผู้ตรวจสอบและตอบสนอง หากไม่แสดงทีมผู้รับผิดชอบหรือผู้รับผิดชอบที่ชัดเจน คนที่เห็น Alert จะต้องเริ่มจากการตัดสินใจก่อนว่า "ฉันต้องดูอันนี้หรือเปล่า?", "ควรถามใครดี?" และในการณ์เหตุขัดข้อง การตัดสินใจสั้น ๆ แบบนี้ก็อาจนำไปสู่ความล่าช้าในการตอบสนองได้

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

สรุปแล้ว สถานการณ์ที่ต้องการปรับปรุงมีดังนี้

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

ปรับปรุงอะไร และอย่างไร

ทิศทางการปรับปรุงมีอยู่สามข้อ คือ ทำให้วิธีสร้าง Alert เป็นมาตรฐาน, นำเสนอข้อมูลที่จำเป็นในข้อความ Slack ด้วยโครงสร้างที่สม่ำเสมอ, และ จัดโครงสร้างให้มองเห็นผู้รับผิดชอบและเส้นทางการส่งต่อของแต่ละ Alert ได้อย่างชัดเจน

ทำให้การสร้างและการจัดการ Alert เป็นมาตรฐาน

อย่างแรกคือรวบวิธีการสร้างและจัดการ Alert ให้เป็นหนึ่งเดียว การประเมินและการทำงานของ Alert rule ถูก รวมให้ใช้ Grafana เป็นมาตรฐาน การเชื่อมต่อระหว่าง Grafana·Slack·PagerDuty ถูก ทำ abstraction เป็น Terraform Module และนิยาม Alert ทั้งหมดถูกจัดการเป็น IaC ภายใต้ไดเรกทอรี alerts/ ของรีโป alerts ภายในบริษัท นอกจากนี้ยังจัดให้โมดูลส่วนกลางเป็นผู้ดูแลการเชื่อมต่อช่อง Slack การเชื่อมกับ PagerDuty รูปแบบข้อความ และการสร้างปุ่มส่วนกลางด้วย

จากนี้ไป ผู้เขียน Alert ไม่จำเป็นต้องเข้าใจ Alert pipeline ทั้งหมดครบทุกส่วน แต่สามารถโฟกัสกับ จะตรวจจับเงื่อนไขอะไร, Alert นี้สำคัญแค่ไหน, ใครควรเป็นผู้ตรวจสอบ, และ ต้องมีข้อมูลอะไรเพื่อการตอบสนอง ได้มากขึ้น

ภายในรีโปยังมีการจัดการแนวทางการเขียน โครงสร้างไดเรกทอรี ฟิลด์ที่จำเป็น และ convention ที่แนะนำร่วมกัน และเมื่อจัดการ Alert เป็นโค้ดแล้ว การรีวิวและประวัติการเปลี่ยนแปลงก็จะถูกเก็บไว้ในระดับ PR และ commit ด้วย

โครงสร้างไดเรกทอรี Alert

Alert ทั้งหมดถูกจัดให้ใช้โครงสร้าง {main-category}/{sub-category}/{severity}/{alert-name}.yml

ตัวอย่างเช่น

  • infra/kubernetes/critical/pod-unhealthy.yml
  • data/airflow/warning/task-failed.yml
  • finops/aws/warning/cost-increase.yml

ด้วยโครงสร้างนี้ เพียงดูตำแหน่งไฟล์ก็จะเข้าใจได้ว่า เป็น Alert ของโดเมนใด และ ถูกจัดการในระดับความรุนแรงประมาณไหน อีกทั้งยังสามารถใช้เป็นเกณฑ์ในการรวบรวม Alert ของแต่ละโดเมน ตรวจสอบ Alert ที่ซ้ำซ้อนหรือเก่า รวมถึงเชื่อมต่อ Slack channel, PagerDuty Service และ CODEOWNERS ได้ด้วย

รูปแบบการนิยาม Alert

ในแต่ละไฟล์ Alert จะใส่ข้อมูลอย่าง datasource, query, threshold, condition, message ไว้ด้วยกัน

ไม่ได้สร้าง DSL ใหม่แยกขึ้นมา แต่เป็นรูปแบบที่ใกล้เคียงกับการนำเนื้อหาของ Grafana Alert ที่ถูก serialize เป็น JSON มาแสดงในรูป YAML ทำให้หากเป็น Alert ที่สามารถนิยามใน Grafana ได้ ส่วนใหญ่ก็สามารถทำเป็น IaC ได้ด้วยโครงสร้างเดียวกัน

ในช่วงหลังยังมีการใช้ LLM ด้วย หากมีคนอธิบายด้วยภาษาธรรมดาว่า "อยากได้รับ Alert ด้วยข้อความแบบไหนภายใต้เงื่อนไขใด" LLM ก็จะอ้างอิงตัวอย่างและ convention เดิม เพื่อสร้างร่างนิยาม Alert ในรูปแบบ YAML ขึ้นมาให้ ด้วยเหตุนี้ ผู้เขียน Alert จึงสามารถโฟกัสกับ จะตรวจจับอะไร และทำไมจึงจำเป็น ได้มากกว่าการจมอยู่กับรูปแบบการ serialize ที่ซับซ้อน

ทำให้เข้าใจและตอบสนองต่อข้อความ Alert ได้ทันที

เรามองว่าข้อความ Alert เองก็เป็น อินเทอร์เฟซ อย่างหนึ่ง ในสถานการณ์เหตุขัดข้อง มักไม่มีเวลามากพอที่จะค่อย ๆ ตีความข้อความ ดังนั้นไม่ว่าจะมี Alert แบบใดเข้ามา ก็ต้องสามารถตรวจดูข้อมูลประเภทเดียวกันได้จากตำแหน่งเดียวกันเสมอ

ดังนั้นจึงจัดโครงสร้างข้อความใน Slack ให้เป็นรูปแบบเดียวกัน โดยในหัวข้อจะใส่ชื่อ Alert, สถานะ และระดับความรุนแรง ส่วนเนื้อหาจะใส่คำอธิบายที่คนอ่านแล้วเข้าใจได้ทันที พร้อมทั้งผู้รับผิดชอบ ทีม บริการ รีเจียน ชื่อรีซอร์ส และ label สำคัญต่าง ๆ นอกจากนี้ยังแยกปุ่มออกเป็นปุ่มกลางและปุ่มเลือกใช้ โดยค่าเริ่มต้นจะแสดง IaC, PagerDuty, Silence และจะแสดง Runbook, Dashboard, Log เฉพาะเมื่อจำเป็นเท่านั้น

ปุ่มกลางถูกสร้างและเชื่อมโยงโดยระบบอัตโนมัติ และยังให้บันทึกการเปลี่ยนสถานะของ Alert ทั้งหมดไว้ใน Slack thread ด้วย ทำให้สามารถดูเป็นลำดับเดียวกันได้ว่าใครเป็นคน Acknowledge, จัดการจากใน Slack หรือจาก PagerDuty, Resolve เมื่อไร และระหว่างการตอบสนองมีการทิ้งบันทึกอะไรไว้บ้าง

ผลลัพธ์คือไม่ว่าใครจะสร้าง Alert แบบไหน รูปแบบที่เห็นใน Slack ก็คล้ายกันมากขึ้น และสมาชิกทีมก็ตัดสินใจได้เร็วขึ้นว่าควรดูตรงไหน

ทำให้โครงสร้างความรับผิดชอบของ Alert ชัดเจน

สิ่งที่สำคัญพอ ๆ กับการดู Alert แล้วเข้าใจได้ทันที คือการทำให้เห็นชัดว่า ใครต้องรับผิดชอบและต้องเป็นคนตรวจสอบ Alert นั้น

จึงนำข้อมูล tag และ label ของรีซอร์สมาใช้ในขั้นตอนการตอบสนอง แทนที่จะระบุทีมผู้รับผิดชอบหรือผู้รับผิดชอบลงไปในแต่ละ Alert โดยตรง ก็ใช้ metadata เช่น บริการ ทีม รีซอร์ส และสภาพแวดล้อม เพื่อให้ Slack message mention ทีมและผู้รับผิดชอบที่เหมาะสมโดยอัตโนมัติ

เส้นทางการส่งต่อก็ถูกจัดให้อยู่ภายใต้กฎเดียวกัน โดยอ้างอิงจากการจัดประเภทของ Alert และ severity เพื่อให้เชื่อมต่อกับ Slack channel, PagerDuty Service และ Escalation Policy โดยอัตโนมัติ Alert ระดับ Warning จะส่งไปแค่ Slack channel ส่วน Critical Alert ที่มีผลกระทบต่อผู้ใช้หรือมีโอกาสเป็นเหตุขัดข้องสูง จะสร้าง PagerDuty Incident ด้วย

ยังนำ CODEOWNERS มาใช้ร่วมกันด้วย โดยแยกไฟล์ Alert ออกเป็นไดเรกทอรีตามหมวดหมู่และขอบเขตของบริการ และกำหนดทีมผู้รับผิดชอบของแต่ละ path ใน CODEOWNERS เพื่อให้เห็นภายในรีโปได้ชัดว่าทีมใดเป็นเจ้าของพื้นที่ Alert ส่วนไหน

ผลลัพธ์คือความรับผิดชอบของ Alert ถูกจัดการจากสองจุด คือ เมื่อ Alert ดังขึ้นจริง จะ mention ทีมและผู้รับผิดชอบตาม tag และ label และ เมื่อมีการเปลี่ยนแปลงนิยามของ Alert ก็จะตรวจสอบได้ว่าเป็นพื้นที่ของทีมใดโดยอิงจากโครงสร้างไดเรกทอรีและ CODEOWNERS

บทบาทของ Alert proxy

เพื่อให้โครงสร้างนี้ทำงานได้จริง จำเป็นต้องมีเลเยอร์ตรงกลางสำหรับตีความและส่งต่อ Alert ดังนั้นจึงวาง proxy ที่ทำงานบน AWS Lambda ไว้ระหว่าง Grafana, Slack และ PagerDuty

Grafana ทำหน้าที่ประเมิน Alert rule และส่ง webhook มา ส่วน proxy จะรับ webhook นี้แล้วตีความ Alert context เช่น category, severity, label, annotation และ fingerprint จากนั้นจึงตัดสินใจว่าจะส่งไป Slack channel ไหน จะสร้าง PagerDuty Incident แบบใด จะ mention ใคร จะแนบปุ่มอะไร จะอัปเดต Slack thread เดิมอย่างไร และจะจัดการ lifecycle ของ Ack/Resolve อย่างไร

กล่าวคือ หาก Terraform module และโครงสร้างไดเรกทอรีทำหน้าที่ทำให้ "จะนิยาม Alert อย่างไร" เป็นมาตรฐาน proxy ก็ทำหน้าที่เชื่อมให้นิยามนั้นแสดงผลและทำงานในรูปแบบเดียวกันในกระบวนการปฏิบัติการจริง

ด้วย proxy จึงสามารถจัดการรูปแบบข้อความใน Slack, การ mention ผู้รับผิดชอบ, การเชื่อมต่อกับ PagerDuty, การอัปเดต Slack thread และการโต้ตอบแบบ Ack/Resolve ได้อย่างสม่ำเสมอจากจุดเดียว และยังขยายต่อไปยังการปรับปรุงต่าง ๆ อย่าง grouped Alert, custom action button, การเชื่อมต่อ AI agent และ Alert model กลาง ได้ง่ายขึ้นด้วย


ยังมีอะไรที่น่าเสียดายอีกบ้าง

หลังการปรับปรุงครั้งแรก นิยามของ Alert ถูกจัดการด้วย IaC และทั้งข้อความใน Slack กับเส้นทางการส่งต่อก็เริ่มทำงานภายใต้กฎที่สม่ำเสมอ แต่ระบบ Alert ไม่ใช่เครื่องมือที่สร้างครั้งเดียวแล้วจบ เมื่อลองใช้งานจริงเกือบหนึ่งปี ก็เริ่มเห็นปัญหาใหม่ ๆ ตามมาว่าเมื่อ Alert มีจำนวนมากขึ้น ควรแสดงหลาย instance ที่เกิดขึ้นภายใน Alert เดียวกันอย่างไร จะจัดการนิยาม Alert ที่ซ้ำกันอย่างไร จะทำให้คนที่เห็น Alert แล้วสามารถลงมือทำอะไรต่อได้จริงอย่างไร และจะรับประกันพร้อมตรวจสอบความเสถียรของระบบ Alert เองได้อย่างไร

เมื่อ Alert เดียวกันดังพร้อมกันหลายเป้าหมาย จะดูยาก

เมื่อสร้าง Alert ได้ง่ายขึ้น จำนวน Alert ก็เพิ่มขึ้นตามธรรมชาติ สิ่งที่ไม่สะดวกอย่างเห็นได้ชัดในตอนนั้นคือกรณีที่ Alert rule เดียวกันดังพร้อมกันกับหลายเป้าหมาย

ใน Grafana แม้จะเป็น rule เดียวกัน แต่หากค่า label เช่น region, name, node, pod, app แตกต่างกัน ก็จะถูกมองเป็น Alert instance แยกกัน ตัวอย่างเช่น หากมี Pod unhealthy Alert แล้วหลาย pod กลายเป็น unhealthy พร้อมกัน ก็จะเกิด Alert instance แยกตามแต่ละ pod

แม้ Grafana จะมีฟีเจอร์ Alert Grouping อยู่แล้ว แต่การแค่จับรวมเป็น group อย่างเดียวยังไม่เพียงพอ สิ่งสำคัญคือการแสดงสถานะของ Alert ที่ถูกจัดกลุ่มให้ผู้ปฏิบัติงานเข้าใจได้ง่าย ว่าภายใน group มีเป้าหมายอะไรบ้าง ใครยัง firing อยู่ ใครเพิ่ง resolve ไป มีเป้าหมายใหม่ถูกเพิ่มเข้ามาหรือไม่ และการเปลี่ยนสถานะของ group เดียวกันต่อเนื่องกันเป็นลำดับเดียวหรือไม่

นิยาม Alert ที่ซ้ำกันเพิ่มขึ้น

เมื่อจำนวน Alert definition มากขึ้น วิธีคัดลอก YAML แล้วแก้เล็กน้อยทีละจุดก็เริ่มชนข้อจำกัด Alert อย่าง SQS lag, CloudWatch error log, Pod OOM, ALB 5xx, Lambda error/throttle เป็นสิ่งที่ต้องสร้างซ้ำบ่อย ๆ และในช่วงแรกก็แค่คัดลอกไฟล์เดิมแล้วเปลี่ยนชื่อ query threshold และ label เท่านั้น

แต่เมื่อไฟล์มีจำนวนมากขึ้น ก็เริ่มแก้พฤติกรรมร่วมได้ยาก และเกิดปัญหาว่าแม้จะเป็น Alert ที่มีเจตนาเดียวกัน แต่ลิงก์ dashboard, โครงสร้าง labels และรูปแบบการแสดง threshold กลับต่างกันเล็กน้อย จึงจำเป็นต้องมี โครงสร้างที่นำ pattern ที่ใช้ซ้ำกลับมาใช้ใหม่ได้

แม้จะเห็น Alert แล้ว แต่ยังห่างไกลจากการลงมือทำขั้นถัดไป

การใส่ปุ่ม Runbook, Dashboard, IaC และ PagerDuty ลงใน Slack message นั้นช่วยได้ แต่ในการตอบสนองต่อเหตุขัดข้องจริง หลายครั้งแค่ลิงก์อย่างเดียวก็ยังไม่เพียงพอ โดยเฉพาะ Runbook แม้จะมีประโยชน์ชัดเจน แต่การแนบ Runbook ที่ดีให้ครบทุก Alert และคอยอัปเดตให้ทันสมัยอยู่เสมอไม่ใช่เรื่องง่าย

อีกทั้งในการตอบสนองจริง งานสืบสวนอย่างการตรวจ log ของ Kubernetes, ตรวจสถานะ pod, ตรวจ rollout history, ตรวจ metric ของ SQS·Lambda หรือเช็ก error log ก็เป็นงานที่ต้องทำซ้ำคล้ายเดิมอยู่เสมอ งานเหล่านี้ส่วนใหญ่เกิดขึ้นนอก Slack message และผู้รับผิดชอบต้องอ่าน label กับ value จาก Alert แล้วย้ายไปยังเครื่องมืออื่น นำค่าต่าง ๆ ไปใช้ต่อ ก่อนจะแชร์ผลลัพธ์กลับมาใน Slack thread อีกครั้ง

ท้ายที่สุด การปรับปรุงครั้งแรกช่วยให้เราอ่าน Alert ได้ดีขึ้นก็จริง แต่การสืบสวนและการตอบสนองก็ยังคงอยู่นอกตัวข้อความ Alert เป็นจำนวนมาก

SPOF ในระบบมอนิเตอร์เพิ่มขึ้น

เมื่อจัดระบบ Alert ให้เป็นระเบียบ จุดที่อาจกลายเป็น SPOF ก็เพิ่มขึ้นด้วย นิยามและการ deploy ของ Alert rule ถูกรวมไว้ที่รีโป alerts และ Terraform, การประเมิน Alert rule เป็นหน้าที่ของ Grafana ส่วน Slack message, PagerDuty Incident และ lifecycle ของ Ack/Resolve ถูกให้ proxy เป็นผู้จัดการ

การที่บทบาทชัดเจนขึ้นเป็นการเปลี่ยนแปลงที่ดี แต่ในขณะเดียวกันก็เพิ่มโอกาสที่หากจุดใดจุดหนึ่งล้มเหลว จะกระทบต่อ flow ของ Alert ทั้งหมด อีกสิ่งที่ยากกว่าคือความล้มเหลวแบบนี้อาจมองไม่เห็นจากภายนอกได้ง่าย หากเส้นทางที่มีหน้าที่แจ้งความผิดปกติของระบบอื่นหยุดเงียบไป ก็อาจไม่มีใครรู้แม้จะเกิดเหตุขัดข้องจริงขึ้น

ท้ายที่สุดจึงนำไปสู่คำถามว่า "แล้วใครจะคอยมอนิเตอร์ระบบมอนิเตอร์อีกที"


การปรับปรุงครั้งที่สอง

แม้การปรับปรุงครั้งแรกจะทำให้ได้โครงสร้างพื้นฐานของระบบ Alert แล้ว แต่เมื่อการสร้างและส่ง Alert กลายเป็นเรื่องง่ายขึ้น ปัญหาที่ต้องมองระหว่างการใช้งานจริงก็เปลี่ยนไปด้วย

ในการปรับปรุงครั้งที่สอง จึงโฟกัสอยู่ที่สี่เรื่อง คือ เมื่อหลายเป้าหมายดังพร้อมกันจาก Alert rule เดียวกัน ต้องรวมการเปลี่ยนสถานะให้ดูได้ในภาพเดียว, ต้องทำให้นิยาม Alert ที่ซ้ำกันสามารถนำกลับมาใช้ใหม่เป็น pattern กลางได้, ต้องเสริม Runbook พร้อมเชื่อมงานสืบสวนที่ทำซ้ำและงานบรรเทาปัญหาแบบจำกัดผ่านปุ่มใน Slack, และ ต้องวัดและตรวจสอบความเสถียรของนิยาม Alert การประเมิน และเส้นทางการส่งต่อที่อาจเป็น SPOF ได้

จัดการ Grouped Alert ให้ดีจริง ๆ

ก่อนอื่นได้ปรับปรุงวิธีแสดง grouped Alert เมื่อมีหลาย instance ยิงจาก Alert rule เดียวกันพร้อมกัน ถ้าส่งเป็นข้อความแยกกันทั้งหมด ช่อง Slack จะรก แต่ถ้ารวมเป็นก้อนเดียวก็เสี่ยงมองไม่เห็นว่าจริง ๆ แล้ว resource ไหนมีปัญหา

หัวใจสำคัญคือ รวม แต่ไม่ทำให้สถานะของสิ่งที่ถูกรวมหายไป ใน Slack จะแสดง grouped Alert เป็นข้อความตัวแทนเพียงข้อความเดียว แต่แสดงรายการเป้าหมายที่ได้รับผลกระทบอยู่ในตอนนั้นไปด้วย และถ้ามีการเพิ่มเป้าหมายใหม่หรือเป้าหมายเดิมถูกแก้ไขแล้ว การเปลี่ยนแปลงนั้นจะถูกทิ้งไว้ใน thread เดิม เมื่อมีการเปลี่ยนสถานะจำนวนมากพร้อมกัน ก็จะแสดงโดยรวมหลายการเปลี่ยนแปลงเป็น batch และฝั่ง PagerDuty ก็ปรับให้ชี้ไปยังปัญหาเดียวกันกับ grouped Alert ที่เห็นใน Slack

ผลลัพธ์คือ Alert หลายรายการที่เกิดจากสาเหตุเดียวกันสามารถมองเป็น หนึ่งลำดับการไหล บน Slack ได้

ลดการนิยาม Alert ที่ซ้ำ ๆ

วิธีคัดลอกแล้วค่อย ๆ แก้ไขทีละนิดจะยิ่งเพิ่มต้นทุนการดูแลรักษาและโอกาสผิดพลาดเมื่อจำนวน Alert มากขึ้น เพื่อแก้ปัญหานี้จึงเพิ่ม global/templates และ matrix

global/templates คือความสามารถในการนิยามโครงสร้าง Alert ที่ซ้ำกันเป็น template กลาง และ matrix คือความสามารถในการกระจาย Alert เดียวกันออกเป็นหลาย Alert ตามชุดผสมของ region, queue, datasource และ service

ได้สร้าง template สำหรับแพตเทิร์นที่เกิดซ้ำ เช่น SQS queue lag, CloudWatch error log, Pod OOM ของหลายคลัสเตอร์, ALB 5xx, Lambda error/throttle และ ECS memory/CPU/max-capacity แล้วให้ใส่เฉพาะค่าที่ต่างกัน เช่น ชื่อ queue, region, cluster, threshold และตัวแปร dashboard ไว้ใน matrix

เมื่อทำแบบนี้แล้ว จึงสามารถแก้โครงสร้างข้อความกลาง ปุ่ม การจัดการลิงก์ Runbook/dashboard และวิธีจัดการ datasource ได้จากที่เดียว อีกทั้งยังลดความไม่สอดคล้องและต้นทุนการดูแลรักษาที่มักเพิ่มขึ้นตามจำนวน Alert

เริ่มรับมือได้จากในข้อความ Slack ทันที

ถัดมาคือเพิ่มสิ่งที่ทำได้จากในข้อความ Slack แม้ลิงก์ Runbook และ dashboard จะยังสำคัญ แต่ก็อยากลดงานตรวจสอบหรือการบรรเทาปัญหาแบบจำกัดขอบเขตที่ต้องทำซ้ำ ๆ อยู่เสมอให้จบในข้อความ Slack ให้มากขึ้น

จึงเพิ่ม custom action button นอกเหนือจากปุ่มเดิม หากนิยามคำสั่งไว้ใน message.actions ของ Alert YAML ก็จะแสดงเป็นปุ่มในข้อความ Slack และเมื่อกดปุ่ม proxy จะรันคำสั่งผ่าน Lambda invocation แยกต่างหาก จากนั้นจะทิ้งคอมเมนต์ใน Slack thread เดิมว่าใครกดปุ่มอะไรและผลการรันเป็นอย่างไร

ด้วยปุ่มนี้จึงสามารถรองรับงานอย่างการดู log, ตรวจสอบสถานะ Kubernetes rollout, ทำ rollout restart ในสถานการณ์ที่จำกัด, การรันคำสั่งเดี่ยว และการรันหลายคำสั่งแบบต่อเนื่องได้

ส่วนที่ใส่ใจมากที่สุดคือ ความปลอดภัย หากชื่อปุ่มลงท้ายด้วย ! จะเปิด Slack confirm dialog และจะนำค่า label เช่น ${labels.namespace}, ${labels.pod} ไปแทนในคำสั่งโดยใช้ shell quoting เพื่อป้องกัน command injection ส่วนงานที่ต้องใช้สิทธิ์เพิ่มเติมจะให้ assume IAM role ผ่าน actionRole การใช้ role ที่ไม่ได้รับอนุญาตจะจัดการแบบ fail-closed และทั้ง webhook กับการโต้ตอบของ Slack ก็ตรวจสอบด้วย Bearer token, ลายเซ็น HMAC-SHA256 และ replay protection ตามลำดับ

เชื่อมต่อกับ AI เอเจนต์

ยังอยากลดขั้นตอนการรวบรวมข้อมูลที่จำเป็นหลังได้รับ Alert ด้วย จึงเชื่อม abot ซึ่งเป็น AI เอเจนต์ภายในเข้ากับ flow ของ Alert

เมื่อกดปุ่ม abot ในข้อความ Slack ตัว proxy จะรวบรวมชื่อ Alert, คำอธิบาย, labels, values, ลิงก์ IaC และ context ที่ผู้ใช้กรอกเพิ่มใน modal แล้วส่งคำขอวิเคราะห์ โดยให้ abot ทำงานด้วย OAuth-based identity ของผู้ที่กดปุ่ม เพื่อให้แม้จะไปดึงข้อมูลที่จำเป็นจาก Grafana, AWS หรือ Kubernetes ก็ยังดึงได้เฉพาะในขอบเขตที่ผู้ใช้นั้นมีสิทธิ์เห็นจริงเท่านั้น

ผลการวิเคราะห์จะถูกทิ้งเป็นคอมเมนต์ใน Slack thread เดิม พร้อมทั้งระบุว่าตรวจดู metric และ log อะไรบ้าง ควรสงสัยความเป็นไปได้ใดก่อน และมีเบาะแสอะไรที่ควรนำไปสรุปใน RCA ได้บ้าง ทำให้ลดเวลาที่ต้องเปิดหลายระบบเพื่อรวบรวมข้อมูลซ้ำอีกครั้ง

มอนิเตอร์ระบบมอนิเตอร์อีกชั้น

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

เริ่มจากเก็บ metric การปฏิบัติการของ proxy เช่น เวลาที่ใช้จนถึง Ack, เวลาที่ใช้จนถึง Resolve, จำนวน Alert ที่ยังค้างอยู่ตอนนี้, ระยะเวลาที่ Alert ค้างอยู่ และจำนวนครั้งที่ Alert เดิมกลับมาร้องซ้ำ รวมถึงเพิ่ม watchdog สำหรับตรวจจับเมื่อ Lambda เข้าใกล้ timeout และถ้า proxy ล้มเหลวระหว่างประมวลผล ก็ให้บันทึก full stack trace พร้อม event payload ต้นฉบับไว้ด้วย

แต่การให้ proxy เป็นฝ่ายแจ้งเตือนเองก็มีข้อจำกัด เพราะถ้า proxy ตายก่อนส่งการแจ้งเตือนนั้น Alert ที่ควรถูกส่งอาจหายไปเฉย ๆ

จึงวางกลไกตรวจจับไว้นอก proxy โดยพึ่งพาคนละระบบ ตัวหนึ่งคือ Grafana ที่นำ metric ซึ่ง proxy ส่งมาไปประเมินเป็น Alert ในโดเมน monitoring เพื่อให้เห็นความผิดปกติ อย่างไรก็ตาม Grafana และ VictoriaMetrics อยู่บน EKS เดียวกัน จึงตรวจจับไม่ได้หาก EKS หรือ Grafana ล่มทั้งก้อน

อีกตัวหนึ่งคือ deadman switch โดยปกติ Grafana จะส่ง /api/health ออกมาเป็น heartbeat และ heartbeat นี้จะถูกรับโดย CloudWatch ซึ่งแยกอิสระจาก EKS จากนั้น CloudWatch alarm จะตัดสินว่าเป็นเหตุขัดข้องเมื่อ heartbeat หยุดหรือหายไป และในกรณีนี้จะส่งแจ้งไปยัง PagerDuty และ Slack โดยตรงโดยไม่ผ่าน Grafana หรือ proxy

กล่าวคือวางฝั่งที่ตรวจจับกับฝั่งที่ถูกตรวจจับไว้บน infrastructure คนละชุด ทำให้ตราบใดที่ EKS และ CloudWatch ไม่ล่มพร้อมกัน ก็ยังรู้ได้ว่าระบบ monitoring ล่ม


งานปรับปรุงต่อจากนี้

แม้การปรับปรุงรอบที่สองจะเสร็จแล้ว แต่ก็ยังมีส่วนที่ต้องพัฒนาต่อ

ใช้ metric การปฏิบัติการที่เก็บมาให้คุ้มจริง ๆ

เมื่อ proxy เก็บ metric การปฏิบัติการของ Alert ก็ทำให้ตอบคำถามด้วยข้อมูลได้ เช่น ช่องไหนมี Alert แบบใดดังบ่อยแค่ไหน มี Alert ไหลไปรวมที่ผู้รับผิดชอบหรือทีมใดมากเกินไปหรือไม่ หรือมี Alert ที่วนอยู่แค่ firing กับ auto resolve โดยไม่มีการโต้ตอบใดเลยหรือเปล่า

แต่การมองเห็นข้อมูลกับการนำข้อมูลนั้นไปปรับแต่ง Alert จริงเป็นคนละเรื่อง ตอนนี้ยังไม่ได้ลงมือจริงจังกับการปรับ threshold, การรวม Alert ที่คล้ายกัน, การลบ Alert ที่ไม่จำเป็น, การลด noisy alert หรือการปรับปรุงที่มุ่งลดเวลาในการรับรู้และเวลาในการแก้ไขอย่างเป็นรูปธรรม

ปรับปรุง Alert IaC

ปัจจุบันนิยาม Alert ถูก deploy จาก alerts repo ผ่าน CI/CD แต่ยังอิงกับ resource grafana_rule_group ของ Grafana Terraform provider อยู่ ปัญหาคือแม้เปลี่ยนแค่ rule เดียว ใน PR ก็จะดูเหมือนทั้ง rule group ถูกเปลี่ยนไปหมด ทำให้อ่าน diff ได้ยาก และเนื่องจาก interval_seconds อยู่ในระดับ rule group หากต้องการกำหนดรอบการประเมินต่างกันในแต่ละ Alert ก็ต้องแยก group ให้ย่อยลงมาก

ช่วงหลัง Grafana มี alerting API ใหม่ที่จัดการ Alert rule ได้เหมือน Kubernetes resource และใน Terraform provider ก็เพิ่ม resource grafana_apps_rules_alertrule_v0alpha1 เข้ามาแล้ว แม้ตอนนี้ยังเป็น alpha จึงยังไม่ได้นำมาใช้ทันที แต่เมื่อเสถียรแล้วก็มีแผนจะพิจารณาย้ายจาก grafana_rule_group เดิม

สิ่งที่คาดหวังนั้นชัดเจนคือ แยกการนิยาม rule group กับ rule ออกจากกัน, เปลี่ยนแค่ rule เดียวก็เห็นเฉพาะการเปลี่ยนแปลงนั้นอย่างสะอาดใน diff, ปรับรอบการประเมินได้ละเอียดในระดับแต่ละ rule และ ใช้ทรัพยากร monitoring ได้อย่างมีประสิทธิภาพมากขึ้น


ปิดท้าย

เป้าหมายแรกเริ่มนั้นเรียบง่ายมาก คือ ทำให้สร้าง Alert ได้ง่ายขึ้น เข้าใจได้ในทันทีเมื่อได้รับ และทำให้ชัดเจนว่าใครเป็นผู้รับผิดชอบ

ในการปรับปรุงรอบแรก ได้รวบรวมการนิยาม Alert ให้เป็น IaC, ทำให้ข้อความ Slack และเส้นทางการส่งต่อเป็นมาตรฐาน, เชื่อม mention ของผู้รับผิดชอบเข้ากับ CODEOWNERS และจัดระเบียบ lifecycle ของ Slack/PagerDuty ผ่าน proxy

ปัญหาที่ปรากฏขึ้นระหว่างดำเนินงานถูกนำไปปรับแก้ในการปรับปรุงรอบที่สอง โดยรวม Alert ที่หลั่งไหลมาจาก rule เดียวกันให้แสดงเป็นหนึ่งเดียว ลดนิยามที่ซ้ำกันด้วย template และ matrix ทำให้สามารถเริ่มตรวจสอบและบรรเทาปัญหาได้จากในข้อความ Slack และยังเตรียมกลไกไว้ให้รู้ตัวได้เมื่อระบบมอนิเตอร์เองหยุดทำงาน

ด้วยเหตุนี้ งานสร้าง Alert การส่ง Alert และการรับมือกับมันจึงทำได้ง่ายขึ้นกว่าเดิม

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


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

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น