ข้ามไปยังเนื้อหา

Health Check API

service ของคุณรันเป็น replica เหมือน ๆ กันหลายตัวอยู่หลัง load balancer โดยมี orchestrator คอยจัดตารางและสั่งเริ่มหรือหยุดได้ตลอดเวลา ปัญหาคือ replica ไม่ได้พร้อมทำงานทันทีที่ process เริ่มรัน อาจยังเปิด database connection pool ไม่เสร็จ ยัง warm cache อยู่ หรือกำลังรัน migration ค้างไว้ และ replica ก็กลายเป็นตัวป่วยได้นานหลัง startup ไปแล้ว เช่น database connection หลุด downstream dependency ที่ต้องใช้ดับไป หรือเกิด deadlock ขึ้นทั้งที่ในทางเทคนิค process ยังรันอยู่

แต่ platform ที่ห่อ service ของคุณอยู่ไม่รู้เรื่องพวกนี้เลย ในสายตา load balancer process ที่ยังรับ TCP connection ได้ก็คือ process ที่ยังมีชีวิต ต่อให้ทุก request ที่ให้บริการจะคืน error ออกมาก็ตาม

ถ้า orchestrator และ load balancer แยกไม่ออกว่า replica ตัวไหนแข็งแรงตัวไหนป่วย ก็จะส่ง traffic ของผู้ใช้จริงไปยัง instance ที่ให้บริการไม่ได้ต่อไปเรื่อย ๆ pod ที่ database connection หลุดไปแล้วยังได้รับ request หนึ่งในสาม แล้วก็ล้มเหลวทุกครั้ง ส่วน replica ที่เพิ่งเริ่มรันก็ถูกใส่เข้า rotation ก่อน warm up เสร็จ ผู้ใช้กลุ่มแรกจึงเจอ error กันถ้วนหน้า

คุณอยากให้ platform ตัดสินใจเรื่อง routing และ restart ได้เอง แต่สิ่งเดียวที่รู้จริง ๆ ว่า replica แข็งแรงหรือไม่คือตัว replica เอง คำถามจึงเป็นว่า service จะบอก platform ได้อย่างไร ให้บอกได้ต่อเนื่อง ต้นทุนต่ำ และอยู่ในรูปแบบที่ platform เข้าใจอยู่แล้ว ว่าตอนนี้ควรได้รับ traffic หรือยัง

แต่ละบริการเปิดเผย Health Check API คือ HTTP endpoint หนึ่งตัวหรือมากกว่าที่ platform poll ตามตารางเวลา บริการตรวจสอบสถานะของตัวเองและคืนค่า status code ที่ platform สามารถนำไปทำงานได้ — 200 สำหรับแข็งแรง, อะไรสักอย่างในช่วง 5xx สำหรับไม่แข็งแรง

ข้อคิดสำคัญคือ “แข็งแรง” มีสองความหมายที่ชัดเจน และ platform ที่เป็นผู้ใหญ่แล้วจะถามแยกกัน

  • Livenessprocess เสียหายจนต้อง restart แล้วหรือยัง liveness check ที่ล้มเหลวจะบอก orchestrator ให้ฆ่าทิ้งแล้วสร้าง instance ใหม่ ดังนั้นให้เขียน check นี้แบบต้นทุนต่ำและไม่พึ่ง dependency ควรล้มเหลวเฉพาะตอนที่ restart แล้วช่วยได้จริง เช่น deadlock ไม่ใช่ตอนที่ downstream แค่ช้า
  • Readinessตอนนี้ instance นี้รับ traffic ไหวหรือยัง readiness check ที่ล้มเหลวจะบอก load balancer ให้หยุด route request มาที่นี่ แต่ ไม่ restart process ตรงนี้แหละคือจุดที่ควรตรวจ dependency ที่ service ต้องใช้จริง ทั้ง database, cache ที่จำเป็น และ downstream ที่สำคัญ เพื่อให้ replica ที่กำลัง warm up หรือทรุดชั่วคราวถูกดึงออกจาก rotation แบบเงียบ ๆ แล้วใส่กลับเข้ามาเมื่อฟื้น
flowchart LR
  ORCH[Orchestrator] -->|GET /health/live every Ns| LIVE{liveness ok?}
  LB[Load Balancer] -->|GET /health/ready every Ns| READY{readiness ok?}
  LIVE -->|no| KILL[restart instance]
  LIVE -->|yes| KEEP[leave running]
  READY -->|no| DRAIN[stop routing traffic here]
  READY -->|yes| SERVE[send traffic here]
orchestrator poll liveness เพื่อตัดสินใจเรื่อง restart; load balancer poll readiness เพื่อตัดสินใจเรื่อง routing

ตัวอย่าง readiness endpoint ที่ตรวจ dependency ซึ่ง service ต้องใช้จริง แล้วรายงานผลออกมาแบบมีโครงสร้าง จะคืน 200 เฉพาะตอนที่ทุก dependency ที่จำเป็นเข้าถึงได้ นอกนั้นคืน 503 เพื่อให้ load balancer drain instance นี้ออกได้เองโดยไม่ต้องรอคนตัดสินใจ

import express from 'express';
const app = express();
async function checkDb(): Promise<boolean> {
try {
await db.query('SELECT 1');
return true;
} catch {
return false;
}
}
// Liveness: cheap, no dependencies. Fail only if a restart would help.
app.get('/health/live', (_req, res) => {
res.status(200).json({ status: 'UP' });
});
// Readiness: check required dependencies.
app.get('/health/ready', async (_req, res) => {
const checks = { database: await checkDb() };
const healthy = Object.values(checks).every(Boolean);
res.status(healthy ? 200 : 503).json({
status: healthy ? 'UP' : 'DOWN',
checks,
});
});
app.listen(8080);

สิ่งที่คุณได้รับ:

  • Routing และ restart ที่ self-healing platform ดึง replica ที่ป่วยออกจาก rotation และ restart ตัวที่เสียโดยไม่ต้องมีมนุษย์มาเกี่ยวข้อง ดังนั้น instance ที่ล้มเหลวจึงทำให้ความจุลดลงแทนที่จะก่อให้เกิด error ที่ผู้ใช้มองเห็น
  • Rollout และ scaling ที่ปลอดภัย replica ใหม่จะได้รับทราฟฟิกก็ต่อเมื่อ readiness ผ่าน ดังนั้นการ deploy และ scale-up จึงไม่ก่อให้เกิด error แบบเย็น ๆ

สิ่งที่คุณต้องจ่าย:

  • คุณต้องแยก liveness ออกจาก readiness อย่างระมัดระวัง ถ้าการตรวจ liveness ของคุณล้มเหลวเมื่อใดก็ตามที่ downstream ช้า orchestrator ก็จะ restart instance ที่แข็งแรงทุกครั้งที่ downstream สะดุด — เปลี่ยนการสะดุดเล็กน้อยของ dependency ให้กลายเป็นพายุ restart
  • health check โกหกได้ หรือแพงเกินไปก็ได้ check ที่คืน 200 เสมอไม่ว่าสถานะจริงจะเป็นอย่างไร แย่กว่าไม่มี check เลย ส่วน check ที่รัน query แพง ๆ ทุกครั้งที่ถูก poll ก็เพิ่มภาระจนกลายเป็นคอขวดเสียเอง เขียน check ให้มีความหมายแต่ต้นทุนต่ำ และ cache ผลไว้ถ้าถูก poll ถี่
  • Application Metrics — health เป็นสัญญาณใช่/ไม่ใช่; metrics แสดงการเสื่อมสภาพแบบค่อยเป็นค่อยไปที่นำหน้าก่อนการตรวจที่ล้มเหลว
  • Log Aggregation — เมื่อการตรวจ readiness เริ่มล้มเหลว logs ที่ถูกรวบรวมจะบอกคุณว่า ทำไม
ข้อดีข้อแลกเปลี่ยน
orchestrator (Kubernetes) รู้ว่า instance พร้อมรับ traffic หรือไม่health check ที่ผิดพลาดทำให้ route traffic ไปหา instance ที่ไม่ ready
auto-restart instance ที่ไม่ healthyhealth check ที่ expensive ทำให้ load บน service เพิ่ม
liveness และ readiness แยกกัน — restart vs traffic routingdependency ที่ check ใน health endpoint ล้ม ทำให้ทั้ง service ถูก restart
ง่ายต่อการ integrate กับ load balancer และ service meshต้องดูแล health endpoint ให้ accurate ตลอด

Health Check ที่ Check ทุกอย่าง — รวม external dependency ทั้งหมดใน health check อาการ:

  • database ช้า → health check fail → Kubernetes restart pod → restart ไม่ช่วย
  • downstream service ล้ม → ทุก instance ถูก restart พร้อมกัน

Liveness = Readiness — ใช้ endpoint เดียวสำหรับทั้งสอง อาการ:

  • service startup ช้า → liveness check fail → restart ก่อน start เสร็จ → restart loop
  • แยก: liveness = “ฉันยังมีชีวิตอยู่”, readiness = “ฉันพร้อมรับ request”

💡 ตัวอย่างจากของจริง

Kubernetes:

  • livenessProbe: restart pod ถ้า fail
  • readinessProbe: หยุด route traffic ถ้า fail แต่ไม่ restart
  • startupProbe: ให้เวลา startup นาน ก่อนที่ liveness จะเริ่ม check

Netflix:

  • health endpoint แสดง status ของ service พร้อม dependency tree
  • Eureka ใช้ health check สำหรับ Service Discovery — instance ที่ไม่ healthy ถูก deregister
liveness check กับ readiness check ต่างกันอย่างไร
ทำไม liveness check จึงไม่ควรเรียก downstream dependency
ทำไมการให้ readiness ล้มเหลวทันทีที่ downstream ตัวใดเข้าถึงไม่ได้จึงเสี่ยง