Skip to content

Health Check Architecture — Probe đến từ đâu và "healthy" nghĩa là gì

Why this matters in production

Health check là cơ chế điều khiển luồng traffic, không phải một tính năng giám sát phụ trợ. Một backend chỉ nhận traffic khi và chỉ khi prober của Google đánh dấu nó healthy; khoảnh khắc nó bị đánh dấu unhealthy, LB ngừng gửi traffic mới tới nó, bất kể các backend khác còn capacity hay không. Nghĩa là: health check quyết định traffic đi đâu. Đây là lý do phần lớn sự cố "traffic không vào pod", "502 hàng loạt sau deploy", "phân phối lệch" truy nguyên về health check.

Sai lầm phổ biến nhất là cấu hình health check không phản ánh trạng thái thực của khả năng phục vụ: backend "healthy" theo GCP nhưng app bên trong đã treo (vì probe TCP chỉ kiểm tra port mở), hoặc backend "unhealthy" oan vì firewall chặn probe từ một dải IP mà kỹ sư không ngờ tới. Ba câu hỏi production mà chỉ người hiểu cơ chế mới trả lời đúng:

  • "Tôi đã mở firewall nhưng backend vẫn unhealthy." Mở cho dải nào? Probe đến từ 35.191.0.0/16130.211.0.0/22 — không phải từ IP của LB, không phải từ internet.
  • "Tôi đặt interval 10s nhưng log thấy probe mỗi 1–2s." Vì có nhiều prober dư thừa, mỗi prober probe độc lập theo interval của nó → tần suất tổng cao hơn nhiều lần.
  • "Backend mới mất ~10s mới nhận traffic dù app start trong 2s."healthy threshold cần nhiều probe thành công liên tiếp × interval mới chuyển trạng thái.

File này mổ xẻ cơ chế prober để bạn reason đúng thay vì đoán mò.

Internal model — distributed prober model

Probe không đến từ một nơi, mà từ một fleet prober dư thừa

Hình dung sai phổ biến: "có một con health checker probe backend của tôi mỗi interval giây". Thực tế, theo tài liệu health check, Google "gửi health check probe từ nhiều hệ thống dư thừa gọi là prober", và số lượng prober "có thể thay đổi, nhưng thường từ 5 đến 10".

Đây không phải chi tiết phụ — nó thay đổi cách bạn diễn giải hành vi probe. Hệ quả cơ chế quan trọng nhất: mỗi prober áp dụng interval và timeout độc lập. Nếu bạn đặt check-interval = 10s và hệ thống dùng 8 prober, backend của bạn nhận khoảng 8 probe mỗi 10 giây — tức trung bình gần 1 probe/giây, không phải 1 probe/10giây. Từ đó:

  • Log backend cho thấy tần suất probe cao hơn nhiều lần cấu hình → đây là bình thường, không phải bug hay tấn công.
  • Đặt interval quá thấp (ví dụ 1s) với 8 prober → ~8 probe/giây/backend → có thể gây tải đáng kể nếu endpoint /healthz không nhẹ (truy vấn DB, tính toán nặng).
  • Một vài probe lẻ tẻ fail (do một prober gặp blip mạng tạm thời) không lập tức đánh unhealthy, vì các prober khác vẫn thành công và đánh giá là tổng hợp từ nhiều prober.

Mô hình dư thừa này là có chủ đích: nó tránh việc một prober đơn lẻ gặp lỗi mạng làm đánh giá sai một backend khỏe. Health state là kết luận tổng hợp từ một fleet phân tán, không phải phán quyết của một probe đơn lẻ. Việc nhiều prober gửi từ nhiều vị trí cũng giúp phân biệt "backend chết hoàn toàn" (mọi prober fail) với "network partition cục bộ" (chỉ một số prober fail).

Probe đến từ đâu: dải IP nguồn và vì sao phải mở firewall

Đây là kiến thức vận hành quan trọng nhất của file, và là nguyên nhân số một của sự cố. Probe không đến từ IP của load balancer, không từ internet, mà từ các dải IP dành riêng của Google, được route đặc biệt vào VPC của bạn qua các "special route" nằm ngoài VPC (bạn không thấy chúng trong route table thường).

PROXY LB (Application LB, Proxy NLB) và phần lớn LB — probe (và một phần traffic) từ:
   IPv4:  35.191.0.0/16  và  130.211.0.0/22
   IPv6:  2600:2d00:1:1::/64  và  2600:2d00:1:b029::/64

REGIONAL PASSTHROUGH NLB (backend-service & target-pool) — probe từ dải KHÁC:
   IPv4:  35.191.0.0/16,  209.85.152.0/22,  209.85.204.0/22

Ba điều phải nắm:

  1. Firewall ingress phải allow các dải này tới backend, trên port của health check. Nếu không, mọi probe bị drop → backend mãi unhealthy → không nhận traffic. Điều này độc lập với việc LB internal hay external: một internal LB cũng cần mở firewall cho dải prober. Đây là ~70% nguyên nhân của "backend unhealthy không rõ lý do".

  2. Passthrough NLB dùng dải khác (209.85.152.0/22, 209.85.204.0/22 ngoài 35.191.0.0/16). Một lỗi tinh vi: kỹ sư mở firewall cho 130.211.0.0/22 + 35.191.0.0/16 (đúng cho proxy LB) rồi áp y nguyên cho passthrough NLB — thiếu dải 209.85.x → probe passthrough bị chặn. Phải tra đúng dải theo loại LB.

  3. GKE Ingress controller tự tạo firewall rule này; LB dựng thủ công thì không. Khi bạn dùng Ingress/Gateway, controller tự thêm rule allow prober. Khi bạn dựng LB bằng gcloud/Terraform, bạn phải tự tạo:

bash
gcloud compute firewall-rules create allow-health-checks \
  --network=my-vpc \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:8080 \
  --source-ranges=130.211.0.0/22,35.191.0.0/16

Các tham số định lượng — và tác động lên thời gian chuyển trạng thái

Một health check có bốn tham số định lượng. Với GKE, chúng cấu hình qua BackendConfig (Ingress) hoặc HealthCheckPolicy (Gateway):

yaml
apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
  name: api-backendconfig
spec:
  healthCheck:
    checkIntervalSec: 5      # interval per-prober
    timeoutSec: 5            # phải ≤ checkIntervalSec
    healthyThreshold: 2
    unhealthyThreshold: 2
    type: HTTP
    requestPath: /healthz
    port: 8080
Tham sốMặc địnhÝ nghĩa
checkIntervalSec5sThời gian giữa hai probe liên tiếp từ một prober
timeoutSec5sThời gian tối đa chờ phản hồi một probe; quá hạn = probe fail. Phải ≤ checkIntervalSec, nếu không API từ chối
healthyThreshold2Số probe thành công liên tiếp để chuyển unhealthy → healthy
unhealthyThreshold2Số probe thất bại liên tiếp để chuyển healthy → unhealthy

Điểm cơ chế quan trọng nhất, và là nơi nhiều người tính sai: các threshold này quyết định độ trễ phát hiện thay đổi trạng thái. Có thể tính được, không phải đoán:

  • Thời gian một backend mới được coi healthyhealthyThreshold × checkIntervalSec. Với mặc định (2 × 5s = 10s), một backend vừa start không nhận traffic ngay khi app sẵn sàng — nó phải pass 2 probe liên tiếp, tốn tối thiểu ~10s. Đây là lý do "app start trong 2s nhưng 10s+ mới nhận traffic". Muốn lên nhanh hơn, giảm interval (đánh đổi: tải probe cao hơn).

  • Thời gian phát hiện một backend chếtunhealthyThreshold × checkIntervalSec (+ tối đa timeoutSec cho probe cuối). Với mặc định (~10–15s), một backend chết đột ngột vẫn nhận traffic trong cửa sổ này trước khi bị rút → một số request fail. Đây là trade-off cốt lõi: threshold thấp phát hiện nhanh nhưng dễ false-positive (flapping khi mạng nhiễu); threshold cao ổn định nhưng phát hiện chậm.

Hiểu công thức này cho phép thiết kế có chủ đích: muốn phát hiện chết trong ~5s? Đặt interval 2s, unhealthy-threshold 2, timeout ~1–2s. Nhưng nhớ: interval thấp × nhiều prober = tải probe cao. Mọi điều chỉnh là một trade-off định lượng.

Protocol — kiểm tra "port mở" hay "app phục vụ được"

GCP có hai category health check: health checks (khuyến nghị; scope regional & global; protocol gRPC, TCP, SSL, HTTP, HTTPS, HTTP/2) và legacy health checks (chỉ HTTP/HTTPS, chỉ global; theo tài liệu, "chỉ target-pool-based regional external passthrough NLB cần legacy health check"). Tránh legacy trừ khi bị target pool legacy ép.

Chọn protocol đúng là một quyết định về cái gì được kiểm tra, không chỉ cú pháp:

  • TCP chỉ kiểm tra "port có chấp nhận connection không". App có thể accept TCP nhưng đã deadlock/treo GC dài → probe TCP vẫn xanh, traffic vẫn vào, request timeout. TCP check là liveness mức mạng, không phải khả năng phục vụ.
  • HTTP/HTTPS tới /healthz kiểm tra app thực sự xử lý và trả code trong dải [200, 300). Đây là mức kiểm tra gần "phục vụ được" hơn.
  • gRPC dùng giao thức health checking chuẩn (grpc.health.v1.Health.Check) — backend phải implement RPC này.
  • Ngay cả HTTP check trả 200 cho /healthz vẫn có thể che giấu việc app lỗi 500 cho traffic thật (ví dụ một dependency hỏng mà /healthz không chạm). Đây chính là lý do Envoy-based LB bổ sung outlier detection (file 03) — kiểm tra lỗi trên traffic thật, không chỉ probe.

Lưu ý ràng buộc GKE: BackendConfig của Ingress chỉ hỗ trợ HTTP/HTTPS/HTTP2 cho health check; cần gRPC/TCP health check thì dùng HealthCheckPolicy của Gateway API.

Quy tắc thiết kế: health check nên kiểm tra càng gần khả năng phục vụ thật càng tốt, nhưng endpoint phải nhẹ (vì bị probe rất thường xuyên). Đừng để /healthz gọi xuống mọi dependency — nếu không, một dependency chậm sẽ làm toàn bộ fleet "đỏ" dù app còn phục vụ được phần lớn request.

Health checking khác nhau giữa proxy LB và passthrough LB

Vì hai họ datapath khác nhau, ngữ nghĩa health check cũng khác:

  • Proxy LB (Application/Proxy NLB): prober probe backend; với NEG-based backend, probe tới đúng (Pod IP : containerPort) — mỗi Pod được đánh giá độc lập, một Pod unhealthy không ảnh hưởng routing tới Pod khác. Với instance group backend, probe tới (node IP : nodePort) — node "healthy" không đảm bảo Pod trên node đó healthy (đây là điểm yếu cố hữu của non-NEG, xem file 05).

  • Passthrough NLB: khi một backend bị đánh unhealthy, nó bị loại khỏi bảng consistent hashing → connection mới không tới nó nữa. Nhưng connection đang chạy (đã có trong connection tracking) vẫn có thể tiếp tục tới backend đó tùy connectionPersistenceOnUnhealthyBackends (file 04). Tức "unhealthy" trong passthrough chỉ chặn cái mới, không tự cắt cái cũ. Dải probe cũng khác (209.85.x), và trong một số cấu hình passthrough, app phải bind vào VIP của LB để nhận được probe (do DSR giữ IP đích là VIP).

  • Ngoại lệ Cloud Service Mesh: thay vì prober tập trung, "Cloud Service Mesh lập trình Envoy proxy thực hiện health check cho internet NEG và hybrid NEG" — health check phân tán tới chính Envoy thay vì prober trung tâm. Hữu ích khi đích nằm ngoài Google Cloud (prober trung tâm khó reach).

Constraints, trade-offs & failure modes

Trạng thái health và "outage khi mọi backend unhealthy"

Một backend trong backend service có thể ở các trạng thái: HEALTHY (nhận traffic), UNHEALTHY (không nhận), DRAINING (đang connection draining — không nhận cái mới nhưng phục vụ nốt cái cũ, xem file 08). Một backend service được coi còn phục vụ nếu có ít nhất một backend HEALTHY.

Failure mode nghiêm trọng nhất: mọi backend cùng lúc unhealthy → LB không còn đích → trả 502/503. Nguyên nhân thường gặp: (a) rolling update tắt Pod cũ trước khi Pod mới kịp pass healthyThreshold; (b) health check quá strict (probe / phụ thuộc dependency, dependency chậm → toàn bộ đỏ); (c) một thay đổi firewall vô tình chặn dải prober trên mọi backend. Vì health check điều khiển traffic, một sai sót cấu hình health check có thể tự gây outage toàn service mà không backend nào thực sự chết.

Failure mode: firewall — checklist theo xác suất

Khi một backend "unhealthy không rõ lý do", duyệt theo thứ tự xác suất:

  1. Firewall chặn dải prober. Chưa allow 35.191.0.0/16 + 130.211.0.0/22 (proxy LB) hoặc thiếu 209.85.x (passthrough). → ~70% trường hợp.
  2. Sai port/path. Probe port 8080 nhưng app nghe 80; hoặc /healthz trả 404/redirect (3xx không nằm trong dải healthy [200,300)).
  3. App chưa kịp pass threshold. Backend mới cần healthyThreshold × interval; app start chậm hơn → trượt liên tục, không bao giờ ổn định.
  4. Protocol sai. HTTPS check tới port chỉ nói HTTP, hoặc ngược lại; gRPC check tới app không implement health RPC.

Failure mode: flapping do threshold quá nhạy

Đặt unhealthyThreshold = 1 + interval thấp khiến một probe fail đơn lẻ (do GC pause, network blip) lập tức rút backend, rồi probe sau pass lại đưa vào — backend "flap" liên tục, traffic dao động, connection bị reset mỗi lần rút. Đây là lý do mặc định threshold = 2 (cần liên tiếp). Khi tinh chỉnh để phát hiện nhanh, đừng hạ threshold xuống 1 trừ khi chấp nhận flapping; thay vào đó giảm interval và giữ threshold ≥ 2.

Trade-off ba chiều: phát hiện nhanh ↔ tải probe ↔ ổn định

Ba mục tiêu mâu thuẫn: (a) phát hiện chết nhanh → interval thấp, threshold thấp; (b) giảm tải probe lên backend → interval cao, ít prober; (c) tránh flapping → threshold cao. Không thể tối ưu cả ba. Lựa chọn điển hình cho web service: interval 5s, timeout 5s, unhealthy-threshold 2–3, healthy-threshold 2 — cân bằng phát hiện ~10–15s với ổn định tốt. Service nhạy cảm latency cần phát hiện nhanh hơn phải chấp nhận tải probe cao hơn (và endpoint health phải cực nhẹ).

Common mistakes / anti-patterns

Anti-pattern 1 — Mở firewall cho 0.0.0.0/0 hoặc cho IP của LB thay vì dải prober. Hiểu lầm: nghĩ probe đến từ LB hoặc từ internet. Hậu quả: hoặc backend mãi unhealthy (mở sai dải), hoặc lỗ hổng bảo mật (mở quá rộng). Sửa: allow chính xác 35.191.0.0/16 + 130.211.0.0/22 (proxy) hoặc thêm 209.85.x (passthrough), chỉ trên port health check.

Anti-pattern 2 — Health check / thay vì endpoint chuyên dụng nhẹ. Hiểu lầm: dùng trang chủ làm health endpoint. Hậu quả: mỗi probe (rất thường xuyên do nhiều prober) chạy logic nặng/query DB → tải thừa; hoặc / phụ thuộc dependency làm health "đỏ" khi dependency chậm dù app còn phục vụ được → tự gây outage. Sửa: /healthz nhẹ, kiểm tra liveness cốt lõi, không gọi dependency nặng.

Anti-pattern 3 — Tin TCP health check là đủ. Hiểu lầm: "port mở = app khỏe". Hậu quả: app treo (deadlock, GC dài) nhưng vẫn accept TCP → health xanh, traffic vẫn vào, request timeout. Sửa: dùng HTTP/gRPC health check kiểm tra app thực sự phản hồi; cân nhắc outlier detection cho lỗi trên traffic thật.

Anti-pattern 4 — Bỏ qua readiness gate cho startup chậm. Hiểu lầm: để Pod nhận traffic ngay khi K8s Ready. Hậu quả: app cần warmup dài hơn interval × threshold → Pod bị đánh unhealthy ngay khi vừa lên rồi flap. Sửa: dùng Pod readiness gate của container-native LB (file 05) — Pod chỉ Ready khi LB đã xác nhận healthy, không nhận traffic non.

References