ยกระดับการเฝ้าระวังระบบด้วยตัวชี้วัดเชิงลึก และศิลปะแห่งการสืบสวนและแก้ไขปัญหาในระดับ Production
สัปดาห์ที่แล้วเราได้เรียนรู้ "3 เสาหลักของ Observability" และเครื่องมือพื้นฐาน
ตัวชี้วัดเชิงตัวเลข จัดเก็บและดึงข้อมูลด้วย Prometheus และแสดงผลด้วย Grafana
บันทึกเหตุการณ์เป็นข้อความ ค้นหาและวิเคราะห์แบบรวมศูนย์ด้วย ELK Stack
การแจ้งเตือนอัตโนมัติผ่าน Alertmanager เมื่อตัวชี้วัดเกินเกณฑ์ที่กำหนด
การรู้แค่ว่า "CPU กำลังทำงาน 80%" ไม่เพียงพออีกต่อไปในยุคของ Microservices
ในระบบ Prometheus การจะดึงข้อมูลมาเก็บได้ (Scrape) บริการปลายทางต้องเปิด Endpoint /metrics เอาไว้
แต่ถ้าซอฟต์แวร์นั้นไม่ได้สร้างมาเพื่อ Prometheus ล่ะ จะทำอย่างไร?
คำตอบคือการใช้ "Exporter" ซึ่งทำหน้าที่เป็น "ตัวกลางล่ามแปลภาษา" ดึงข้อมูลดิบจากระบบเป้าหมาย แล้วแปลงให้อยู่ในรูปแบบที่ Prometheus เข้าใจ
ทำหน้าที่รวบรวมข้อมูลระดับฮาร์ดแวร์และระบบปฏิบัติการ (OS) ของเครื่อง Linux/UNIX
# ตัวอย่าง Metrics จาก Node Exporter
node_cpu_seconds_total{mode="idle"} 1234.56
node_memory_MemAvailable_bytes 2147483648
node_filesystem_free_bytes{fstype="ext4"} ...
*มักจะถูกรันแบบ DaemonSet ใน Kubernetes เพื่อให้ทำงานอยู่บนทุกๆ Worker Node
มองระบบแบบกล่องดำ (External View)
แทนที่จะดึงข้อมูลจาก "ภายใน" เครื่อง Blackbox Exporter ทำหน้าที่ "ยิง Probe" ไปทดสอบเป้าหมายจากภายนอก จำลองพฤติกรรมเสมือนว่าผู้ใช้กำลังเข้าเว็บไซต์
หากต้องการรู้ลึกถึงระดับ Business Logic คุณต้องฝัง Code ลงไปเอง (Instrumentation)
# ตัวอย่างการใช้ Prometheus Client Library ใน Python (Flask)
from prometheus_client import Counter
# สร้าง Metric ประเภท Counterเพื่อนับจำนวน
checkout_requests = Counter('checkout_total', 'Total successful checkouts')
@app.route('/checkout')
def checkout():
# เมื่อมีผู้ใช้เข้า URL นี้ ให้เพิ่มค่าขึ้น 1
checkout_requests.inc()
return "Payment Successful!"
*ข้อมูลเหล่านี้จะถูกเปิดให้ Prometheus มาดึงไป ซึ่งมีประโยชน์มากในการวัดยอดขาย หรือจำนวนคิวงานที่ค้างอยู่
"SRE is what happens when you ask a software engineer to design an operations team."
- Benjamin Treynor Sloss, VP of Engineering, Google
SRE นำหลักการของการเขียนโปรแกรมและวิศวกรรมซอฟต์แวร์มาใช้กับงาน Operations (ดูแลระบบ) โดยมีเป้าหมายหลักคือการรักษาสมดุลระหว่าง "ความน่าเชื่อถือของระบบ (Reliability)" และ "ความเร็วในการปล่อยฟีเจอร์ใหม่ (Feature Velocity)"
เพื่อวัดความน่าเชื่อถือ SRE ได้กำหนดคำศัพท์สำคัญ 3 คำ
"สัญญาข้อตกลงธุรกิจ" ถ้าเราทำไม่ได้ตามเป้า จะเกิดผลตามกฎหมายหรือต้องจ่ายเงินคืนลูกค้า
"เป้าหมายภายในทีม" ตัวเลขที่เราตั้งเป้าไว้ให้ระบบทำงานได้ (ต้องเข้มงวดกว่า SLA เสมอ)
"ตัวชี้วัดความจริง" ตัวเลขที่วัดได้จริงๆ ณ ปัจจุบัน จากระบบ Monitoring (เช่น Prometheus)
"สัดส่วนของเหตุการณ์ที่ดี ต่อ เหตุการณ์ทั้งหมด" (แสดงเป็นเปอร์เซ็นต์)
เป้าหมายตัวเลขที่เราต้องการให้ SLI ไปถึง
ตัวอย่างการตั้ง SLO:
*คำเตือน: การตั้ง SLO ไม่ควรตั้งจากสิ่งที่ทีม IT ทำได้ แต่ควรตั้งจาก "ความคาดหวังขั้นต่ำที่ทำให้ผู้ใช้งานพึงพอใจ"
การตั้งเป้าให้ระบบล่ม 0% (Uptime 100%) เป็นเป้าหมายที่แพงและเป็นไปไม่ได้ในทางปฏิบัติ
| Availability (Nines) | เวลาที่ยอมให้ล่มได้ (ต่อปี) | เวลาที่ยอมให้ล่มได้ (ต่อเดือน) | เหมาะสำหรับ |
|---|---|---|---|
| 99% (Two 9s) | 3.65 วัน | 7.30 ชั่วโมง | เว็บไซต์ภายในองค์กร, ระบบที่ไม่วิกฤต |
| 99.9% (Three 9s) | 8.76 ชั่วโมง | 43.8 นาที | แอปพลิเคชันทั่วไป, E-Commerce ทั่วไป |
| 99.99% (Four 9s) | 52.5 นาที | 4.38 นาที | บริการระบบคลาวด์, Core Services |
| 99.999% (Five 9s) | 5.25 นาที | 26 วินาที | ระบบการเงิน, เครื่องมือแพทย์, โทรคมนาคม |
Error Budget = 100% - SLO
ถ้ายอมรับความจริงว่าระบบ 100% ไม่มีอยู่จริง พื้นที่ที่เหลือจากเป้าหมาย (เช่น ถ้าตั้ง SLO ไว้ที่ 99.9% งบประมาณที่เรายอมให้พังได้คือ 0.1%) เรียกว่า Error Budget
ทำไมต้องมี?
ระบบเสถียรมาก ผู้ใช้มีความสุข
Action:
ปล่อยฟีเจอร์ใหม่รัวๆ, ทดลองเทคโนโลยีใหม่, หรือเพิ่มรอบ Deploy ต่อวัน
ระบบเริ่มรวน ผู้ใช้เริ่มบ่น
Action (Freeze!):
หยุด Deploy ฟีเจอร์ใหม่ชั่วคราว, ดึงทีม Dev มาช่วยทีม Ops แก้ Bug และเขียน Test จนกว่ารอบเดือนถัดไป (Budget รีเซ็ต)
เมื่อเกิดเหตุฉุกเฉิน (Incident) สัญชาตญาณมนุษย์มักจะนำไปสู่ความตื่นตระหนกและเดาสุ่ม DevOps และ SRE ที่ดีต้องมี "กรอบแนวคิด (Framework)" ในการสืบสวนปัญหา
คิดค้นโดย Brendan Gregg เป็นเช็คลิสต์สำหรับวิเคราะห์ "ทุกๆ Resource" ในระบบ (CPU, RAM, Disk, Network)
Utilization (การใช้งาน)
สัดส่วนเปอร์เซ็นต์ที่ Resource ถูกใช้งานไป เช่น CPU ทำงานอยู่ที่ 90%
Saturation (ความอิ่มตัว)
การมีงานมา "รอคิว (Queue)" มากกว่าที่ระบบจะรับไหว เช่น คิว Disk I/O สูงปรี๊ด
Errors (ข้อผิดพลาด)
จำนวนข้อผิดพลาดระดับฮาร์ดแวร์/OS เช่น Network Packet drops หรือ Bad sectors
คิดค้นโดย Tom Wilkie สำหรับเฝ้าระวัง Microservices โดยดูจากมุมมองของผู้ใช้งาน (User Experience)
Rate (ปริมาณ)
จำนวน Request ที่เข้ามาต่อวินาที (Requests per second) ถ้าร่วงลงกะทันหัน อาจแปลว่าระบบหน้าบ้านพัง
Errors (พัง)
จำนวน Request ที่ล้มเหลว (เช่น ขึ้น HTTP 500) ต่อวินาที
Duration (ความช้า)
เวลาที่ใช้ในการตอบสนอง (Latency) วัดด้วย Percentile (P95, P99) เช่น ผู้ใช้ 99% โหลดเว็บเสร็จใน 1 วิ
Metrics มีไว้เพื่อหาว่า "มีปัญหาอะไร และ สโคปแค่ไหน"
*ข้อดีของ Metrics คือดูได้อย่างรวดเร็วและใช้ทรัพยากรน้อย แต่บอกไม่ได้ลึกว่าบรรทัดไหนใน Code พัง
Logs มีไว้เพื่อหา "สาเหตุเบื้องลึก (Root Cause) ณ จุดเวลานั้น"
"ERROR", "Exception", "Timeout"เมื่อคุณรู้แล้วว่ามี Pod ตัวไหนมีปัญหา เหล่านี้คือคำสั่งเอาชีวิตรอด:
# 1. เช็คเหตุการณ์ที่เกิดขึ้นกับ Pod (หาสาเหตุที่ไม่ยอมรัน)
$ kubectl describe pod [pod-name]
... Events: BackOff (ImagePullBackOff)
# 2. ดู Logs สดของแอปพลิเคชัน
$ kubectl logs [pod-name] --tail=50 -f
# 3. มุดเข้าไปใน Container เพื่อทดสอบ Network/Files
$ kubectl exec -it [pod-name] -- /bin/sh
# 4. ดูการใช้ Resources จริง (CPU/RAM) เทียบกับ Limit
$ kubectl top pod [pod-name]
Mitigation vs Resolution
เมื่อระบบล่ม (Downtime) เวลาคือเงิน! หน้าที่แรกของ DevOps คือการทำให้ระบบกลับมาให้บริการลูกค้าได้เร็วที่สุด (Mitigate) ไม่ใช่นั่งอ่าน Code เพื่อหาบั๊ก (Resolve) กลางดึก
kubectl rollout undo deployment)เสาหลักที่ 3 ของ Observability (ถัดจาก Metrics และ Logs)
ในสถาปัตยกรรม Microservices หนึ่ง Request จากผู้ใช้ อาจวิ่งผ่านไป 5-10 Services การใช้ Logs ธรรมดาจะหาทางตามรอยได้ยากมาก
"อย่าปล่อยให้ระบบล่มฟรีๆ จงเรียนรู้จากมัน"
Post-mortem หรือ Incident Report คือกระบวนการเขียนสรุปและวิเคราะห์เหตุการณ์หลังจากที่ระบบล่มและแก้ไขจนกลับมาทำงานปกติได้แล้ว
"ใครเป็นคนพิมพ์คำสั่งนี้? ทำไมถึงสะเพร่าแบบนี้? ไล่ออกเดี๋ยวนี้!"
ผลลัพธ์: วิศวกรจะหวาดกลัว ปิดบังความผิด ไม่กล้าแก้ไขปัญหา หรือโยนความผิดให้ทีมอื่น ไม่เกิดการเรียนรู้
"เราเชื่อว่าทุกคนตัดสินใจด้วยเจตนาดีที่สุดจากข้อมูลที่มีในตอนนั้น"
ผลลัพธ์: เราโทษกระบวนการ ไม่ใช่คน (เช่น ทำไมระบบถึงปล่อยให้คนพิมพ์คำสั่งผิดพลาดหลุดขึ้น Production ได้โดยไม่มีคน Review หรือระบบ Test พลาด?)
[อ้างอิงจากหลักการ CI/CD RCA] การถามว่า "อะไรที่หายไปในกระบวนการของเรา ที่ปล่อยให้ปัญหานี้หลุดการตรวจสอบภายในและถูกปล่อยออกไปได้?"
สรุปสั้นๆ ใน 1-2 ย่อหน้า เกิดอะไรขึ้น กระทบลูกค้ากี่คน ใช้เวลาแก้เท่าไหร่
เวลาเริ่มมีปัญหา ➡ เวลาที่ Alert ดัง ➡ เวลาที่เริ่มวิเคราะห์ ➡ เวลาแก้สำเร็จ (ใช้ Log ของเวลาจริงๆ)
คำอธิบายเชิงลึกถึงกระบวนการหรือบั๊กที่เป็นตัวการ (ใช้ The 5 Whys)
งานที่ต้องไปทำต่อเพื่อป้องกันไม่ให้เกิดซ้ำ (ต้องระบุผู้รับผิดชอบและ Ticket Number)
มีคำถามเกี่ยวกับ Monitoring Framework, Error Budgets หรือ Post-mortem ไหมครับ?
ในส่วนปฏิบัติการถัดไป เราจะจำลองสถานการณ์ฉุกเฉิน (Incident) ให้ระบบล่มจริงๆ!
kubectl logs และ kubectl describe เพื่อวิเคราะห์หาจุดพัง (Troubleshooting)สวมบทบาทเป็น Site Reliability Engineer (SRE) เฝ้าระวังระบบและสืบสวนสาเหตุเมื่อเกิดปัญหา
kubectl describe เพื่อวิเคราะห์ปัญหา Pod ที่สร้างไม่สำเร็จkubectl logs เพื่อดู Error Message จาก Application ที่แครชเราจะตั้งค่าให้ Grafana คอยเฝ้าดู Metrics จาก Prometheus และแจ้งเตือนเมื่อมี Container ใช้ CPU เกินกำหนด
High CPU Alert เลือก Data source เป็น Prometheus และใส่ PromQL:
A (อ้างอิงจาก Query A) IS ABOVE 80K8s Alerts และ Evaluation group ให้ตั้งรันทุกๆ 1m (1 นาที)เราจะสร้าง Pod วายร้ายที่กินทรัพยากร CPU 100% อย่างต่อเนื่อง เพื่อทดสอบว่า Alert ของเราทำงานหรือไม่
# 1. รัน Pod ด้วย Image busybox และสั่งให้ทำ Infinite Loop (วนลูปไม่รู้จบ)
$ kubectl run cpu-hog --image=busybox --restart=Never -- /bin/sh -c "while true; do :; done"
pod/cpu-hog created
# 2. ตรวจสอบการกินทรัพยากร (อาจต้องรอสัก 1-2 นาทีให้ Metrics Server เก็บข้อมูล)
$ kubectl top pod cpu-hog
*หมายเหตุ: 999m หมายถึงกิน CPU ไปเกือบ 1 Core เต็มๆ (1000 millicores = 1 CPU)
กลับไปที่หน้าจอกราฟหรือหน้า Alerting ของ Grafana เพื่อดูพฤติกรรมของระบบ
High CPU Alert
Active since: 1 minute ago
✅ เมื่อเห็นสถานะเป็น FIRING ถือว่าการตั้งค่า Alert สำเร็จ ให้คุณเคลียร์ Pod ทิ้งด้วยคำสั่ง kubectl delete pod cpu-hog
สมมติว่ามีการ Deploy ระบบ แต่รันไม่ขึ้น เราจะใช้คำสั่ง describe เพื่อดู "เหตุการณ์ (Events)" ระดับ Cluster
# 1. จำลองการสร้าง Pod โดยพิมพ์ชื่อ Image ผิด
$ kubectl run web-broken --image=nginx:v999
# 2. ลอง get pods ดู จะพบความผิดปกติ
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
web-broken 0/1 ImagePullBackOff 0 45s
# 3. ใช้ describe เพื่อหาสาเหตุ
$ kubectl describe pod web-broken
... (เลื่อนลงไปล่างสุดที่หัวข้อ Events)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 15s (x3 over 45s) kubelet Pulling image "nginx:v999"
Warning Failed 14s (x3 over 44s) kubelet Failed to pull image "nginx:v999": rpc error: code = NotFound...
Warning Failed 14s (x3 over 44s) kubelet Error: ErrImagePull
คำสั่ง describe บอกปัญหาภายนอก (เช่น ดึง Image ไม่ได้) แต่ถ้าแอปพัง "จากภายใน" เราต้องดูด้วย logs
# 1. จำลองแอปพลิเคชันที่แครชทันที (รันแล้วพ่น Error ใส่ Console)
$ kubectl run db-crash --image=busybox --restart=Never -- /bin/sh -c "echo 'FATAL: Connection Refused. Invalid Password' && exit 1"
# 2. ดูสถานะ Pod จะเห็นว่า Error (หรือ CrashLoopBackOff ถ้ารันผ่าน Deployment)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
db-crash 0/1 Error 0 10s
# 3. เจาะดู Log ภายใน Container
$ kubectl logs db-crash
FATAL: Connection Refused. Invalid Password
kubectl describekubectl logs
แคปเจอร์หน้าจอ Grafana ที่แสดงสถานะ Alert Rule "High CPU Alert" ขึ้นเป็นสีแดง (FIRING)
แคปเจอร์หน้าจอ Terminal ของคำสั่ง kubectl describe pod web-broken โดยให้เห็นบรรทัด Events ที่แจ้งเตือน Failed to pull image
แคปเจอร์หน้าจอ Terminal ของคำสั่ง kubectl logs db-crash ที่โชว์ข้อความ Error สีแดงจากตัวแอปพลิเคชัน
kubectl delete pod cpu-hog web-broken db-crash