กับดัก Health Check: เมื่อ Kubernetes สวมบทเพชฌฆาต สั่งฆ่า Pod ตัวเองยกฝูงจนระบบพังพินาศ
⏱️ ใช้เวลาอ่านประมาณ: 4 นาที 30 วินาที
คุณตั้งค่า Kubernetes Deployment อย่างรัดกุมตามคู่มือ Best Practice: "ทำ livenessProbe ยิงเช็กที่ /actuator/health ทุกๆ 10 วินาที ถ้าเจ๊งเกิน 3 ครั้ง ให้ Kubernetes สั่ง Restart Pod ใหม่ทันทีเพื่อความทนทาน"
บ่ายวันมหกรรมลดราคาสินค้า... ฐานข้อมูลหลัก (Database) เกิดทำงานช้าลงชั่วคราวเพราะมีคิวรี่หนักหน่วงวิ่งเข้ามา ทำให้ Response Time ของแอปขยับจาก 50ms ขึ้นเป็น 3 วินาที
แอปพลิเคชันของคุณยังมีชีวิตอยู่และยังคงทยอยเคลียร์ออเดอร์ของลูกค้าได้เรื่อยๆ... แต่ทันใดนั้น ปรากฏการณ์ฆ่าล้างเผ่าพันธุ์ก็เกิดขึ้น:
Pod ทั้ง 20 ตัวบนคลัสเตอร์ พร้อมใจกันติดสถานะ CrashLoopBackOff และ Restart ตัวเองวนไปไม่หยุด! เว็บไซต์กลายเป็นจอดำสนิท ลูกค้าหลายหมื่นคนหลุดออกจากระบบในวินาทีเดียว เกิดอะไรขึ้น? ใครกันแน่ที่สั่งยิงเป้าเซิร์ฟเวอร์ของเรา?
💀 สัญญาณชีพอาถรรพ์: The Deep Health Check Trap
เมื่อวิศวกรเข้าไปตรวจดูโค้ดของ Endpoint /health มักจะพบตรรกะที่ดูเหมือนจะฉลาด แต่แฝงยาพิษร้ายแรงไว้:
@GetMapping("/health")
public ResponseEntity checkHealth() {
boolean dbAlive = checkDatabasePing(); // ⚠️ จุดตาย: แอบยิง ping หา DB!
boolean redisAlive = checkRedisPing();
if (!dbAlive || !redisAlive) {
return ResponseEntity.status(503).body("DOWN");
}
return ResponseEntity.ok("UP");
}
"เมื่อฐานข้อมูลช้าหรือสะอึก... Pod ทุกตัวที่ยิงcheckDatabasePing()จะตอบกลับ503 DOWNหรือหมดเวลา Timeout พร้อมกัน Kubernetes เห็นดังนั้นจึงเข้าใจว่า 'Pod ทุกตัวสมองตายแล้ว' เลยชักปืนยิงฆ่าทิ้งทั้ง 20 เครื่องพร้อมเพรียงกัน!"
🌪️ โดมิโนมรณะ: The Death Spiral
การสั่งรีสตาร์ตไม่ได้ช่วยอะไร มิหนำซ้ำยังเร่งปฏิกิริยาล่มสลาย (Cascading Failure):
- Pod ทั้ง 20 ตัวถูกสั่งฆ่าพร้อมกันเพื่อบูตใหม่
- ตอนแอปพลิเคชันกำลังสตาร์ตขึ้นมา มันต้องสร้าง Connection Pool, โหลดแคช, และต่อสายหา Database พร้อมกัน 20 เครื่อง
- Database ที่กำลังเหนื่อยล้าอยู่แล้ว โดนคลื่น Connection ขาใหม่ซัดกระแทกจนสำลักตายสนิท
- เมื่อ Database ไม่ตอบ... Pod ใหม่ที่เพิ่งเกิดมาก็ตรวจสุขภาพไม่ผ่าน ➔ โดนสั่งฆ่าทิ้งซ้ำรอบสอง
- ระบบติดอยู่ในลูปนรก Death Spiral จนกว่าจะมีคนตื่นมาปิด Probe กลางดึก!
⚖️ สองพี่น้องที่ต้องแยกให้ออก: Liveness vs Readiness
Kubernetes ออกแบบ Probe สองตัวนี้มาเพื่อหน้าที่ที่ แตกต่างกันโดยสิ้นเชิง แต่คนส่วนใหญ่กลับชี้ไปที่ Endpoint เดียวกัน:
1. Liveness Probe (คุณยังมีชีวิตอยู่ไหม?):
ถามแค่ว่า: "กระบวนการแอปพลิเคชันค้างในระดับ Deadlock หรือ Out of Memory หรือไม่?"
➔ บทลงโทษเมื่อล้มเหลว: ฆ่าทิ้งแล้วรีสตาร์ตใหม่ (Kill & Restart)
➔ กฎเหล็ก: ห้ามตรวจเช็กบริการภายนอก (DB, Redis, Third-party) เด็ดขาด! ตรวจแค่ว่าตัวแอปเองยังหมุน Thread ได้ก็พอ
2. Readiness Probe (คุณพร้อมรับแขกไหม?):
ถามว่า: "ตอนนี้เครื่องคุณพร้อมที่จะรับคำขอของลูกค้าเข้ามาประมวลผลหรือยัง?"
➔ บทลงโทษเมื่อล้มเหลว: ไม่ฆ่า! แค่สั่งดึง IP ของ Pod ออกจาก Load Balancer ชั่วคราว แขกจะได้ไม่เดินเข้ามาชนกำแพง
➔ กฎเหล็ก: ถ้า DB ล่ม สามารถแจ้งว่า Unready ได้ แต่เมื่อ DB ฟื้น Pod เดิมจะกลับมารับแขกต่อได้ทันทีโดยไม่ต้องเสียเวลาบูตใหม่
🛡️ ท่าแก้เผ็ด: แยกเขี้ยวเล็บด้วย Spring Boot Actuator
ตั้งแต่ Spring Boot 2.3+ เป็นต้นมา ทีมงานได้แยก Probes อัตโนมัติสำหรับ Kubernetes โดยเฉพาะ:
livenessProbe:
httpGet:
path: /actuator/health/liveness # เช็กแค่ว่า JVM ยังไม่ตาย
port: 8080
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness # เช็กความพร้อมในการทำงาน
port: 8080
💡 5-Min Takeaway
"อย่าสั่งประหารชีวิตแอปพลิเคชัน เพียงเพราะเพื่อนข้างบ้านป่วย" การรีสตาร์ต Pod ไม่เคยช่วยรักษาฐานข้อมูลที่กำลังช้า จงใช้ Liveness Probe ตรวจสอบเฉพาะความผิดปกติภายในตัวแอป (Internal Deadlock) เท่านั้น และปล่อยให้หน้าที่จัดการความพร้อมของระบบภายนอกเป็นของ Readiness Probe เพื่อป้องกันไม่ให้เกิดโดมิโนสังหารหมู่ในวันที่ระบบเผชิญกับคลื่นทราฟฟิกสูงสุด