Sau bài này bạn làm được: cấu hình health check đúng để LB không gửi traffic vào instance chết; giải thích connection draining và tại sao thiếu nó gây 502 khi deploy; chọn L4 hay L7 cho từng tình huống.
Load balancer là gì và tại sao cần
Khi có nhiều instance/Pod phục vụ cùng service, cần thành phần phân phối request — đó là Load Balancer:
[Client]
│
▼
[Load Balancer: 1.2.3.4:443]
├──▶ [App instance 1: 10.0.2.10:8080]
├──▶ [App instance 2: 10.0.2.11:8080]
└──▶ [App instance 3: 10.0.2.12:8080]
LB đảm bảo:
- Không có SPOF (single point of failure) ở tầng app.
- Scale out: thêm/xóa instance mà client không biết.
- Health checking: tự loại instance bị lỗi khỏi pool.
L4 vs L7 Load Balancer
| L4 (TCP/UDP) | L7 (HTTP/HTTPS) | |
|---|---|---|
| Nhìn tới tầng | TCP/UDP | HTTP header, URL, cookie |
| TLS | Pass-through hoặc terminate | Thường terminate |
| Routing | Theo IP:port | Theo Host, Path, Header |
| Hiệu suất | Cao hơn (ít xử lý) | Thấp hơn (parse HTTP) |
| Dùng cho | Database proxy, game, streaming | Web API, microservice |
| Ví dụ | AWS NLB, TCP mode HAProxy | AWS ALB, NGINX, Traefik |
Khi nào dùng L4:
- Cần preserve client IP một cách thuần túy.
- Protocol không phải HTTP (MQTT, Redis, PostgreSQL wire protocol).
- Cần throughput cực cao, latency cực thấp.
Khi nào dùng L7:
- HTTP/HTTPS và cần routing theo path/host.
- Cần ghi log chi tiết (URL, method, status code).
- A/B test, canary deploy theo header.
gRPC cần LB biết HTTP/2
Lưu ý quan trọng: gRPC chạy trên HTTP/2, mở một connection rồi ghép nhiều RPC (stream) vào đó và giữ kết nối lâu. Nếu dùng L4 LB (NLB) với chế độ round-robin theo connection, tất cả RPC của một client sẽ đổ vào một backend → mất cân bằng nghiêm trọng khi có ít client nhưng nhiều RPC.
Giải pháp:
- L7 LB biết HTTP/2 (Envoy, NGINX với
grpc_pass, ALB hỗ trợ gRPC, Istio, Linkerd) — load balance theo stream/request không theo connection. - Hoặc client-side load balancing với service discovery (gRPC-LB, xDS từ Envoy).
- Trên Kubernetes: Service
ClusterIP+ iptables load balance theo connection → gRPC cũng lệch tải; dùng headless Service + DNS round-robin hoặc service mesh để phân tải theo request.
Gateway API — thay thế Ingress ở L7
Với K8s production mới, cân nhắc Gateway API (xem bài 10) thay vì Ingress annotation vendor: tách vai trò platform (Gateway) khỏi team app (HTTPRoute), hỗ trợ weighted routing, gRPC, TCP native. Controller điển hình: Envoy Gateway, Istio, NGINX Gateway Fabric, Cilium.
Thuật toán phân tải
| Thuật toán | Cách hoạt động | Phù hợp |
|---|---|---|
| Round Robin | Lần lượt theo thứ tự | Request xử lý nhanh, đồng đều |
| Least Connections | Gửi tới instance ít connection nhất | Request xử lý lâu, khác nhau |
| IP Hash | Hash IP client → instance cố định | Session affinity |
| Weighted | Phân theo trọng số | Canary (10% traffic mới) |
Health check — trái tim của LB
LB định kỳ probe từng instance. Nếu fail → loại khỏi pool; khi OK lại → thêm vào pool.
[LB] ─── probe mỗi 10s ──▶ [Instance: GET /health → 200 OK]
[Instance: GET /health → 503] ← loại khỏi pool
[Instance: timeout 5s] ← loại khỏi pool
Cấu hình health check quan trọng
| Tham số | Ý nghĩa | Giá trị điển hình |
|---|---|---|
| Path | Endpoint probe (HTTP) | /health, /ready, /ping |
| Interval | Tần suất probe | 10–30s |
| Timeout | Thời gian chờ response | 5s |
| Healthy threshold | Số lần OK liên tiếp để vào pool | 2–3 |
| Unhealthy threshold | Số lần fail để loại khỏi pool | 2–3 |
Health check endpoint tốt
GET /health → 200 OK
{
"status": "ok",
"db": "ok",
"cache": "ok"
}
GET /health → 503 Service Unavailable (khi DB không reach được)
Không dùng endpoint phức tạp (query DB nặng) làm health check — gây quá tải khi LB probe liên tục.
Phân biệt /health vs /ready:
/health(liveness): app còn sống không? Nếu fail → restart pod (K8s)./ready(readiness): app sẵn sàng nhận traffic không? Nếu fail → không gửi traffic, nhưng không restart.
Connection draining / deregistration delay
Khi LB remove một instance (deploy mới, scale down):
Không có draining:
[LB] ─── stop gửi mới ──▶ [Instance cũ: đang xử lý 50 request]
[LB] ─── đóng connection ngay ──▶ 50 request bị cắt → client 502
Với connection draining (60s):
[LB] ─── stop gửi MỚI ──▶ [Instance cũ]
[LB] ─── chờ tối đa 60s ──▶ [Instance hoàn thành 50 request hiện tại]
[LB] ─── đóng sau khi drain xong ──▶ Không có 502
Cấu hình điển hình:
- AWS ALB: “Deregistration delay” = 30–60s.
- Kubernetes:
terminationGracePeriodSeconds+preStophook.
Graceful shutdown trong app
App cũng phải hỗ trợ graceful shutdown để không cắt request giữa chừng:
[SIGTERM nhận được]
1. Dừng nhận request mới (close listening socket hoặc trả 503)
2. Chờ request đang xử lý hoàn thành
3. Đóng kết nối DB / cache
4. Exit 0
Node.js ví dụ:
process.on("SIGTERM", () => {
server.close(() => {
db.disconnect().then(() => process.exit(0));
});
// Force exit nếu quá lâu
setTimeout(() => process.exit(1), 30000);
});
Tóm tắt
- L4 cho TCP/UDP thuần; L7 cho HTTP routing và visibility.
- Health check đúng endpoint và threshold = không có traffic vào instance xấu.
- Connection draining = deploy không 502; không có draining = cắt connection bất ngờ.
- App phải handle SIGTERM gracefully — LB draining và app graceful shutdown phải khớp nhau.
Câu hỏi hay gặp
Health check gọi DB mỗi lần probe — có vấn đề không?
Trả lời: Có thể rất tải DB (30×10×instance = số query/phút). Nên endpoint nhẹ (process sống, file tĩnh) hoặc cached check; DB chỉ khi thật cần và throttle.
terminationGracePeriodSeconds: 30 nhưng request dài nhất 60s — điều gì xảy ra?
Trả lời: Sau 30s kubelet SIGKILL (sau SIGTERM); request có thể bị cắt — client 502/reset. Cần tăng grace period, preStop delay, hoặc drain ở LB trước.
Chỉ có L4 NLB mà muốn tách /api và /admin — làm được không?
Trả lời: L4 không đọc path HTTP. Cần L7 (ALB, Ingress NGINX, HAProxy HTTP mode) hoặc hai listener/port khác nhau nếu tách bằng cổng.
Bài tiếp theo (Giai đoạn IV): TLS tự động trong cluster với cert-manager — tự động hóa vòng đời chứng chỉ để không bao giờ bị hết hạn bất ngờ.