Sign in

Part of 10306343 การจัดการโครงสร้างพื้นฐานและการส่งมอบซอฟต์แวร์อย่างต่อเนื่อง (Development Operations: DevOps)

2026-10-20 · updated 2026-10-29 · 27 min read

บทที่ 11 Observability พื้นฐาน: SLI, SLO, Prometheus และประตูตรวจหลัง deploy

ShareFacebookXLinkedInLINE

แผนบริหารการสอนประจำบทที่ 11

หัวข้อเนื้อหา

  • 11.1 Metrics, Logs และ Traces
  • 11.2 SLI และ SLO
  • 11.3 Error budget
  • 11.4 Prometheus และ Grafana
  • 11.5 นิยามตัวตั้ง ตัวหาร และช่วงเวลาก่อนคำนวณ
  • 11.6 counter กับจำนวนเหตุการณ์ในหน้าต่างเวลา
  • 11.7 เลือก label ที่ช่วยวิเคราะห์โดยไม่ทำข้อมูลระเบิด
  • 11.8 healthcheck ผ่านยังอาจละเมิด SLO
  • ปฏิบัติการที่ 11 วัดระบบด้วยตัวเลขและตั้งประตูตรวจหลัง deploy (Lab A–D)

วัตถุประสงค์เชิงพฤติกรรม

เมื่อศึกษาบทนี้และทำปฏิบัติการครบถ้วนแล้ว ผู้เรียนสามารถปฏิบัติได้ตามผลลัพธ์การเรียนรู้ต่อไปนี้ โดยแต่ละข้อมีงานที่ใช้วัดผลกำกับไว้

รหัสผลลัพธ์การเรียนรู้วัดจากCLO ของรายวิชา
11.1รัน Prometheus และ Grafana จากไฟล์ที่ให้มา และสร้าง panel แสดงจำนวน error โดยใช้เอกสารทางการงานข้อ 1CLO3
11.2เพิ่ม healthcheck ใน workflow deploy ให้ถือว่าสำเร็จเมื่อแอปตอบ health เท่านั้นงานข้อ 2CLO2

CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):

  • CLO2 สามารถกำหนดค่าและใช้งาน DevOps workflow ที่กำหนดมา เพื่อให้กระบวนการพัฒนา–ทดสอบ–ส่งมอบซอฟต์แวร์เป็นไปอย่างถูกต้องและมีประสิทธิภาพ
  • CLO3 ใช้เครื่องมือดิจิทัลอย่างเหมาะสมเพื่อพัฒนาระบบ และสามารถเรียนรู้และปรับตัวด้วยตนเองจากแหล่งข้อมูลที่เชื่อถือได้

บทนี้จับคู่ Prometheus และ Grafana กับ CLO3 เพราะเป็นเครื่องมือใหม่ที่ต้องเรียนจากเอกสารทางการด้วยตนเอง และจับคู่ประตูตรวจหลัง deploy กับ CLO2 เพราะเป็นการกำหนดค่า workflow ส่งมอบที่มีอยู่ให้ตัดสินผลได้ถูกต้อง

วิธีสอนและกิจกรรมการเรียนการสอน

  1. บรรยายแนวคิดตามหัวข้อ 11.1–11.4 โดยเริ่มจากคำถามว่า "ถ้าผู้ใช้ถามว่าระบบของทีมดีแค่ไหน จะตอบเป็นตัวเลขอะไร" และใช้คำถามตรวจความเข้าใจท้ายหัวข้อเป็นกิจกรรมอภิปรายเป็นคู่
  2. สาธิตการหยุดแอปชั่วคราวแล้วนับ request ที่ได้ 502 จาก log ของ Nginx เพื่อคำนวณ SLI ด้วยคำสั่งจากบทที่ 1
  3. สาธิตหน้า Targets ของ Prometheus และการค้นด้วย PromQL ก่อนให้ผู้เรียนสร้าง panel เอง
  4. ปฏิบัติการ Lab A–D บน repository ทีม แต่ละ Lab ประกอบด้วยส่วนฝึกตามตัวอย่างและส่วนที่ผู้เรียนต้องตัดสินใจเอง
  5. มอบหมายแบบฝึกหัดท้ายบท Learning Log ครั้งที่ 11 และการศึกษาค้นคว้าเพิ่มเติม

สื่อการเรียนการสอน

  • เอกสารประกอบการสอนบทที่ 11 (บทความนี้) และหน้ารวมรายวิชา 10306343
  • repository ทีม devops-app ที่ผ่าน M9 จากบทที่ 10 และเครื่องปลายทางของทีมที่รันระบบชุด devops-prod
  • ไฟล์ที่ให้มาในบทนี้: src/metrics.js, compose.monitoring.yaml, monitoring/prometheus.yml และ monitoring/grafana/datasources.yml
  • หนังสือ Site Reliability Engineering บทที่ 4, The Site Reliability Workbook บทที่ 2 และเอกสารทางการของ Prometheus, Grafana และ OpenTelemetry

การวัดผลและการประเมินผล

กำหนดส่ง: ก่อนเริ่มเรียนบทที่ 13 เพื่อเว้นระยะหนึ่งบท

ข้องานหลักฐานผลลัพธ์CLO
1M10 dashboard แสดง errorลิงก์ pull request ของตนเองที่ merge แล้ว (Lab B หรือ D) · ภาพหน้าจอ dashboard ของตนเองที่มี panel แสดง request ที่ตอบ 4xx และ 5xx แยกตาม status พร้อมเห็นคำค้น PromQL · บันทึกรายบุคคลไม่เกินครึ่งหน้าว่าใช้เอกสารทางการหน้าใดสร้าง panel11.1CLO3
2ประตูตรวจ health หลัง deployscripts/deploy.sh บน main ที่ตรวจ health ผ่าน proxy · ลิงก์ run ของ CD ที่ล้มที่ขั้น deploy เพราะ health ล้ม (Lab D.3) · ลิงก์ run ที่ผ่านหลังแก้11.2CLO2
3Learning Log ครั้งที่ 11ตอบ 4 หัวข้อ: สิ่งที่เรียนในบทนี้ · คำสั่งหรือข้อความ error ที่พบและแก้อย่างไร · แหล่งที่ใช้ค้นหาคำตอบ (ระบุลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง) · ตัวเลข SLI ของทีมจาก Lab A และความหมาย—บันทึกสะท้อนการเรียนรู้ ไม่มีหมวดคะแนน portfolio แยก

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

เกณฑ์ให้คะแนน

ข้อผ่านยังไม่ผ่าน
2scripts/deploy.sh ตรวจ health ผ่าน proxy และเทียบเวอร์ชันกับ tag · run ที่ล้มล้มที่ขั้น Deploy with Compose พร้อมข้อความ deploy ล้ม: · run หลังแก้ผ่านและหน้าเว็บแสดงเวอร์ชันใหม่ไม่มี run ที่ล้ม หรือ run ล้มที่ขั้นอื่น หรือสคริปต์ยังจบด้วย exit code 0 เมื่อ health ล้ม
3ตอบครบ 4 หัวข้อ และมีลิงก์เอกสารทางการขาดหัวข้อ หรือไม่มีแหล่งอ้างอิง

ข้อ 1 ใช้รายการตรวจ (checklist) CLO3 ผ่านเมื่อครบทุกข้อ

ลำดับรายการ
1หน้า Targets ของ Prometheus แสดง job devops-app สถานะ UP
2panel ใช้คำค้น PromQL ที่อ้าง http_requests_total และกรองเฉพาะ status กลุ่ม 4xx และ 5xx
3panel แสดงค่าแยกตาม status และมีชื่อ panel ที่สื่อความหมาย
4บันทึกระบุลิงก์เอกสารทางการของ Prometheus หรือ Grafana อย่างน้อย 1 หน้า และอธิบายด้วยคำของตนเองว่าใช้ส่วนใด

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

การคิดคะแนนฉบับปัจจุบัน

มติผู้สอน 28 ต.ค. 2569: เข้าเรียน 10% · ใบงานบทที่ 1–13 รวม 40% · FP 20% · FW 20% · โครงงาน 10% ส่วนบทที่ 14–15 ฝึกเพื่อรับข้อเสนอแนะ ไม่นับคะแนน

ใบงาน C11-WS01 มีคะแนนดิบเต็ม 20 คะแนน แยกตามผลลัพธ์ด้านล่าง ระบบนำไปรวมในหมวดใบงาน 40% โดยไม่เพิ่มคะแนน milestone, portfolio หรือจุดตรวจเป็นหมวดแยก

ผลลัพธ์หลักฐานตามงานส่งคะแนนดิบเต็ม
LLO11.1งานข้อ 110
LLO11.2งานข้อ 210

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

ใช้เกณฑ์ความถูกต้อง เหตุผล และหลักฐานที่ระบุในงานแต่ละข้อประกอบการประเมินราย LLO งานเตรียมพื้นและ Learning Log ที่ไม่มีรหัส LLO ใช้ให้ข้อเสนอแนะ ไม่เพิ่มคะแนนดิบให้ใบงาน

บทนำ

หลังบทที่ 10 pipeline ของทีมทดสอบ สแกน และ deploy ได้อัตโนมัติ แต่ทีมยังตอบไม่ได้ว่าระบบที่ deploy แล้ว "ดี" แค่ไหน และ workflow ถือว่า deploy สำเร็จทันทีที่คำสั่งจบ แม้ผู้ใช้จะเปิดหน้าเว็บแล้วได้ 502 ก็ตาม บทนี้วัดระบบด้วยตัวเลข เริ่มจากคำนวณ SLI จาก log ของ Nginx ด้วยคำสั่งจากบทที่ 1 แล้วให้แอปเปิดตัวนับ request ที่ Prometheus ดึงไปเก็บและ Grafana แสดงเป็นกราฟ ปิดท้ายด้วยประตูตรวจหลัง deploy ที่ทำให้ run ล้มเมื่อแอปไม่ตอบ health ผ่าน proxy

ความสำคัญ: การปรับปรุงระบบต้องเริ่มจากการวัด ทีมที่ไม่มีตัวเลขจะรู้ว่าระบบมีปัญหาเมื่อผู้ใช้แจ้งเท่านั้น SLI และ SLO ทำให้ทีมคุยเรื่องความน่าเชื่อถือด้วยตัวเลขเดียวกัน และ error budget ช่วยตัดสินว่าเมื่อใดควรเร่งออกฟีเจอร์ เมื่อใดควรหยุดแก้ความเสถียร

ความเชื่อมโยงกับบทอื่น: บทนี้ใช้คำสั่ง grep, sort และ uniq -c จากบทที่ 1 healthcheck ของ Compose จากบทที่ 7 scripts/deploy.sh และ workflow CD จากบทที่ 8 และ Nginx กับปัญหา 502 จากบทที่ 9 ผลผลิตของบทนี้ถูกใช้ต่อตามตารางต่อไปนี้

สิ่งที่ได้จากบทนี้ใช้ต่อในบท
ประตูตรวจ health ใน scripts/deploy.sh12 (pipeline ครบสายและการ rollback) · 13 (Capstone)
/metrics, Prometheus และ dashboard12 (สังเกตเวอร์ชันพังระหว่าง rollback) · 13 (AI วิเคราะห์ log)
SLI ของทีมจาก Lab A12 (เทียบก่อนและหลัง rollback)

ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์

ความรู้พื้นฐาน: การค้นหาและนับใน log จากบทที่ 1 Compose หลายบริการและ healthcheck จากบทที่ 7 การ deploy ด้วย tag จากบทที่ 8 และ reverse proxy จากบทที่ 9

เครื่องมือ: เครื่องที่มี Docker Engine และ Docker Compose plugin repository ~/devops-app ที่ตรงกับ main ล่าสุด เบราว์เซอร์สำหรับเปิด Prometheus และ Grafana และเครื่องปลายทางของทีม พอร์ต 9090 และ 3001 ต้องว่างบนเครื่องพัฒนา

รุ่นซอฟต์แวร์ที่ใช้ทดสอบ:

ซอฟต์แวร์รุ่นที่ทดสอบ
Prometheusimage จำลองชื่อ prom/prometheus:v3.7.3 ที่ผู้เขียนสร้างจากไฟล์โปรแกรม Prometheus 3.7.3 ในหน้า Releases ของโครงการ ทดสอบเมื่อ 19 ต.ค. 2569
Grafanagrafana/grafana:12.2.0 ยังไม่ได้ทดสอบ
Docker Engine และ Docker Compose29.8.2 และ v5.5.1
image อื่นimage จำลอง node:22-slim, postgres:16 และ nginx:1.28 เดียวกับบทที่ 6–10
แอปdevops-app 0.4.1 → 0.5.0
⚠️ข้อจำกัดของการทดสอบในบทนี้

สภาพแวดล้อมที่ใช้เขียนบทความดึง image จาก Docker Hub ไม่ได้และดาวน์โหลด Grafana ไม่ได้ ผู้เขียนจึงสร้าง image จำลองของ Prometheus จากไฟล์โปรแกรมรุ่น 3.7.3 โดยตั้งคำสั่งเริ่มต้นให้อ่าน /etc/prometheus/prometheus.yml ตามที่ใช้ในบทความ ผลของ /metrics, หน้า Targets, คำค้น PromQL, การคำนวณ SLI จาก log และประตูตรวจ health ได้จากการรันจริง ส่วนขั้นตอนทั้งหมดที่ใช้ Grafana ผู้เขียนยังไม่ได้ทดสอบ รวมถึงไฟล์ตั้งค่าแหล่งข้อมูลอัตโนมัติ ชื่อผู้ใช้เริ่มต้น และขั้นตอนสร้าง panel ชื่อ image และ tag ของ Prometheus และ Grafana ยังไม่ได้ตรวจกับเอกสารติดตั้งของแต่ละโครงการ

ค่าใช้จ่ายและข้อมูลลับ: Prometheus และ Grafana รุ่นโอเพนซอร์สใช้ได้โดยไม่มีค่าใช้จ่าย บทนี้รันทั้งสองโปรแกรมบนเครื่องพัฒนาเท่านั้น และผูกพอร์ตไว้กับ 127.0.0.1 เพื่อไม่ให้เครื่องอื่นในเครือข่ายเข้าถึงได้ ให้เปลี่ยนรหัสผ่านผู้ดูแลของ Grafana ตั้งแต่เข้าใช้ครั้งแรก และห้ามแนบรหัสผ่านนั้นในภาพหน้าจอ

11.1 Metrics, Logs และ Traces

OpenTelemetry ซึ่งเป็นโครงการมาตรฐานด้านการสังเกตระบบ ระบุชนิดของข้อมูล (signal) ไว้ 5 ชนิด ได้แก่ Traces, Metrics, Logs, Baggage และ Profiles (OpenTelemetry: Signals) บทนี้สอนแกนหลักสองชนิด คือ metrics และ logs

signalคืออะไรตัวอย่างในระบบของทีม
Logsบันทึกเหตุการณ์ทีละรายการพร้อมรายละเอียดaccess log และ error log ของ Nginx ที่เห็นในบทที่ 9
Metricsตัวเลขที่วัดเป็นระยะและเก็บเป็นอนุกรมเวลาจำนวน request แยกตาม status ที่ /metrics ใน Lab B
Tracesเส้นทางของ request หนึ่งรายการผ่านหลายบริการยังไม่ใช้ในรายวิชา (เลือกทำเป็นงานต่อยอดได้)

ระดับที่ 1: log ตอบคำถามว่า "เกิดอะไรขึ้นกับ request นี้" เช่น บรรทัด connect() failed (111: Connection refused) ในบทที่ 9 ส่วน metric ตอบคำถามว่า "เกิดบ่อยแค่ไหน" เช่น 3 request จาก 39 request ได้ 502 metric จึงเหมาะกับการเฝ้าดูภาพรวมและสร้างกราฟ ส่วน log เหมาะกับการหาสาเหตุเมื่อรู้แล้วว่ามีปัญหา

ระดับที่ 2: metric ใช้พื้นที่น้อยกว่า log มาก เพราะเก็บเพียงตัวเลขต่อช่วงเวลา ไม่ว่ามี request กี่รายการ แต่ต้องตัดสินใจล่วงหน้าว่าจะนับอะไรและแยกตามอะไร ถ้าแอปนับเพียงจำนวน request ทั้งหมด จะตอบไม่ได้ว่ามี error กี่รายการ

ระดับที่ 3: สามชนิดใช้ร่วมกัน ขั้นตอนตรวจปัญหาที่พบบ่อยคือ dashboard ของ metric แสดงว่า error เพิ่มขึ้นตอน 10:05 จากนั้นค้น log ช่วงเวลานั้นเพื่อหาข้อความ error และถ้ามี trace จะเห็นว่า request ช้าหรือล้มที่บริการใด การเลือกเก็บข้อมูลจึงควรเริ่มจากคำถามที่ทีมต้องตอบให้ได้ ไม่ใช่เก็บทุกอย่าง

คำถามตรวจความเข้าใจ 11.1: จากเหตุการณ์ 502 หลัง deploy ในบทที่ 9 ระบุว่า metric จะบอกอะไร log จะบอกอะไร และเหตุใดการมีเพียง metric อย่างเดียวจึงหาสาเหตุไม่ได้

11.2 SLI และ SLO

หนังสือ Site Reliability Engineering ของ Google นิยามคำสามคำที่ใช้คุยเรื่องความน่าเชื่อถือ (Beyer et al., Ch. 4)

คำความหมายตัวอย่าง
SLI (Service Level Indicator)ค่าเชิงปริมาณที่นิยามอย่างระมัดระวังของระดับบริการสัดส่วน request ที่ไม่ใช่ 5xx
SLO (Service Level Objective)ค่าเป้าหมายที่วัดด้วย SLISLI ต้องไม่ต่ำกว่า 99% ในรอบ 4 สัปดาห์
SLA (Service Level Agreement)สัญญากับผู้ใช้ที่ระบุผลเมื่อไม่ได้ตามเป้าไม่ใช้ในรายวิชา

The Site Reliability Workbook ให้สูตรของ SLI เป็นสัดส่วนของเหตุการณ์ที่ดีต่อเหตุการณ์ทั้งหมด (Beyer et al., Workbook Ch. 2)

TEXT
SLI = (จำนวนเหตุการณ์ที่ดี ÷ จำนวนเหตุการณ์ทั้งหมด) × 100%

ผลการทดสอบใน Lab A ซึ่งหยุดแอปชั่วคราวระหว่างส่ง request ผ่าน Nginx ได้ request ทั้งหมด 39 รายการ เป็น 5xx 3 รายการ จึงได้ SLI = 36 / 39 × 100% ≈ 92.3%

ระดับที่ 1: การนิยาม "เหตุการณ์ที่ดี" ต้องชัดเจนก่อนนับ บทนี้นิยามว่า request ที่ดีคือ request ที่ไม่ได้ status กลุ่ม 5xx เพราะ 5xx หมายถึงฝั่งระบบผิดพลาด ส่วน 404 จาก /api/items/99 เป็นการขอสิ่งที่ไม่มีอยู่ ซึ่งระบบตอบถูกต้องแล้ว

ระดับที่ 2: ตัวเลข SLI ขึ้นกับจุดที่วัด ถ้านับจาก /metrics ของแอป จะไม่เห็น 502 เลย เพราะตอนที่ Nginx ตอบ 502 แอปไม่ได้รับ request นั้น การวัดที่ proxy จึงใกล้ประสบการณ์ของผู้ใช้มากกว่า นี่คือเหตุผลที่ Lab A ใช้ log ของ Nginx และประตูตรวจใน Lab D ตรวจผ่าน proxy

ระดับที่ 3: SLO ที่ 100% ไม่ใช่เป้าที่ดี เพราะการเปลี่ยนแปลงระบบทุกครั้งมีความเสี่ยง เป้า 100% จึงเท่ากับห้ามเปลี่ยนแปลง ขณะที่ผู้ใช้ยังพบปัญหาจากส่วนที่อยู่นอกระบบของทีมอยู่ดี เช่น เครือข่ายของผู้ใช้เอง การเลือก SLO ควรเริ่มจากระดับที่ผู้ใช้ยอมรับได้และทีมรักษาได้จริง

คำถามตรวจความเข้าใจ 11.2: ในหนึ่งวันระบบรับ 12,000 request ได้ 5xx 60 รายการ และ 404 300 รายการ คำนวณ SLI ตามนิยามของบทนี้ และบอกว่าผ่าน SLO 99.5% หรือไม่

11.3 Error budget

Error budget คือสัดส่วนของเหตุการณ์ที่ "ยอมให้ล้มได้" ภายใต้ SLO คำนวณจากส่วนที่เหลือของ SLO (Beyer et al., Workbook Ch. 2)

TEXT
Error budget = 100% − SLO

ตัวอย่างจาก Workbook: SLO 99.9% ในหน้าต่าง 4 สัปดาห์ ถ้าบริการรับ 3,000,000 request ในช่วงนั้น error budget คือ 0.1% ของ 3,000,000 = 3,000 error

ระดับที่ 1: error budget แปลง SLO เป็นจำนวนที่นับได้ ทีมที่มี SLO 99% และรับ 10,000 request ต่อสัปดาห์ ใช้ error ได้ 100 รายการต่อสัปดาห์ ถ้า deploy ครั้งหนึ่งทำให้ได้ 502 80 รายการ แสดงว่าใช้งบไปแล้ว 80%

ระดับที่ 2: error budget ใช้ตัดสินใจร่วมกันระหว่างคนพัฒนาฟีเจอร์และคนดูแลระบบ เมื่องบยังเหลือ ทีมออกเวอร์ชันใหม่ได้ตามปกติ เมื่องบใกล้หมด ทีมควรชะลอการเปลี่ยนแปลงและแก้ความเสถียรก่อน การตัดสินจึงอิงตัวเลข ไม่ใช่ความรู้สึก

ระดับที่ 3: หน้าต่างเวลาของ SLO มีผลต่อการตัดสินใจ หน้าต่างสั้น (เช่น 1 วัน) ตอบสนองเร็วแต่แกว่งตามเหตุการณ์ครั้งเดียว หน้าต่างยาว (เช่น 4 สัปดาห์) นิ่งกว่าแต่ปัญหาครั้งใหญ่จะกินงบไปนาน ระบบของทีมในรายวิชามีผู้ใช้น้อย จำนวน request จริงจึงน้อยเกินกว่าจะให้ตัวเลขที่มีความหมายทางสถิติ ตัวเลขในบทนี้ใช้เพื่อฝึกคิด ไม่ใช่เพื่อตัดสินคุณภาพของระบบจริง

คำถามตรวจความเข้าใจ 11.3: ทีมตั้ง SLO 99.5% ในรอบ 4 สัปดาห์ และคาดว่าจะรับ 40,000 request หลังผ่านไป 2 สัปดาห์มี 5xx แล้ว 150 รายการ คำนวณ error budget ทั้งรอบ ส่วนที่ใช้ไปแล้วเป็นร้อยละ และเสนอว่าทีมควรออกเวอร์ชันใหม่ในสัปดาห์ที่ 3 หรือไม่

11.4 Prometheus และ Grafana

Prometheus เป็นชุดเครื่องมือ monitoring และ alerting แบบโอเพนซอร์สในมูลนิธิ CNCF เก็บข้อมูลเป็นอนุกรมเวลา (time series) ที่ระบุด้วยชื่อ metric และ label แบบคู่คีย์–ค่า และดึง (pull) ข้อมูลจากเป้าหมายผ่าน HTTP (Prometheus: Overview) Grafana อ่านข้อมูลจาก Prometheus แล้วแสดงเป็น panel และ dashboard

ผลการทดสอบ /metrics ของแอปหลัง Lab B (ส่ง request ไปที่ / 20 ครั้ง /api/items/99 5 ครั้ง และ path ที่ไม่มีอยู่ 1 ครั้ง)

TEXT
# HELP http_requests_total Total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="GET",route="/health",status="200"} 2
http_requests_total{method="GET",route="/metrics",status="200"} 1
http_requests_total{method="GET",route="/",status="200"} 20
http_requests_total{method="GET",route="/api/items/:id",status="404"} 5
http_requests_total{method="GET",route="unmatched",status="404"} 1
ส่วนความหมาย
http_requests_totalชื่อ metric
{method="GET",route="/",status="200"}label ที่แยกอนุกรมเวลา หนึ่งชุดของ label คืออนุกรมเวลาหนึ่งเส้น
20ค่าปัจจุบัน
# TYPE ... counterชนิดของ metric

Prometheus แบ่งชนิดของ metric ไว้หลายแบบ สองแบบพื้นฐานคือ counter ที่ค่าเพิ่มขึ้นอย่างเดียว (หรือกลับเป็นศูนย์เมื่อเริ่มโปรแกรมใหม่) เหมาะกับการนับ request และ error และ gauge ที่ค่าขึ้นลงได้ เหมาะกับค่าอย่างหน่วยความจำที่ใช้อยู่ (Prometheus: Metric types)

ระดับที่ 1: ค่าของ counter เพียงค่าเดียวบอกได้น้อย เพราะเป็นยอดสะสมตั้งแต่แอปเริ่มทำงาน สิ่งที่มีประโยชน์คืออัตราการเพิ่มในช่วงเวลา Prometheus มีภาษาค้นชื่อ PromQL ที่คำนวณได้ เช่น increase(http_requests_total[5m]) ให้ค่าที่เพิ่มขึ้นใน 5 นาทีล่าสุด

ระดับที่ 2: label route ใช้รูปแบบของเส้นทาง (/api/items/:id) ไม่ใช่ path จริง (/api/items/99) เพราะทุกชุดของ label เป็นอนุกรมเวลาแยกกัน ถ้าใช้ path จริง ผู้ใช้ที่ขอ /api/items/1 ถึง /api/items/100000 จะสร้างอนุกรมเวลาแสนเส้นและทำให้ Prometheus ใช้หน่วยความจำมากโดยไม่จำเป็น request ที่ไม่ตรงกับเส้นทางใดถูกรวมไว้ใต้ route="unmatched" ด้วยเหตุผลเดียวกัน ส่วน /health ที่นับได้ 2 ครั้งทั้งที่ส่งเพียงครั้งเดียว มาจาก healthcheck ของ Compose ที่เรียกทุก 10 วินาที

ระดับที่ 3: ผลการทดสอบคำค้น sum by (status) (increase(http_requests_total{status=~"4..|5.."}[5m])) หลังส่ง request ที่ได้ 404 เพิ่ม 7 ครั้ง ได้ค่า 7.88... ไม่ใช่ 7 พอดี เพราะ Prometheus ประมาณค่าจากตัวอย่างที่ดึงทุก 15 วินาทีให้ครอบคลุมทั้งช่วงเวลา (พฤติกรรมนี้สังเกตจากการทดสอบ เอกสารของฟังก์ชัน increase ยังไม่อยู่ในรายการแหล่งอ้างอิง) ตัวเลขบน dashboard จึงใช้ดูแนวโน้ม ไม่ใช่ใช้นับทีละรายการ

คำถามตรวจความเข้าใจ 11.4: สมาชิกคนหนึ่งเสนอเพิ่ม label user_id ใน http_requests_total เพื่อดูว่าผู้ใช้คนใดเจอ error บ่อย วิเคราะห์ข้อเสนอนี้โดยอ้างหลักการของ label และเสนอทางอื่นที่ตอบคำถามเดียวกันได้

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

11.5 นิยามตัวตั้ง ตัวหาร และช่วงเวลาก่อนคำนวณ

SLI ที่เป็นสัดส่วนต้องกำหนดว่าเหตุการณ์ใดนับเป็นทั้งหมด เหตุการณ์ใดถือว่าดี และวัดที่จุดใด แนวทาง Google SRE แยกสิ่งที่ต้องการวัดจากวิธีนำไปเก็บจริง เพื่อให้ค่าที่ได้สะท้อนประสบการณ์ของผู้ใช้ ไม่ใช่เพียงความสะดวกของเครื่องมือ (Google SRE: Implementing SLOs)

ข้อมูลต่อไปนี้เป็นข้อมูลสมมติ: ในช่วงหนึ่งชั่วโมงมีคำขอที่เข้าเกณฑ์ 10,000 ครั้ง ตอบสำเร็จตามนิยาม 9,940 ครั้ง และล้มเหลว 60 ครั้ง ถ้า SLI คือสัดส่วนสำเร็จ ค่าจะเป็น 9,940 / 10,000 = 99.4% หาก SLO สำหรับช่วงเดียวกันกำหนด 99.5% จะอนุญาตเหตุการณ์ไม่ดี 50 ครั้ง จึงเกิน budget 10 ครั้ง ตัวเลขนี้เป็น request-based budget ไม่ใช่จำนวนวินาทีที่ระบบหยุด

ตัวอย่างที่ไม่ถูกต้องคือใช้ 60 หาร 9,940 แล้วรายงานเป็นอัตราความล้มเหลว ตัวหารของนิยามนี้ต้องเป็นคำขอทั้งหมดที่เข้าเกณฑ์ อีกความผิดคือรวมคำขอ /metrics และ /health ที่ระบบยิงถี่เข้ากับคำขอผู้ใช้โดยไม่อธิบาย หากคำขอตรวจทั้งหมดผ่านแต่คำขอใช้งานจริงล้ม สัดส่วนรวมอาจดูดีเกินประสบการณ์ผู้ใช้ ต้องประกาศการรวมและการยกเว้นให้ชัดก่อนดูผล

การเฉลี่ยเปอร์เซ็นต์โดยไม่ถ่วงน้ำหนักก็อาจผิด สมมติช่วงแรกสำเร็จ 90 จาก 100 ครั้งหรือ 90% ช่วงที่สองสำเร็จ 990 จาก 1,000 ครั้งหรือ 99% ค่าเฉลี่ยสองเปอร์เซ็นต์เป็น 94.5% แต่สัดส่วนรวมจริงคือ 1,080 / 1,100 หรือประมาณ 98.18% เพราะจำนวนเหตุการณ์ต่างกัน วิธีรวมที่สอดคล้องกับนิยามคือรวมตัวตั้งกับตัวหารก่อนคำนวณ

ถ้าไม่มีคำขอเลย ตัวหารเป็นศูนย์ ไม่ควรประกาศ 100% โดยไม่มีนโยบาย ข้อมูลไม่พอให้ประเมินสัดส่วนเป็นคนละกรณีกับตรวจแล้วสำเร็จทุกครั้ง เช่นเดียวกับไม่มี log เพราะตัวเก็บข้อมูลล้ม ไม่ใช่หลักฐานว่าระบบไม่มี error

11.6 counter กับจำนวนเหตุการณ์ในหน้าต่างเวลา

counter เป็นค่าสะสมที่เพิ่มตามเหตุการณ์และอาจเริ่มใหม่เมื่อกระบวนการ restart การนำค่าดิบมาหารกันอาจไม่ตอบคำถามว่าในห้านาทีล่าสุดเกิดอะไรขึ้น Prometheus มีฟังก์ชันอย่าง rate() และ increase() สำหรับวิเคราะห์ counter ในช่วงเวลา โดยพิจารณาการ reset ตามกติกาของฟังก์ชัน (Prometheus: Query functions)

ตัวอย่างต่อไปนี้กำหนด metric สมมติ http_requests_total ที่มี label code และใช้เฉพาะคำขอซึ่งตรงนิยาม SLI ชื่อนี้เป็นตัวอย่างประกอบ ไม่ใช่ข้อรับรองว่าแอปตั้งต้นได้ติดตั้ง metric นี้ไว้แล้ว

PROMQL
# สัดส่วน 5xx ในช่วง 5 นาที เมื่อมี traffic และ series ครบตามนิยาม
sum(rate(http_requests_total{code=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))

ตัวอย่างที่ผิดคือเรียกอัตราจากค่าดิบสะสมเมื่อเริ่มรันหลายชั่วโมง แล้วสรุปว่าปัญหาห้านาทีล่าสุดมีขนาดเท่ากัน ค่าจากอดีตอาจกลบ spike ใหม่ อีกตัวอย่างคือรวม counter หลาย instance ก่อนคำนวณ rate จนการ reset ของแต่ละตัวแยกไม่ออก โดยทั่วไปควรคำนวณ rate ต่อ series แล้วรวมตามคำถามที่ต้องการ

สูตรข้างต้นยังมีข้อจำกัด: ถ้า numerator ไม่มี series หรือไม่มี traffic ผลอาจไม่เป็นตัวเลขที่คาด ไม่ควรแทนข้อมูลหายด้วยศูนย์อย่างไม่แยกสาเหตุ ต้องมีการตรวจการ scrape และความครบถ้วนของชุดข้อมูลประกอบ หากแอปมี 4xx ที่นับเป็นเหตุการณ์ไม่ดีตามสัญญา สูตรที่ดูเฉพาะ 5xx ก็ไม่ครอบคลุม ต้องแก้นิยามหรือ query ให้ตรงกัน

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

11.7 เลือก label ที่ช่วยวิเคราะห์โดยไม่ทำข้อมูลระเบิด

label แยก time series ตามมิติที่ต้องการ เช่น method และ route template แต่ทุกชุดค่าที่ต่างกันเพิ่ม series ใหม่ การใช้ user ID, email, token หรือ URL ที่มีรหัสไม่จำกัดเป็น label อาจสร้าง cardinality สูงและอาจเปิดข้อมูลอ่อนไหว แนวทาง Prometheus ให้จำกัดมิติและเพิ่ม label ตามกรณีใช้ที่ชัดเจน (Prometheus: Instrumentation)

ข้อมูลสมมติช่วยคำนวณขนาดได้: มี 3 methods, 10 route templates, 5 กลุ่มสถานะ และ 4 instances จะมีได้ถึง 3 × 10 × 5 × 4 = 600 ชุดค่าหากทุก combination เกิดขึ้น เมื่อเพิ่ม user ID อีก 10,000 ค่า ขอบเขตเชิงคูณกลายเป็น 6,000,000 ชุด ตัวเลขนี้เป็นการแสดงกลไกการเพิ่มจำนวน ไม่ใช่ผลวัดการใช้หน่วยความจำจริง

ตัวอย่างที่ผิดคือใช้ path เต็ม /orders/12345 เป็น route label ทั้งที่ทุกคำขอมีเลขใหม่ แนวทางที่เหมาะสมกว่าคือ template /orders/:id แล้วเก็บรายละเอียดเฉพาะคำขอใน log ที่มีนโยบายเข้าถึงและการปิดข้อมูลส่วนบุคคล การใช้ metrics กับ logs จึงควรแบ่งบทบาทตามคำถาม ไม่พยายามเก็บรายละเอียดทุกอย่างใน labels

อีกตัวอย่างคือ dashboard แสดง error count โดยไม่มีปริมาณคำขอ ช่วงที่ผู้ใช้เพิ่มขึ้นอาจมี error มากขึ้นแต่สัดส่วนลดลง หรือสัดส่วนเพิ่มขึ้นมากแม้จำนวนไม่สูง การแสดงทั้งจำนวน อัตรา latency และความครบถ้วนของข้อมูลช่วยลดการตีความผิดจากกราฟตัวเดียว

11.8 healthcheck ผ่านยังอาจละเมิด SLO

healthcheck เป็นการตรวจตามเงื่อนไขเฉพาะจุด ส่วน SLO อธิบายคุณภาพบริการในช่วงเวลา สมมติ /health ตอบ 200 ทุกครั้ง แต่ endpoint หลักตอบช้าเกินเกณฑ์ 20% ของคำขอ ระบบอาจผ่าน healthcheck แต่ยังไม่ผ่าน latency SLO จึงไม่ควรใช้สถานะของ probe ตัวเดียวแทนประสบการณ์ทุกเส้นทาง

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

การตั้ง alert ควรนำไปสู่การกระทำที่ชัดเจน เช่น ตรวจการเปลี่ยน release ล่าสุดและอัตรา error ไม่แจ้งทุกความผันผวนจนทีมละเลย สัญญาณ CPU สูงอาจใช้ช่วยหาสาเหตุ แต่การบอกว่าผู้ใช้ได้รับผลกระทบต้องมีหลักฐานที่เชื่อมกับการบริการด้วย การเลือกสัญญาณจึงเริ่มจากคำถาม “เมื่อแจ้งแล้วต้องทำอะไร” และ “ข้อมูลใดยืนยันว่าปัญหาหายแล้ว”

หลักฐานที่ส่งต่อไปบทที่ 12 ควรมีนิยาม SLI, ช่วงเวลา, query, แหล่งข้อมูล, release ที่เกี่ยวข้อง และข้อจำกัด เช่น ข้อมูลหายหรือจำนวนคำขอน้อย เพื่อให้การตัดสิน rollback ใช้ข้อมูลที่ตรวจความหมายได้ ไม่ใช่เพียงภาพกราฟที่ไม่มีหน่วยและช่วงเวลา

กรณีอธิบายต่อเนื่อง: dashboard สองใบที่ให้ข้อสรุปต่างกัน

กำหนดข้อมูลสังเคราะห์ของบริการในสองช่วง ช่วงแรกคำขอทั้งหมด 1,000 ครั้ง ล้มเหลว 10 ครั้ง ช่วงที่สองคำขอทั้งหมด 9,000 ครั้ง ล้มเหลว 180 ครั้ง dashboard ก แสดงจำนวน error เพิ่มจาก 10 เป็น 180 ส่วน dashboard ข แสดงอัตรา error เพิ่มจาก 1% เป็น 2% ทั้งสองถูกตามคำถามของตน แต่คำอธิบาย “ระบบแย่ลง 18 เท่า” ยังไม่ครบ เพราะ traffic เพิ่ม 9 เท่า ขณะที่อัตราความล้มเหลวเพิ่ม 2 เท่า

ถ้าต้องรายงานผลรวมของสองช่วง ความสำเร็จเท่ากับ 990 + 8,820 = 9,810 จากทั้งหมด 10,000 หรือ 98.1% อัตรา error เท่ากับ 1.9% ไม่ใช่ค่าเฉลี่ย 1% กับ 2% ที่ได้ 1.5% การรวมตัวตั้งและตัวหารก่อนคำนวณรักษาน้ำหนักตามจำนวนคำขอ และทำให้ผู้อ่านตรวจย้อนจากข้อมูลดิบได้

สมมติ SLO แบบ request-based ของสองช่วงรวมกำหนด 99% budget จะเท่ากับ 1% ของ 10,000 หรือ 100 เหตุการณ์ ข้อมูลนี้ใช้ไป 190 เหตุการณ์ จึงใช้ 190% ของ budget และเกินอยู่ 90 เหตุการณ์ คำว่าใช้เกิน 100% ของ budget ไม่ได้หมายความว่าอัตรา error มากกว่า 100% แต่เป็นการเปรียบเทียบกับขนาด budget ที่กำหนดไว้

ตัวอย่างการตีความที่ผิดคือเรียก error budget 1% ว่าอนุญาต downtime 14.4 นาทีต่อวันทันที การแปลงนี้ใช้กับนิยาม time-based ภายใต้เงื่อนไขที่เหมาะสม แต่โจทย์นี้นับ requests ซึ่งอาจไม่กระจายสม่ำเสมอ หาก 180 errors เกิดในช่วง traffic หนาแน่น เวลาที่กระทบอาจสั้นแต่มีคำขอล้มมาก ต้องระบุหน่วยและนิยามให้ตรงก่อนเปลี่ยนจากจำนวนเหตุการณ์เป็นเวลา

พิจารณาอีกสถานการณ์ที่ Prometheus scrape ไม่สำเร็จช่วงหนึ่ง กราฟ error อาจหายไป หาก dashboard เติมข้อมูลหายเป็นศูนย์โดยไม่มีสัญญาณกำกับ ผู้ใช้จะเห็นเหมือนระบบไม่มี error วิธีที่เหมาะสมคือแสดงความครบถ้วนของข้อมูลและสถานะการ scrape ร่วมกัน พร้อมแยก “ไม่มีเหตุการณ์ที่วัดได้” ออกจาก “วัดแล้วไม่มีเหตุการณ์ผิด”

สำหรับการตรวจ release ใหม่ กำหนดข้อมูลสมมติว่าก่อน deploy error 1% หลัง deploy error 2% ยังไม่ควรยืนยันความเป็นเหตุจากเวลาเพียงอย่างเดียว เพราะ traffic และ dependency อาจเปลี่ยนพร้อมกัน ต้องตรวจ release identity เส้นทางที่ได้รับผล log ตามเวลา และความต่างของภาระงาน การ rollback ที่ทำให้ผลดีขึ้นเป็นหลักฐานเพิ่ม แต่ยังควรบันทึกปัจจัยอื่นที่เปลี่ยนด้วย

ตารางรายงานที่สมบูรณ์จึงควรมีชื่อ SLI นิยามคำขอที่เข้าเกณฑ์ จุดวัด ช่วงเวลา จำนวนทั้งหมด จำนวนดี จำนวนผิด ค่า SLO แหล่งข้อมูล และข้อจำกัด การเพิ่มกราฟโดยไม่มีนิยามเหล่านี้อาจทำให้ผู้ดูคนละคนตัดสินจากตัวเลขเดียวกันต่างกัน

ปฏิบัติการที่ 11 วัดระบบด้วยตัวเลขและตั้งประตูตรวจหลัง deploy

ปฏิบัติการนี้ประกอบด้วย Lab A–D ทุกคนรันระบบและเครื่องมือ monitoring บนเครื่องของตนเอง ส่วนการเปลี่ยนแปลงที่เข้า repository แบ่งผู้รับผิดชอบดังนี้ เพื่อให้ทุกคนมี pull request ของตนเองใน milestone M10

pull requestbranchLabผู้รับผิดชอบผู้ตรวจทาน
เพิ่ม /metrics, test และปรับเวอร์ชันเป็น 0.5.0obs/metricsB.1–B.2สมาชิกคนที่ 1สมาชิกคนที่ 2
เพิ่ม compose.monitoring.yaml และโฟลเดอร์ monitoring/obs/monitoringB.3สมาชิกคนที่ 2สมาชิกคนที่ 3
เพิ่มประตูตรวจ health ใน scripts/deploy.shdeploy/health-gateD.1–D.2สมาชิกคนที่ 3สมาชิกคนที่ 1

เมื่อ merge ครบแล้วหัวหน้าทีมออกเวอร์ชัน v0.5.0 ตามบทที่ 8 แล้วทำ Lab D.3 ร่วมกับทีม แต่ละ Lab แบ่งเป็นส่วน ฝึกตามตัวอย่าง และส่วน ตัดสินใจเอง (A.3, B.4, C.3 และ D.4)

ℹ️ผลลัพธ์อาจต่างจากตัวอย่าง

ผลลัพธ์ของ Lab A, B และ D ได้จากการรันจริงบน Docker Engine 29.8.2 และ Docker Compose v5.5.1 โดยใช้ image จำลองตามที่ระบุในบทนำ จำนวน request เวลา และที่อยู่ IP จะต่างจากตัวอย่าง ขั้นตอนของ Grafana ใน Lab B.3 และ Lab C ผู้เขียนยังไม่ได้ทดสอบ

Lab A: คำนวณ SLI จาก log

สถานการณ์: ทีมยังไม่มีเครื่องมือ monitoring แต่มี access log ของ Nginx อยู่แล้ว ทีมต้องการตัวเลข SLI แรกของระบบจากข้อมูลที่มี

เป้าหมาย: สร้างเหตุการณ์ที่มีทั้ง request ปกติและ 5xx แล้วนับจาก log ของ Nginx ด้วยคำสั่งจากบทที่ 1 เพื่อคำนวณ SLI (พื้นฐานของหัวข้อ 11.2)

ฝึกตามตัวอย่าง

A.1 สร้าง request ปกติและ 5xx (สมาชิกทุกคน บนเครื่องของตนเอง)

BASH
cd ~/devops-app
git switch main
git pull
docker compose up -d --wait
for i in $(seq 1 20); do curl -s -o /dev/null http://localhost/; done
for i in $(seq 1 5); do curl -s -o /dev/null http://localhost/api/items/99; done
docker compose stop app
for i in 1 2 3; do curl -s -o /dev/null -w "%{http_code} " http://localhost/; done; echo
docker compose start app
docker compose exec -T proxy nginx -s reload
TEXT
502 502 502

คำสั่ง seq 1 20 สร้างตัวเลข 1 ถึง 20 ให้ for วนส่ง request 20 ครั้ง ระหว่างที่แอปหยุด Nginx ตอบ 502 ทุกครั้ง หลังเริ่มแอปใหม่ต้อง reload Nginx ตามเหตุผลในบทที่ 9

A.2 นับจาก log

BASH
docker compose logs --no-log-prefix proxy > access.log
grep -c -E 'HTTP/1\.[01]" [0-9]{3} ' access.log
grep -c -E 'HTTP/1\.[01]" 5[0-9]{2} ' access.log
grep -o -E 'HTTP/1\.[01]" [0-9]{3} ' access.log | sort | uniq -c
grep -c '"GET ' access.log
TEXT
39
3
     23 HTTP/1.1" 200
     13 HTTP/1.1" 404
      3 HTTP/1.1" 502
42
คำสั่งนับอะไร
grep -c -E 'HTTP/1\.[01]" [0-9]{3} 'บรรทัด access log ทุกบรรทัด คือ request ทั้งหมด (total events)
grep -c -E 'HTTP/1\.[01]" 5[0-9]{2} 'request ที่ได้ status กลุ่ม 5xx (bad events)
grep -o ... | sort | uniq -cจำนวน request แยกตาม status

ตัวเลขของเครื่องผู้เขียนรวม request จาก Lab ก่อนหน้าด้วย จึงมากกว่าที่ส่งใน A.1 SLI = (39 − 3) / 39 × 100% ≈ 92.3% บรรทัดสุดท้ายที่นับ "GET ได้ 42 มากกว่า 39 เพราะ error log ของ Nginx มีข้อความ request: "GET / HTTP/1.1" อยู่ด้วย รูปแบบที่ใช้นับจึงต้องเจาะจงให้ตรงเฉพาะ access log

ตัดสินใจเอง A.3: ตั้ง SLO ของระบบทีมสำหรับการฝึกนี้ (เช่น 99%) แล้วคำนวณจากตัวเลขของตนเองว่า (1) SLI ผ่าน SLO หรือไม่ (2) ด้วยจำนวน request เท่านี้ error budget มีกี่รายการ และใช้ไปแล้วกี่รายการ (3) ถ้าต้องการ SLI ที่วัดเฉพาะ request ไปยัง /api/ จะปรับคำสั่งนับอย่างไร บันทึกคำสั่งและตัวเลขลงใน Learning Log

ผลลัพธ์ที่คาดหวัง A.3: ตัวเลขที่คำนวณจาก log ของตนเอง พร้อมคำสั่งที่กรอง /api/ ซึ่งทำงานได้จริง

จุดตรวจ A: สมาชิกทุกคนคำนวณ SLI จาก log ของตนเองได้ และอธิบายได้ว่าเหตุใดนับ "GET ตรง ๆ จึงได้ตัวเลขเกินจริง

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
หลังเริ่มแอปใหม่ยังได้ 502Nginx ยังใช้ IP เดิมของแอปdocker compose exec -T proxy nginx -s reload
grep -c ได้ 0รูปแบบไม่ตรงกับรูปแบบ log หรือใช้ access.log ผิดไฟล์เปิดดูด้วย tail -n 3 access.log แล้วปรับรูปแบบ
curl ค้างนานก่อนได้ 504IP ปลายทางไม่ตอบรอได้ หรือเพิ่ม --max-time 10 ใน curl

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab B: เพิ่ม /metrics และรัน Prometheus กับ Grafana

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

เป้าหมาย: ให้แอปเปิด /metrics ในรูปแบบที่ Prometheus อ่านได้ แล้วรัน Prometheus และ Grafana จากไฟล์ที่ให้มา (LLO 11.1)

ฝึกตามตัวอย่าง

B.1 เพิ่มตัวนับ request (สมาชิกคนที่ 1)

BASH
git switch -c obs/metrics
nano src/metrics.js
JS
// metrics.js : นับ request แยกตาม method, route และ status ในรูปแบบข้อความที่ Prometheus อ่านได้
const counts = new Map();
 
function metricsMiddleware(req, res, next) {
  res.on('finish', () => {
    const route = req.route ? req.route.path : 'unmatched';
    const labels = `method="${req.method}",route="${route}",status="${res.statusCode}"`;
    counts.set(labels, (counts.get(labels) || 0) + 1);
  });
  next();
}
 
function metricsHandler(req, res) {
  const lines = [
    '# HELP http_requests_total Total number of HTTP requests.',
    '# TYPE http_requests_total counter',
  ];
  for (const [labels, value] of counts) {
    lines.push(`http_requests_total{${labels}} ${value}`);
  }
  res.type('text/plain; version=0.0.4').send(lines.join('\n') + '\n');
}
 
module.exports = { metricsMiddleware, metricsHandler };
ส่วนหน้าที่
res.on('finish', ...)นับหลังส่งคำตอบเสร็จ จึงรู้ status จริง
req.route ? req.route.path : 'unmatched'ใช้รูปแบบเส้นทาง เช่น /api/items/:id ไม่ใช่ path จริง (หัวข้อ 11.4 ระดับที่ 2)
countsเก็บยอดของแต่ละชุด label ในหน่วยความจำ ค่ากลับเป็นศูนย์เมื่อแอปเริ่มใหม่ ตรงกับนิยามของ counter
metricsHandlerพิมพ์ยอดทั้งหมดในรูปแบบข้อความของ Prometheus

ใน src/app.js เพิ่มสามจุด

JS
const { metricsMiddleware, metricsHandler } = require('./metrics');

ต่อจากบรรทัด require('../package.json') จากนั้นเพิ่ม app.use(metricsMiddleware); ต่อจาก const app = express(); และเพิ่ม app.get('/metrics', metricsHandler); ก่อนเส้นทาง /api/items

B.2 เพิ่ม test และปรับเวอร์ชัน

BASH
nano test/metrics.test.js
JS
const test = require('node:test');
const assert = require('node:assert');
const app = require('../src/app');
 
test('GET /metrics counts requests by route and status', async (t) => {
  const server = app.listen(0);
  await new Promise((resolve) => server.once('listening', resolve));
  t.after(() => server.close());
  const base = `http://127.0.0.1:${server.address().port}`;
 
  await fetch(`${base}/api/items/99`);
  const res = await fetch(`${base}/metrics`);
  const text = await res.text();
 
  assert.strictEqual(res.status, 200);
  assert.match(text, /# TYPE http_requests_total counter/);
  assert.match(text, /http_requests_total\{method="GET",route="\/api\/items\/:id",status="404"\} 1/);
});
BASH
npm version minor --no-git-tag-version
npm test
git add src/ test/ package.json package-lock.json
git commit -m "feat: expose request counter at /metrics, bump 0.5.0"
git push -u origin obs/metrics

(ทดสอบแล้ว) npm version minor เปลี่ยนเวอร์ชันจาก 0.4.1 เป็น 0.5.0 เพราะเป็นความสามารถใหม่ตามหลัก SemVer ในบทที่ 4 และ npm test ได้ # tests 6, # pass 6 (ถ้ามี test ของสมาชิกจากบทก่อน ๆ จำนวนจะมากกว่านี้)

B.3 รัน Prometheus และ Grafana (สมาชิกคนที่ 2 สร้างไฟล์ ทุกคนรัน)

สร้างไฟล์สามไฟล์ตามที่ผู้สอนให้มา

monitoring/prometheus.yml

YAML
global:
  scrape_interval: 15s
 
scrape_configs:
  - job_name: devops-app
    static_configs:
      - targets: ["app:3000"]

monitoring/grafana/datasources.yml

YAML
apiVersion: 1
 
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true

compose.monitoring.yaml

YAML
services:
  prometheus:
    image: prom/prometheus:v3.7.3
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "127.0.0.1:9090:9090"
 
  grafana:
    image: grafana/grafana:12.2.0
    volumes:
      - ./monitoring/grafana/datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml:ro
      - grafana-data:/var/lib/grafana
    ports:
      - "127.0.0.1:3001:3000"
    depends_on:
      - prometheus
 
volumes:
  grafana-data:
BASH
git switch -c obs/monitoring
git add compose.monitoring.yaml monitoring/
git commit -m "chore: add Prometheus and Grafana for local monitoring"
git push -u origin obs/monitoring

หลัง merge ทั้งสอง pull request ทุกคนรัน

BASH
git switch main
git pull
docker compose -f compose.yaml -f compose.monitoring.yaml up -d --build --wait
curl -s http://127.0.0.1:9090/api/v1/targets | grep -o '"health":"[a-z]*"'
TEXT
"health":"up"

ตัวเลือก -f สองครั้งรวมสองไฟล์เป็นระบบเดียว Prometheus จึงอยู่ใน network เดียวกับแอปและเรียก app:3000 ได้ scrape_interval: 15s คือดึงทุก 15 วินาที ส่วน 127.0.0.1: หน้าพอร์ตทำให้เปิดได้เฉพาะจากเครื่องตนเอง เปิด http://localhost:9090 แล้วไปที่เมนู Status → Targets จะเห็น job devops-app สถานะ UP (ผลจาก API ทดสอบแล้ว ส่วนชื่อเมนูในหน้าเว็บยังไม่ได้ตรวจกับรุ่น 3.7.3) Grafana อยู่ที่ http://localhost:3001 ผู้เขียนยังไม่ได้ทดสอบ Grafana ในบทนี้

ตัดสินใจเอง B.4: ตอนนี้ /metrics เปิดให้ทุกคนเข้าถึงผ่านพอร์ต 80 ของ Nginx ได้ด้วย ให้ตัดสินว่าควรปิดหรือไม่ โดยพิจารณาว่าข้อมูลใน /metrics บอกอะไรกับผู้ที่ไม่หวังดีได้บ้าง และ Prometheus ดึงข้อมูลผ่านเส้นทางใด ถ้าปิด ให้แก้ nginx/default.conf และพิสูจน์ด้วย curl ว่าเข้าผ่านพอร์ต 80 ไม่ได้ แต่หน้า Targets ยังเป็น UP บันทึกเหตุผลและแหล่งอ้างอิงในคำอธิบาย pull request (ทำใน branch deploy/health-gate ของ Lab D ได้)

ผลลัพธ์ที่คาดหวัง B.4: การตัดสินใจพร้อมเหตุผล และถ้าปิด ต้องมีผล curl ที่ได้ 404 ผ่านพอร์ต 80 คู่กับ /health ที่ยังได้ 200

จุดตรวจ B: หน้า Targets แสดง devops-app เป็น UP บนเครื่องของสมาชิกทุกคน

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
Targets แสดง DOWN และ connection refusedแอปยังไม่มี /metrics (image เก่า) หรือแอปหยุดรันด้วย --build เพื่อสร้าง image จากโค้ดล่าสุด
Targets แสดง DOWN และ no such hostรัน Prometheus แยกจากระบบหลัก จึงไม่อยู่ใน network เดียวกันใช้ -f compose.yaml -f compose.monitoring.yaml ในคำสั่งเดียว
Bind for 127.0.0.1:9090 failedมีโปรแกรมอื่นใช้พอร์ตหยุดโปรแกรมนั้น หรือเปลี่ยนพอร์ตฝั่งซ้ายในไฟล์
test ล้มที่บรรทัด assert.match ของ /metricsลืม app.use(metricsMiddleware) หรือวางหลังเส้นทางวาง app.use ต่อจาก const app = express(); ทันที

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab C: สร้าง panel แสดง error

สถานการณ์: ทีมต้องการเห็นจำนวน request ที่ผิดพลาดเป็นกราฟ เพื่อสังเกตได้ทันทีเมื่อ deploy แล้ว error เพิ่มขึ้น

เป้าหมาย: ทดลองคำค้นใน Prometheus แล้วสร้าง panel ใน Grafana ที่แสดงจำนวน request ที่ได้ 4xx และ 5xx แยกตาม status โดยหาขั้นตอนจากเอกสารทางการเอง (LLO 11.1)

ฝึกตามตัวอย่าง

C.1 สร้าง error และทดลองคำค้นใน Prometheus (สมาชิกทุกคน)

BASH
for i in $(seq 1 7); do curl -s -o /dev/null http://localhost/api/items/42; done

รอประมาณ 30 วินาทีให้ Prometheus ดึงข้อมูลอย่างน้อยหนึ่งรอบ แล้วเปิด http://localhost:9090 ใส่คำค้นต่อไปนี้ในช่องค้นหา และกด Execute

TEXT
sum by (status) (increase(http_requests_total{status=~"4..|5.."}[5m]))
ส่วนความหมาย
http_requests_total{status=~"4..|5.."}เลือกอนุกรมเวลาที่ status ตรงกับรูปแบบ 4xx หรือ 5xx (=~ คือเทียบกับ regular expression)
increase(...[5m])ค่าที่เพิ่มขึ้นในช่วง 5 นาทีล่าสุด
sum by (status) (...)รวมทุก route ให้เหลือหนึ่งเส้นต่อ status

ผลการทดสอบผ่าน API ของ Prometheus หลังส่ง 404 เพิ่ม 7 ครั้งได้ {status="404"} ค่า 7.88... ตามเหตุผลในหัวข้อ 11.4 ระดับที่ 3 ถ้าได้ค่าว่างหรือ 0 แสดงว่ายังไม่มีการเพิ่มในช่วงเวลานั้น ให้ส่ง request ใหม่แล้วรอ

C.2 สร้าง panel ใน Grafana เปิด http://localhost:3001 เข้าสู่ระบบและเปลี่ยนรหัสผ่านผู้ดูแลตามที่หน้าจอแจ้ง แหล่งข้อมูลชื่อ Prometheus ควรถูกตั้งไว้แล้วจากไฟล์ datasources.yml จากนั้นหาขั้นตอนสร้าง dashboard และ panel แรกจากเอกสารทางการของ Grafana ที่ grafana.com/docs ด้วยตนเอง แล้วสร้าง panel ที่ใช้คำค้นจาก C.1 ตั้งชื่อ panel ว่า "Errors by status (5m)" และบันทึก dashboard ชื่อ "devops-app" บันทึกลิงก์หน้าเอกสารที่ใช้ เพราะเป็นหลักฐานของงานข้อ 1

ผลลัพธ์ที่คาดหวัง: panel แสดงเส้นของ status 404 ที่เพิ่มขึ้นหลัง C.1 และภาพหน้าจอที่เห็นทั้งกราฟและคำค้น

ตัดสินใจเอง C.3: เพิ่ม panel ที่สองที่แสดง SLI ของแอปเป็นร้อยละตามนิยามในหัวข้อ 11.2 ออกแบบคำค้นเอง แล้วทดลองหยุดแอปตาม Lab A.1 ดูว่า panel นี้เปลี่ยนหรือไม่ อธิบายผลที่เห็นโดยอ้างหัวข้อ 11.2 ระดับที่ 2 และระบุว่าถ้าต้องการ SLI ที่รวม 502 ของ Nginx ด้วย ต้องเก็บข้อมูลจากที่ใดเพิ่ม

ผลลัพธ์ที่คาดหวัง C.3: คำค้นที่ทำงานได้ ภาพ panel และคำอธิบายว่าเหตุใด panel SLI จากแอปไม่ลดลงขณะ Nginx ตอบ 502

จุดตรวจ C: สมาชิกทุกคนมีภาพ dashboard ของตนเองตาม checklist ของงานข้อ 1

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
panel ขึ้น No dataยังไม่มี error ในช่วงเวลาที่เลือก หรือเลือกแหล่งข้อมูลผิดส่ง request ที่ได้ 404 ใหม่ แล้วตรวจช่วงเวลามุมขวาบนของ dashboard
Grafana ต่อ Prometheus ไม่ได้ใส่ localhost:9090 เป็นที่อยู่ ซึ่งจาก container ของ Grafana หมายถึงตัวเองใช้ http://prometheus:9090 ตามไฟล์ datasources.yml
กราฟกลับเป็นศูนย์หลังรีสตาร์ตแอปcounter เริ่มใหม่เมื่อแอปเริ่มใหม่เป็นพฤติกรรมปกติของ counter ฟังก์ชัน increase และ rate จัดการการเริ่มใหม่ให้แล้ว

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab D: ประตูตรวจ health หลัง deploy

สถานการณ์: ในบทที่ 9 workflow รายงานว่าสำเร็จทั้งที่หน้าเว็บได้ 502 ทีมต้องการให้ run ล้มทุกครั้งที่ผู้ใช้เข้าแอปผ่าน proxy ไม่ได้ หรือได้เวอร์ชันที่ไม่ตรงกับ tag

เป้าหมาย: เพิ่มการตรวจ health ผ่าน proxy ใน scripts/deploy.sh ที่ workflow CD เรียก ให้ deploy ถือว่าสำเร็จเมื่อแอปตอบ health ของเวอร์ชันที่ deploy เท่านั้น และสร้าง run ที่ล้มเพื่อพิสูจน์ (LLO 11.2)

ฝึกตามตัวอย่าง

D.1 ตรวจว่าค่าตั้งของ Nginx ถึง container จริง (สมาชิกคนที่ 3)

ประตูตรวจใน D.3 พิสูจน์ผลด้วยการแก้ nginx/default.conf แล้ว deploy ใหม่ จึงต้องแน่ใจก่อนว่าการแก้ไฟล์ไปถึง Nginx จริง ระหว่างเขียนบทนี้ ผู้เขียนทดลองกับ compose.yaml ที่ต่อไฟล์เดี่ยว (./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro) แล้วพบว่าหลังแก้ไฟล์และ deploy ใหม่ Nginx ยังใช้ค่าตั้งเดิม

TEXT
$ sed -i 's/app:3000/app:3001/' nginx/default.conf
$ docker compose -p devops-prod ... exec -T proxy nginx -T | grep proxy_pass
        proxy_pass http://app:3000;

การต่อไฟล์เดี่ยวเข้า container ผูกกับไฟล์ต้นฉบับตัวเดิม เมื่อเครื่องมือแก้ไฟล์ด้วยการสร้างไฟล์ใหม่มาแทน (เช่น sed -i หรือ git checkout) container ยังเห็นไฟล์เดิม (สังเกตจากการทดสอบด้วย sed -i การ checkout ของ Git ใน workflow อาจเขียนไฟล์ใหม่แบบเดียวกัน แต่ผู้เขียนยังไม่ได้ทดสอบ และเอกสารของ Docker เรื่องนี้ไม่อยู่ในรายการแหล่งอ้างอิง) บทที่ 9 จึงให้ต่อทั้งโฟลเดอร์ตั้งแต่ต้น ให้ตรวจว่าบริการ proxy ใน compose.yaml บน main เป็นแบบนี้ ถ้ายังเป็นไฟล์เดี่ยวให้แก้ใน pull request เดียวกับ D.2

YAML
    volumes:
      - ./nginx:/etc/nginx/conf.d:ro

D.2 เพิ่มประตูตรวจใน scripts/deploy.sh

BASH
git switch -c deploy/health-gate
nano scripts/deploy.sh

เพิ่มบรรทัด HEALTH_URL ต่อจาก ENV_FILE และเพิ่มส่วนตรวจต่อท้ายไฟล์ ไฟล์ทั้งหมดเป็นดังนี้

BASH
#!/usr/bin/env bash
# deploy.sh TAG : ดึง image ตาม TAG แล้วรันระบบบนเครื่องปลายทาง
set -euo pipefail
 
TAG="${1:?usage: scripts/deploy.sh TAG (เช่น 0.3.0)}"
ENV_FILE="${DEPLOY_ENV_FILE:-$HOME/devops-deploy/.env}"
HEALTH_URL="${HEALTH_URL:-http://localhost/health}"
 
export IMAGE_TAG="$TAG"
docker compose -p devops-prod --env-file "$ENV_FILE" pull app
docker compose -p devops-prod --env-file "$ENV_FILE" up -d --no-build --wait
docker compose -p devops-prod --env-file "$ENV_FILE" exec -T proxy nginx -s reload
docker compose -p devops-prod --env-file "$ENV_FILE" ps
 
echo "== ตรวจ health ผ่าน proxy: $HEALTH_URL"
if ! curl -fsS --retry 5 --retry-delay 2 --retry-all-errors "$HEALTH_URL" | grep -F "\"version\":\"$TAG\""; then
  echo "deploy ล้ม: $HEALTH_URL ไม่ตอบ health ของเวอร์ชัน $TAG" >&2
  exit 1
fi
echo "deploy สำเร็จ: เวอร์ชัน $TAG ตอบ health ผ่าน proxy"
ส่วนหน้าที่
curl -fจบด้วย exit code ที่ไม่ใช่ 0 เมื่อได้ status 4xx หรือ 5xx
--retry 5 --retry-delay 2 --retry-all-errorsลองใหม่สูงสุด 5 ครั้ง ห่างกัน 2 วินาที ทุกชนิดของความล้มเหลว เผื่อช่วงที่ระบบเพิ่งเริ่ม
grep -F "\"version\":\"$TAG\""ผ่านเมื่อคำตอบเป็นของเวอร์ชันที่ deploy เท่านั้น
set -o pipefail (บรรทัดที่ 3)ทำให้ความล้มเหลวของ curl ในท่อทำให้ทั้งเงื่อนไขล้ม
HEALTH_URLเปลี่ยนได้เมื่อ proxy ใช้พอร์ตอื่น เช่น HEALTH_URL=http://localhost:8000/health
BASH
shellcheck scripts/deploy.sh
git add scripts/deploy.sh
git commit -m "deploy: verify health through proxy"
git push -u origin deploy/health-gate

ผลการทดสอบบนเครื่องจำลองกับระบบชุด devops-prod (ไม่รวมขั้น pull จาก registry)

TEXT
== ตรวจ health ผ่าน proxy: http://localhost/health
{"status":"ok","version":"0.5.0"}
deploy สำเร็จ: เวอร์ชัน 0.5.0 ตอบ health ผ่าน proxy

D.3 พิสูจน์ว่าประตูทำงาน (หัวหน้าทีม ร่วมกับทีม หลังออก v0.5.0 แล้ว)

สร้างเวอร์ชันที่ test ผ่านแต่ proxy ส่งต่อไปผิดพอร์ต แล้วออกเป็นเวอร์ชันจริง

BASH
git switch main
git pull
git switch -c demo/broken-proxy
sed -i 's/app:3000/app:3001/' nginx/default.conf
npm version patch --no-git-tag-version
git commit -am "demo: wrong upstream port (do not merge)"
git push -u origin demo/broken-proxy
git tag v0.5.1
git push origin v0.5.1

ผลลัพธ์ที่คาดหวัง: run ของ CD ผ่าน job test และ build-push แต่ job deploy ล้มที่ขั้น Deploy with Compose ผลการทดสอบบนเครื่องจำลองให้ข้อความดังนี้

TEXT
curl: (22) The requested URL returned error: 502
curl: (22) The requested URL returned error: 502
curl: (22) The requested URL returned error: 502
deploy ล้ม: http://localhost/health ไม่ตอบ health ของเวอร์ชัน 0.5.0

(บนเครื่องจำลองผู้เขียนทดสอบด้วยเวอร์ชัน 0.5.0 ใน run จริงจะเป็น 0.5.1) เก็บลิงก์ run นี้เป็นหลักฐานของงานข้อ 2 ประตูตรวจบอกว่าระบบพัง แต่ไม่ได้ย้อนระบบกลับให้ ผู้ใช้ยังได้ 502 จนกว่าทีมจะแก้ ให้กู้ระบบทันทีด้วยการ deploy เวอร์ชันเดิมด้วยมือบนเครื่องปลายทาง (ทางสำรองจากบทที่ 8 ซึ่งบทที่ 12 จะพัฒนาเป็นขั้นตอน rollback)

BASH
cd ~/devops-app
git fetch --tags
git switch --detach v0.5.0
read -rs POSTGRES_PASSWORD && export POSTGRES_PASSWORD
bash scripts/deploy.sh 0.5.0
unset POSTGRES_PASSWORD
git switch main

ผลที่คาดหวังคือบรรทัดสุดท้าย deploy สำเร็จ: เวอร์ชัน 0.5.0 ตอบ health ผ่าน proxy (ทดสอบแล้วบนเครื่องจำลองว่าการคืนไฟล์ค่าตั้งที่ถูกต้องแล้ว deploy ใหม่ทำให้ผ่าน) จากนั้นลบ branch demo/broken-proxy ทั้งในเครื่องและบน GitHub โดยเก็บ tag v0.5.1 ไว้เป็นบันทึก เวอร์ชันถัดไปของทีมจึงเป็น 0.5.2

ตัดสินใจเอง D.4: ประตูตรวจปัจจุบันตรวจเพียง /health ให้ระบุอย่างน้อย 2 สถานการณ์ที่ /health ตอบถูกต้องแต่ผู้ใช้ยังใช้ระบบไม่ได้ เลือกหนึ่งสถานการณ์มาเพิ่มการตรวจใน scripts/deploy.sh แล้วทดสอบบนเครื่องของตนเองว่าการตรวจใหม่ล้มเมื่อควรล้ม และผ่านเมื่อควรผ่าน บันทึกเหตุผลและผลทดสอบในคำอธิบาย pull request ใหม่

ผลลัพธ์ที่คาดหวัง D.4: รายการสถานการณ์ การตรวจที่เพิ่ม และผลทดสอบทั้งกรณีผ่านและล้ม สคริปต์ยังผ่าน shellcheck

จุดตรวจ D: มีลิงก์ run ที่ล้มเพราะ health และ run ที่ผ่านหลังแก้ หน้าแรกบนเครื่องปลายทางแสดงเวอร์ชันที่ตรงกับ deploy ล่าสุด

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
ประตูล้มทั้งที่หน้าเว็บเปิดได้proxy ใช้พอร์ตอื่นที่ไม่ใช่ 80ตั้ง HEALTH_URL ใน env ของขั้น deploy ให้ตรงกับ PROXY_PORT
ประตูล้มด้วยข้อความ version ไม่ตรง ทั้งที่ health ได้ 200ลืมปรับ version ใน package.json ให้ตรงกับ tagใช้ npm version ก่อนสร้าง tag ทุกครั้ง
curl: option --retry-all-errors: is unknowncurl บนเครื่องปลายทางรุ่นเก่าอัปเดต curl หรือเอาตัวเลือกนี้ออกแล้วเพิ่ม sleep ก่อนตรวจ
แก้ nginx/default.conf แล้ว Nginx ยังใช้ค่าเดิมยังต่อไฟล์เดี่ยวอยู่เปลี่ยนเป็นต่อโฟลเดอร์ตาม D.1 (บทที่ 9 Lab B.2) และสร้าง proxy ใหม่หนึ่งครั้งด้วย up -d

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

เก็บกวาดเมื่อจบปฏิบัติการ

บนเครื่องของตนเองหยุดระบบด้วย docker compose -f compose.yaml -f compose.monitoring.yaml down (ไม่มี -v เพื่อเก็บ dashboard ของ Grafana ไว้ใช้ในบทที่ 12) และลบไฟล์ access.log ที่สร้างใน Lab A ตรวจด้วย git status ว่าไม่มีไฟล์ log ค้างใน repository บน GitHub ลบ branch demo/broken-proxy และ branch อื่นที่ merge แล้ว

สรุป

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

SLI คือตัววัดระดับบริการที่นิยามอย่างชัดเจน เช่น สัดส่วน request ที่ไม่ใช่ 5xx คำนวณจากเหตุการณ์ที่ดีหารด้วยเหตุการณ์ทั้งหมด SLO คือเป้าหมายของ SLI และ error budget คือ 100% ลบด้วย SLO ซึ่งแปลงเป้าหมายเป็นจำนวน error ที่ยอมให้เกิดได้ ทีมใช้ error budget ตัดสินว่าควรเร่งออกฟีเจอร์หรือหยุดแก้ความเสถียร จุดที่วัดมีผลต่อตัวเลข การวัดที่ proxy ใกล้ประสบการณ์ของผู้ใช้มากกว่าการวัดในแอป

Prometheus ดึง metric จาก /metrics ของแอปผ่าน HTTP และเก็บเป็นอนุกรมเวลาที่ระบุด้วยชื่อและ label counter เหมาะกับการนับ request และ error label ต้องมีค่าจำนวนจำกัด Grafana แสดงผลเป็น dashboard ประตูตรวจ health หลัง deploy ทำให้ workflow ถือว่าสำเร็จเฉพาะเมื่อผู้ใช้เข้าแอปเวอร์ชันใหม่ผ่าน proxy ได้จริง แต่ประตูตรวจเพียงบอกว่าพัง ยังไม่ได้ย้อนระบบกลับให้

สิ่งที่ส่งต่อไปยังโครงงานและ Capstone

สิ่งที่ได้นำไปใช้ที่
ประตูตรวจ health ใน scripts/deploy.sh และ run ที่ล้มบทที่ 12 pipeline ครบสายและการ rollback · บทที่ 13 Capstone
/metrics, compose.monitoring.yaml และ dashboard (M10)บทที่ 12 สังเกตผลระหว่าง rollback · บทที่ 13 AI วิเคราะห์ log
tag v0.5.1 ที่พังและขั้นตอนกู้ด้วยมือบทที่ 12 RUNBOOK และ rollback
Learning Log ครั้งที่ 11portfolio ที่ส่งในบทที่ 13

สรุปคำสั่งในบทนี้

คำสั่งหรือค่าตั้งหน้าที่
for i in $(seq 1 N); do ...; doneทำคำสั่งซ้ำ N ครั้ง
docker compose logs --no-log-prefix proxy > access.logเก็บ log ของบริการเป็นไฟล์โดยไม่มีชื่อ container นำหน้า
grep -c -E 'HTTP/1\.[01]" 5[0-9]{2} ' access.logนับ request ที่ได้ 5xx จาก access log
grep -o -E '...' | sort | uniq -cนับจำนวนแยกตามค่า
docker compose -f compose.yaml -f compose.monitoring.yaml up -d --build --waitรวมไฟล์ Compose สองไฟล์เป็นระบบเดียว
curl -s http://127.0.0.1:9090/api/v1/targetsดูสถานะการดึงข้อมูลของ Prometheus
increase(metric[5m]), rate(metric[5m])ค่าที่เพิ่มขึ้น และอัตราต่อวินาที ในช่วง 5 นาที
sum by (label) (...)รวมอนุกรมเวลาให้เหลือหนึ่งเส้นต่อค่าของ label
curl -fsS --retry 5 --retry-delay 2 --retry-all-errors URLเรียก URL โดยล้มเมื่อได้ 4xx/5xx และลองใหม่

แบบฝึกหัดท้ายบท

ตอนที่ 1 ฝึกทบทวน

ชุด FP10306343-P11 จำนวน 12 ข้อ ข้อละ 1 คะแนน รวม 12 คะแนน ครอบคลุม metrics กับ logs, SLI, SLO และ error budget, ส่วนของ metric และคำค้น PromQL, การเลือก label, ประตูตรวจ health หลัง deploy และความต่างระหว่าง healthcheck กับ SLO

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

ตอนที่ 2 แบบฝึกหัดเขียนตอบ (5 ข้อ)

ชุด FW10306343-P11 จำนวน 5 ข้อ ข้อละ 4 คะแนน รวม 20 คะแนน เป็นงานเขียนตอบที่ดัดแปลงจากแบบฝึกหัดเดิมด้วยสถานการณ์ใหม่ ครอบคลุม SLI, SLO และ SLA การตรวจ health ผ่าน proxy การคำนวณ error budget การคำนวณ SLI จาก access log และการวิเคราะห์ข้อเสนอที่ทำให้ประตูตรวจไม่ล้ม

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

ใช้ประกอบการตรวจตามรูบริก ไม่เปิดให้นักศึกษา

แบบฝึกหัดเพิ่มเติมสำหรับอภิปรายและเขียนตอบ

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

ก. ความเข้าใจ

  • ก1 (LLO 11.1) อธิบายความต่างระหว่าง SLI, SLO และ SLA พร้อมยกตัวอย่างของระบบทีม
  • ก2 (LLO 11.1) อธิบายว่าเหตุใด counter จึงเหมาะกับการนับ error มากกว่า gauge และเหตุใดค่า counter เพียงค่าเดียวจึงบอกได้น้อย
  • ก3 (LLO 11.2) อธิบายว่าเหตุใดประตูตรวจจึงตรวจ /health ผ่าน proxy ที่พอร์ต 80 แทนการตรวจที่แอปโดยตรง

ข. การประยุกต์ (คำนวณ ไม่นับคะแนน)

  • ข1 (LLO 11.1) บริการหนึ่งรับ 250,000 request ในหนึ่งสัปดาห์ ได้ 5xx 500 รายการ คำนวณ SLI และบอกว่าผ่าน SLO 99.9% หรือไม่
  • ข2 (LLO 11.1) SLO 99% ในหน้าต่าง 4 สัปดาห์ คาดว่าจะรับ 80,000 request คำนวณ error budget เป็นจำนวน request ถ้า deploy ที่พังครั้งหนึ่งทำให้ได้ 5xx 600 รายการ ใช้งบไปกี่เปอร์เซ็นต์
  • ข3 (LLO 11.1) จาก access log ต่อไปนี้ คำนวณ SLI ตามนิยามของบทนี้ และเขียนคำสั่ง grep ที่นับ bad events
TEXT
172.19.0.1 - - [19/Oct/2026:10:00:01 +0000] "GET / HTTP/1.1" 200 26 "-" "curl/8.5.0" "-"
172.19.0.1 - - [19/Oct/2026:10:00:02 +0000] "GET /api/items/9 HTTP/1.1" 404 21 "-" "curl/8.5.0" "-"
172.19.0.1 - - [19/Oct/2026:10:00:03 +0000] "GET / HTTP/1.1" 502 166 "-" "curl/8.5.0" "-"
172.19.0.1 - - [19/Oct/2026:10:00:04 +0000] "GET /health HTTP/1.1" 200 33 "-" "curl/8.5.0" "-"
172.19.0.1 - - [19/Oct/2026:10:00:05 +0000] "GET / HTTP/1.1" 504 167 "-" "curl/8.5.0" "-"

ค. การวิเคราะห์ปัญหา

  • ค1 (LLO 11.1) หน้า Targets ของ Prometheus แสดง devops-app เป็น DOWN พร้อมข้อความ connection refused ทั้งที่ curl http://localhost/health ได้ 200 วิเคราะห์สาเหตุที่เป็นไปได้อย่างน้อย 2 ข้อ และคำสั่งที่ใช้ตรวจแต่ละข้อ
  • ค2 (LLO 11.2) หลังเพิ่มประตูตรวจ run ของ CD ล้มทุกครั้งด้วย deploy ล้ม: ... ไม่ตอบ health ของเวอร์ชัน 0.5.2 แต่ log แสดง {"status":"ok","version":"0.5.1"} ก่อนข้อความนั้น วิเคราะห์สาเหตุและวิธีแก้
  • ค3 (LLO 11.2) สมาชิกคนหนึ่งเสนอให้เปลี่ยนประตูตรวจเป็น curl -s http://localhost/health || true เพราะ "run แดงบ่อยเกินไป" วิเคราะห์ผลของข้อเสนอนี้และเสนอสิ่งที่ควรทำแทน

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

การศึกษาค้นคว้าเพิ่มเติม

  1. อ่าน Site Reliability Engineering บทที่ 4 Service Level Objectives แล้วบันทึกตัวอย่าง SLI อีก 2 แบบที่ไม่ใช่สัดส่วน error เช่น latency
  2. อ่าน The Site Reliability Workbook บทที่ 2 Implementing SLOs ส่วนการคำนวณ error budget เพื่อทบทวนแบบฝึกหัด ข1–ข2
  3. อ่าน Prometheus: Overview และ Prometheus: Metric types แล้วอธิบายใน Learning Log ว่า histogram ต่างจาก counter อย่างไร
  4. อ่าน OpenTelemetry: Signals เพื่อดูว่า trace ช่วยอะไรที่ log และ metric ทำไม่ได้ เป็นแนวทางสำหรับงานต่อยอด
  5. ผู้ที่สนใจการแจ้งเตือน อ่าน Grafana: Configure Slack for Alerting ซึ่งเป็นส่วนเสริม ไม่สอนในคาบ
  6. ทำแบบฝึกหัดท้ายบทให้ครบ ส่งงานข้อ 1–2 และเขียน Learning Log ครั้งที่ 11

เอกสารอ้างอิง

แหล่งข้อมูลเพิ่มเติมสำหรับตัวอย่างและกรณีอธิบาย

ตรวจเอกสารออนไลน์เมื่อ 23 ตุลาคม 2569 ตัวเลขและเหตุการณ์ที่ระบุว่าสมมติใช้เพื่ออธิบายกลไก ไม่ใช่ผลทดลองหรือสถิติขององค์กรจริง

เอกสารอ้างอิงของเนื้อหาเดิม

By Watcharin Sarachai