Health check tốt không trả lời một câu hỏi chung chung rằng “ứng dụng có khỏe không”. Nó trả lời ba câu hỏi khác nhau: tiến trình có cần được khởi động lại không, instance có nên nhận lưu lượng mới không, và ứng dụng đã hoàn tất giai đoạn khởi động chưa. Nếu gom cả ba vào một endpoint kiểm tra mọi dependency, một lần database chậm có thể khiến Kubernetes đồng thời rút Pod khỏi Service và restart hàng loạt container, làm sự cố nhỏ trở thành gián đoạn diện rộng.
Bài viết này xây dựng một mô hình thực dụng cho backend chạy trên Kubernetes. Trọng tâm không nằm ở cú pháp YAML đơn thuần mà ở hợp đồng của từng probe, cách chọn tín hiệu, đặt ngân sách thời gian, phối hợp với rollout và graceful shutdown, quan sát kết quả, rồi kiểm thử các kịch bản lỗi trước production. Các ví dụ là mẫu thiết kế, cần hiệu chỉnh bằng số liệu thật của ứng dụng.
1. Ba probe là ba quyết định vận hành khác nhau
Kubelet dùng liveness probe để quyết định khi nào container cần restart. Readiness probe quyết định Pod có sẵn sàng nhận traffic qua Service hay không; thất bại readiness không đồng nghĩa container bị restart. Startup probe bảo vệ ứng dụng khởi động chậm: khi probe này tồn tại, liveness và readiness chưa chạy cho đến khi startup thành công.
| Probe | Câu hỏi cần trả lời | Hậu quả khi thất bại | Tín hiệu phù hợp |
|---|---|---|---|
| Liveness | Tiến trình có mắc kẹt đến mức restart là cách phục hồi hợp lý? | Container bị restart sau khi đạt ngưỡng lỗi | Event loop còn tiến triển, worker nội bộ không deadlock, trạng thái tiến trình còn phục hồi được |
| Readiness | Instance này có thể phục vụ request mới đúng hợp đồng? | Pod bị đánh dấu không sẵn sàng và rút khỏi backend của Service | Đã nạp cấu hình, còn capacity, dependency bắt buộc dùng được trong ngân sách |
| Startup | Ứng dụng đã hoàn tất bootstrap ban đầu? | Container bị restart nếu không thành công trong cửa sổ cho phép | Migration không nằm trong tiến trình, cache/warmup thiết yếu hoàn tất, server thực sự sẵn sàng |
Sai lầm nguy hiểm nhất là cho ba probe gọi cùng một hàm sâu kiểm tra database, Redis, message broker và mọi API bên ngoài. Khi một dependency chung gặp lỗi, tất cả Pod có thể fail liveness cùng lúc. Kubernetes restart chúng, tạo thêm kết nối và tải khởi động đúng lúc dependency đang yếu. Liveness chỉ nên thất bại khi restart chính container có khả năng cải thiện tình hình.
Readiness bảo vệ người dùng khỏi một instance tạm thời không phục vụ được; liveness bảo vệ hệ thống khỏi một tiến trình không thể tự phục hồi. Hai mục tiêu này không thể thay thế nhau.
2. Thiết kế endpoint với hợp đồng rõ ràng
Một cấu trúc dễ vận hành là tách endpoint thành /health/live, /health/ready và /health/startup. Mỗi endpoint trả mã 2xx khi điều kiện của chính nó đạt, và mã không phải 2xx khi không đạt. Body nên nhỏ, ổn định và không chứa bí mật. Probe không phải API chẩn đoán đầy đủ; chi tiết nhạy cảm về hostname, chuỗi kết nối hay exception nên nằm trong log nội bộ có kiểm soát.
GET /health/live
200 {"status":"ok"}
GET /health/ready
503 {"status":"not_ready","reason":"database_budget_exceeded"}
GET /health/startup
200 {"status":"started"}
Handler phải rẻ, không khóa dài và không phụ thuộc vào hàng đợi request chính đang có thể bị nghẽn. Nếu health endpoint dùng chung một executor đã đầy, probe sẽ báo lỗi dù tiến trình chưa deadlock; đôi khi đó là tín hiệu readiness hợp lệ nhưng chưa chắc là lý do restart. Với runtime có event loop, có thể duy trì một heartbeat nội bộ được cập nhật định kỳ và để liveness xác nhận heartbeat còn tiến triển trong giới hạn.
Không nên dùng endpoint chỉ trả 200 vô điều kiện. Nó chứng minh HTTP listener còn nhận kết nối nhưng không phát hiện ứng dụng đã kẹt. Ngược lại, không nên chạy truy vấn nặng, quét bảng hoặc ghi dữ liệu thật ở mỗi probe. Một truy vấn kết nối nhẹ có timeout ngắn có thể phù hợp với readiness nếu database là dependency bắt buộc, nhưng nó phải được tính vào tải tổng vì probe chạy trên mọi Pod theo chu kỳ.
3. Liveness: chỉ restart khi restart thực sự có ích
Liveness nên dựa chủ yếu vào trạng thái nội tại của tiến trình. Ví dụ: event loop không tiến triển, thread điều phối bị chết, worker quan trọng đã dừng vĩnh viễn, hoặc ứng dụng tự phát hiện trạng thái bất biến bị phá vỡ và không có đường phục hồi. Một lỗi request riêng lẻ, tỷ lệ 5xx tăng, database timeout hoặc queue backlog cao thường không đủ để kết luận container cần restart.
Hãy xem restart là một thao tác có chi phí. Nó làm mất cache nóng, đóng kết nối, phát sinh bootstrap, có thể giao lại job và chuyển tải sang các replica còn lại. Nếu nhiều Pod cùng restart, capacity giảm nhanh và những Pod sống sót càng quá tải. Vì vậy liveness cần failureThreshold đủ hấp thụ lỗi thoáng qua, timeoutSeconds thực tế, và endpoint độc lập với dependency dùng chung.
- Không fail liveness chỉ vì database, Redis hoặc API bên ngoài đang lỗi.
- Không dùng liveness để ép rollout khi cấu hình thay đổi; Deployment đã có cơ chế thay Pod.
- Không kiểm tra dung lượng đĩa chung nếu restart container không giải phóng được dung lượng đó.
- Có thể fail khi tiến trình mất khả năng tiến triển và restart đã được chứng minh là phục hồi.
- Ghi metric và log nguyên nhân trước khi trả lỗi để điều tra được vòng restart.
Nếu ứng dụng có cơ chế tự ngắt khi gặp trạng thái không thể phục hồi, liveness vẫn hữu ích như một hàng rào cuối. Tuy nhiên cần tránh hai bộ điều khiển cùng phản ứng quá nhanh. Một watchdog nội bộ, probe của kubelet và process supervisor có thể tạo vòng lặp khó hiểu nếu mỗi tầng có timeout và chính sách khác nhau.
4. Readiness: mô hình hóa khả năng nhận traffic mới
Readiness có thể kiểm tra dependency bắt buộc, nhưng phải phân biệt dependency nào thực sự chặn mọi chức năng. Nếu endpoint đọc sản phẩm vẫn chạy khi hệ thống gửi email lỗi, email không nên làm toàn Pod mất readiness. Nếu mọi request đều cần database và không có chế độ suy giảm, database có thể là điều kiện readiness. Với backend nhiều nhóm endpoint, một cờ readiness toàn Pod khá thô; bulkhead, route riêng hoặc workload tách biệt có thể phản ánh capacity chính xác hơn.
Một chiến lược tốt là tổng hợp trạng thái từ các kiểm tra nền thay vì gọi tuần tự mọi dependency trong mỗi HTTP probe. Một tác vụ nền kiểm tra với timeout, lưu kết quả gần nhất và timestamp. Endpoint readiness chỉ đọc snapshot, áp dụng thời hạn tối đa của dữ liệu rồi trả kết quả nhanh. Cách này giới hạn tải thăm dò và tránh probe bị treo theo dependency.
function readiness(snapshot, now) {
if (!startupCompleted) return fail("starting");
if (draining) return fail("draining");
if (now - snapshot.checkedAt > 10s) return fail("stale_check");
if (!snapshot.databaseWithinBudget) return fail("database_unavailable");
if (inFlight > admissionLimit) return fail("capacity_exhausted");
return ok();
}
Đưa capacity vào readiness cần thận trọng. Nếu mọi Pod đồng thời vượt ngưỡng và rút khỏi Service, hệ thống có thể không còn backend dù một phần traffic vẫn phục vụ được. Admission control trả lỗi có kiểm soát đôi khi tốt hơn fail readiness toàn bộ. Hãy dùng hysteresis: ngưỡng rời trạng thái ready và ngưỡng quay lại khác nhau, hoặc yêu cầu nhiều mẫu liên tiếp để tránh trạng thái dao động.
5. Startup probe: dành ngân sách riêng cho bootstrap
Ứng dụng có thể cần thời gian nạp model, biên dịch template, kiểm tra schema, đọc cấu hình hoặc làm nóng dữ liệu. Dùng initialDelaySeconds rất lớn cho liveness chỉ là ước lượng cố định: instance nhanh phải chờ không cần thiết, instance chậm hơn dự kiến vẫn bị restart. Startup probe tạo một cửa sổ riêng và chuyển sang liveness/readiness ngay sau khi bootstrap thành công.
Ngân sách startup xấp xỉ failureThreshold × periodSeconds, cộng ảnh hưởng của timeout và lịch thực thi. Đặt cửa sổ theo phân phối thời gian khởi động quan sát được ở môi trường gần production, kể cả cold start. Không đặt vô hạn vì image hỏng, cấu hình sai hoặc dependency không bao giờ sẵn sàng cần kết thúc rõ ràng.
startupProbe:
httpGet:
path: /health/startup
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 36
livenessProbe:
httpGet:
path: /health/live
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
successThreshold: 1
Đây là cấu hình minh họa, không phải giá trị mặc định cho mọi dịch vụ. Cần tính tổng số probe trên cluster, độ trễ p95/p99 của handler và tốc độ phản ứng mong muốn. Timeout quá thấp gây false negative khi node bận; timeout quá cao làm phản ứng chậm. Chu kỳ quá dày tạo tải đều đặn lên chính ứng dụng và dependency.
6. Phối hợp readiness với rollout và graceful shutdown
Khi Pod nhận tín hiệu kết thúc, ứng dụng nên chuyển sang trạng thái draining và làm readiness thất bại trước khi dừng nhận request mới. Sau đó nó hoàn tất request đang chạy, ngừng lấy job mới, commit hoặc nack công việc theo hợp đồng, đóng kết nối và thoát trước terminationGracePeriodSeconds. Readiness không tự đảm bảo mọi thành phần mạng ngừng gửi traffic ngay lập tức; propagation có độ trễ, nên handler vẫn phải chịu được request đến muộn.
Trong rollout, maxUnavailable, maxSurge, readiness và thời gian khởi động cùng quyết định capacity. Nếu readiness báo thành công trước khi cache hoặc pool thực sự sẵn sàng, traffic đổ vào quá sớm và làm instance mới chậm. Nếu điều kiện quá nghiêm ngặt, rollout có thể đứng dù dịch vụ vẫn có thể phục vụ. minReadySeconds và progress deadline nên được chọn dựa trên hành vi quan sát được, không sao chép máy móc.
- Ứng dụng khởi động và startup probe giữ liveness/readiness chưa hoạt động.
- Bootstrap hoàn tất; startup thành công, readiness bắt đầu đánh giá khả năng phục vụ.
- Pod ready và được đưa vào backend của Service.
- Khi termination bắt đầu, ứng dụng đặt cờ draining để readiness thất bại.
- Ứng dụng hoàn tất công việc trong ngân sách rồi thoát sạch.
Đối với worker không nhận HTTP traffic qua Service, readiness vẫn có thể hữu ích cho vận hành nhưng không tự dừng broker giao message. Worker phải chủ động pause consumer hoặc hạ prefetch khi draining. Health probe và giao thức nhận việc cần cùng phản ánh một state machine.
7. Chọn HTTP, TCP, exec hay gRPC
HTTP probe dễ biểu đạt nhiều trạng thái và trả mã rõ ràng. TCP probe chỉ xác nhận có thể mở kết nối đến port; nó không biết handler nghiệp vụ có tiến triển. Exec probe chạy lệnh trong container, hữu ích với tiến trình không mở cổng nhưng tạo thêm process và phụ thuộc môi trường image. gRPC probe phù hợp khi ứng dụng triển khai gRPC Health Checking Protocol và muốn kiểm tra service theo chuẩn đó.
| Kiểu | Ưu điểm | Giới hạn chính |
|---|---|---|
| HTTP | Rõ trạng thái, dễ đo và kiểm thử | Handler có thể phụ thuộc nhầm vào stack đang nghẽn |
| TCP | Đơn giản, không cần endpoint HTTP | Port mở không chứng minh ứng dụng phục vụ đúng |
| Exec | Kiểm tra được trạng thái cục bộ đặc thù | Tốn process, dễ phụ thuộc shell hoặc binary không có |
| gRPC | Phù hợp dịch vụ gRPC và hợp đồng health chuẩn | Ứng dụng phải triển khai protocol tương ứng |
Dù dùng cơ chế nào, probe phải xác định đúng port, scheme và giao thức thực tế. Không nên mở endpoint chẩn đoán chi tiết ra Internet chỉ để kubelet gọi. NetworkPolicy, bind address, ingress rule và cơ chế xác thực cần được thiết kế sao cho kubelet truy cập được mà bề mặt công khai vẫn tối thiểu.
8. Quan sát probe như một phần của SLO
Chỉ nhìn trạng thái Pod hiện tại sẽ bỏ lỡ những lần dao động trước đó. Hãy thu thập số lần probe thất bại theo loại và nguyên nhân, restart count, thời gian startup, thời gian Pod ở trạng thái not ready, số backend sẵn sàng, rollout duration và sự kiện kubelet. Ở tầng ứng dụng, đo latency của health handler nhưng không gắn nhãn có cardinality cao.
Log chuyển trạng thái thay vì log mọi probe thành công. Một probe mỗi vài giây trên hàng trăm Pod có thể tạo lượng log vô ích rất lớn. Khi chuyển từ ready sang not ready, ghi reason code ổn định, tuổi snapshot dependency, in-flight work và correlation với sự kiện deploy. Khi quay lại ready, ghi thời gian gián đoạn để đánh giá flapping.
- Cảnh báo khi tỷ lệ replica ready xuống dưới capacity tối thiểu, không chỉ khi bằng zero.
- Phân biệt restart do liveness, OOM, lỗi tiến trình và rollout chủ động.
- Theo dõi startup p95/p99 sau mỗi thay đổi image hoặc cấu hình.
- So sánh probe failure với dependency latency, CPU throttling và node pressure.
- Đặt dashboard cho trạng thái rollout để phát hiện Pod không bao giờ ready.
9. Kiểm thử failure mode trước production
Unit test xác nhận mapping trạng thái; integration test phải chứng minh hành vi của Pod. Tạo một deployment thử nghiệm, quan sát EndpointSlice hoặc backend Service, rồi gây lỗi có chủ đích. Mỗi kịch bản cần kỳ vọng rõ: Pod rút khỏi traffic hay container restart, mất bao lâu, request đang chạy ra sao và hệ thống phục hồi thế nào.
- Làm database chậm: readiness có thể fail theo chính sách nhưng liveness không được kéo mọi Pod vào restart.
- Làm event loop hoặc worker điều phối ngừng tiến triển: liveness phải phát hiện sau ngưỡng đã định.
- Kéo dài cold start: startup probe phải bảo vệ ứng dụng cho đến khi bootstrap xong.
- Gửi tải vượt capacity: xác nhận probe không bị starvation và không gây rút toàn bộ backend.
- Thực hiện rolling update: luôn duy trì đủ replica ready cho tải dự kiến.
- Xóa Pod dưới tải: request đang chạy hoàn tất hoặc thất bại theo hợp đồng, không nhận job mới sau khi draining.
Đặc biệt cần kiểm tra flapping. Một dependency chập chờn có thể khiến readiness đổi liên tục, làm endpoint routing biến động và tăng tải lên replica còn lại. Dùng nhiều mẫu, cache snapshot ngắn và hysteresis khi phù hợp, nhưng không che lỗi lâu đến mức gửi traffic vào instance thực sự hỏng.
Checklist triển khai
- Ba probe có mục tiêu và endpoint riêng, không dùng chung một kiểm tra sâu.
- Liveness chỉ phụ thuộc trạng thái mà restart container có thể cải thiện.
- Readiness phản ánh dependency bắt buộc, draining và capacity theo hợp đồng.
- Startup có ngân sách dựa trên cold-start đo được và thất bại hữu hạn.
- Health handler rẻ, timeout ngắn, không rò bí mật và không ghi log mỗi lần thành công.
- Rollout giữ đủ capacity; termination chuyển readiness trước khi đóng tiến trình.
- Metric phân biệt probe failure, restart cause, startup duration và ready replicas.
- Chaos test chứng minh dependency outage không tạo vòng restart hàng loạt.
Một thiết kế health check tốt làm cho quyết định tự động trở nên dễ đoán. Kubernetes chỉ hành động theo tín hiệu ứng dụng cung cấp; vì vậy chất lượng của probe nằm ở mô hình trạng thái và failure mode, không nằm ở việc endpoint trả JSON đẹp đến đâu. Hãy bắt đầu với hợp đồng tối giản, đo trên tải thật, rồi điều chỉnh ngưỡng bằng bằng chứng.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.