Hai trường requests và limits trong pod spec nhìn giống nhau nhưng làm những việc rất khác nhau, và nhầm lẫn giữa chúng tạo ra phần lớn các vấn đề khó chịu trên Kubernetes: pod bị OOMKilled bí ẩn, API chậm lúc cao điểm, cluster “đủ tài nguyên mà scheduling fail”.
Bài này đi đủ sâu: scheduler dùng requests thế nào, kernel cgroup thực thi limits ra sao, khi nào nên đặt limit khi nào không, QoS class ảnh hưởng eviction ra sao, và cách đặt giá trị dựa trên số liệu chứ không phải đoán.
1. Hai con số, hai câu chuyện
1.1. requests — yêu cầu tối thiểu
- Scheduler dùng để quyết pod có nhét được vào node không.
- Node tổng hợp
requestscủa tất cả pod để biết còn bao nhiêu “slot” trống. - Không phải giới hạn gì về mặt runtime; pod có thể dùng nhiều hơn requests nếu node còn rảnh.
1.2. limits — giới hạn cứng
- Do kernel cgroup thực thi, per-container.
- Memory vượt limit → process bị OOMKilled (SIGKILL, không có tín hiệu chờ).
- CPU vượt limit → throttle (chậm xuống), không kill.
Một pod có thể có requests mà không có limits, hoặc ngược lại, hoặc cả hai bằng nhau.
1.3. Đơn vị
- CPU:
1= 1 core full;500m= 0.5 core;100m= 0.1 core. Không phải “tốc độ xung nhịp” — là thời gian CPU trong một cửa sổ (thường 100ms). - Memory: bytes (
512Mi,2Gi).Milà mebibyte (2^20),Mlà megabyte (10^6) — khác nhau ~4.8%.
2. CPU: cgroup CFS và throttling
2.1. Cơ chế
Linux CFS (Completely Fair Scheduler) dùng hai tham số trên cgroup:
cpu.cfs_period_us— cửa sổ (mặc định 100,000 = 100ms).cpu.cfs_quota_us— hạn mức trong cửa sổ đó.
Với limit = 500m → quota = 50,000us trong period 100,000us. Khi container dùng hết quota trước hết period, kernel đình (throttle) đến period sau.
2.2. Vì sao throttling gây latency spike
Ứng dụng Go/Java multi-threaded có thể dùng nhiều core song song trong burst ngắn — tổng CPU-time tích dồn nhanh. Với limit 500m, chỉ cần 5ms dùng 10 thread full → hết quota → bị pause tới 95ms.
Triệu chứng: P99 latency nhảy vọt, CPU util trung bình thấp (vì phần lớn thời gian đang đợi), container_cpu_cfs_throttled_seconds_total tăng.
2.3. Có nên đặt CPU limit?
Quan điểm thực tế (được Tim Hockin, Google, nhiều team lớn chia sẻ):
- Đặt CPU request luôn luôn (scheduler cần nó; QoS cần nó).
- CPU limit: thường không nên đặt, hoặc đặt rất rộng.
Lý do: CPU là compressible resource — thiếu CPU không làm app crash, chỉ chậm. Scheduler + request đảm bảo fairness; limit thêm vào chỉ tạo throttling không cần thiết khi node còn rảnh.
Ngoại lệ: workload cần dự đoán được (batch, test môi trường), hoặc multi-tenant nơi bạn phải chặn noisy neighbor.
2.4. Cách phát hiện throttling
Metric Prometheus:
sum by (pod) (
rate(container_cpu_cfs_throttled_seconds_total{pod=~"my-app-.*"}[5m])
)
Nếu giá trị này > 0 đáng kể, app đang bị throttle. Dashboard chuẩn nên có panel này cạnh CPU usage.
3. Memory: cgroup và OOMKilled
3.1. Khác CPU: memory không “throttle”
Process không thể “chạy chậm để dùng ít RAM”. Khi container chạm limit:
- Kernel cố gắng reclaim page cache.
- Nếu vẫn không đủ, OOM killer trong cgroup chọn process trong cgroup đó để kill.
- Thường là process chính → container exit với reason: OOMKilled.
Xem log:
kubectl describe pod <pod>
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
dmesg | grep -i oom
# Memory cgroup out of memory: Killed process ...
Exit code 137 = 128 + 9 (SIGKILL) — signature đặc trưng.
3.2. Node-level OOM
Khác với OOM trong cgroup: khi toàn node hết RAM (workload không có limit, kernel panic phòng ngự), node OOM killer chạy. Pod bị kill dựa vào oom_score_adj mà kubelet đặt dựa trên QoS class (mục 4).
3.3. Ước lượng memory cho ngôn ngữ
- Go: GC với
GOGC=100mặc định, memory ~ 2x live heap. ĐặtGOMEMLIMIT(Go 1.19+) thấp hơn limit ~10% để GC nhường chỗ. - JVM: container-aware từ JDK 10+, nhưng vẫn cần
-XX:MaxRAMPercentage=75hoặc tương tự. Off-heap (direct buffer, code cache) thường bị quên → limit nên cao hơn heap max. - Node.js:
--max-old-space-size=<MB>đặt heap V8, nhưng còn native module, buffer ngoài heap. - Python: không GC offheap, nhưng process fork (gunicorn workers) cộng dồn — tính per-worker × num-workers.
Không tính đúng → OOM lúc load cao.
3.4. Memory hay set limit
Khác CPU, memory nên có limit:
- Leak của một pod không tràn sang pod khác.
- OOM nội cgroup dễ debug hơn node OOM.
- Kubernetes có thể phản ứng (restart pod) thay vì toàn node sụp.
Giá trị: thường limit = request × 1.5 – 2. Quá sát request → dễ OOM burst; quá xa → QoS xuống Burstable (xem mục 4).
4. QoS class và eviction
Dựa vào requests/limits, kubelet gán mỗi pod một QoS class:
- Guaranteed: mọi container có
requests == limitscho cả CPU và memory. - Burstable: có ít nhất một request, nhưng request ≠ limit hoặc chỉ có một loại.
- BestEffort: không có request lẫn limit.
4.1. Eviction khi node thiếu tài nguyên
Khi node bị memory pressure (xuống dưới evictionHard), kubelet evict pod theo thứ tự:
- BestEffort trước.
- Burstable vượt quá requests.
- Guaranteed cuối cùng.
Nếu workload critical (API chính) mà đặt BestEffort, node bận chút là nó bị evict đầu tiên. Ngược lại, workload background (worker, batch) có thể để Burstable — chấp nhận bị ép khi cần.
4.2. oom_score_adj
Kubelet gán oom_score_adj cho container process, ảnh hưởng ai bị node OOM killer chọn:
- BestEffort: ~1000 (rất dễ bị giết).
- Burstable: tỷ lệ thuận với % memory request so với node.
- Guaranteed: -997 (gần như miễn nhiễm).
Thiết kế pod quan trọng thành Guaranteed là cách bảo vệ chúng.
5. Đặt giá trị dựa trên số liệu
Đừng đoán. Quy trình:
5.1. Đo baseline
Chạy app ở load thực tế (hoặc load test) vài ngày. Thu:
container_memory_working_set_bytesP95 / P99.container_cpu_usage_seconds_total→ CPU util trung bình và P95.container_cpu_cfs_throttled_seconds_total— phải ~0 nếu không đặt limit.
5.2. Công thức gợi ý
- CPU request ≈ P95 của CPU usage × 1.2 (buffer 20%).
- CPU limit: bỏ, hoặc đặt 4× request nếu bắt buộc.
- Memory request ≈ P95 working set × 1.2.
- Memory limit ≈ request × 1.5 (hoặc bằng request nếu muốn Guaranteed).
Công thức trên chỉ là khởi đầu. Review sau 1–2 tuần với dữ liệu thật, điều chỉnh.
5.3. Vertical Pod Autoscaler (VPA)
VPA (off mode Off hoặc Initial) cho khuyến nghị requests/limits dựa trên usage lịch sử. Dùng làm input khi điều chỉnh, không để nó auto apply trên workload production critical (restart pod để áp dụng).
5.4. Horizontal Pod Autoscaler (HPA)
HPA scale số replica theo metric (thường CPU %). Base của HPA là requests — targetCPUUtilizationPercentage: 70 nghĩa là nếu usage vượt 70% của requests thì scale lên. Requests sai → HPA behavior sai.
6. Trường hợp đặc biệt
6.1. Init containers
requests/limits tính riêng; init chạy xong thì resource trả lại. Nhưng trong thời gian init, scheduler dùng max(init[i].requests, sum(containers.requests)) để tính.
6.2. Sidecar (Kubernetes 1.29+)
Sidecar chạy suốt vòng đời pod → cộng thêm vào tổng resource pod yêu cầu.
6.3. Burstable CPU với node có nhiều core
Node 16 core, pod request 500m. Nếu không limit, pod có thể xài tới 16 core (nếu rảnh). Tốt cho burst, nhưng:
- Neighbor bị ép khi cần.
- Metric CPU util của pod có thể > 1000% (confusing).
Tính toán dung lượng cluster cần đảm bảo sum(requests) ≤ capacity × 80% để còn buffer.
6.4. LimitRange / ResourceQuota
Namespace-level:
- LimitRange: default request/limit cho pod không khai báo; min/max; ratio limit/request.
- ResourceQuota: trần tổng request/limit trong namespace.
Dùng để tránh team quên khai báo, hoặc vô tình chiếm cluster.
7. Debug thực tế
7.1. Pod bị OOMKilled
kubectl describe pod— xác nhận reason.dmesghoặcjournalctl -ktrên node — xem process nào bị killer chọn.- Metric memory trong giờ đó — rò rỉ? spike do request lớn?
- Nếu ngôn ngữ heap based: dump heap (Go pprof, JVM heap dump) khi gần limit.
- Quyết định: tăng limit hay sửa leak? Tăng limit mà leak → chỉ trì hoãn.
7.2. App chậm nhưng CPU util thấp
Dấu hiệu throttling:
- Throttled seconds tăng.
- Nhiều process trong state
R+chờ CPU nhưng container cgroup quota hết. toptrong pod thấy %CPU bị giới hạn.
Thử bỏ limit hoặc tăng nhiều, đo lại.
7.3. Pod pending lâu
kubectl describe thường ghi: “0/10 nodes available: Insufficient cpu/memory.”
- Node thực sự đầy → scale node, hoặc giảm request.
- Request quá lớn so với node → không bao giờ fit; cân nhắc chia workload.
- Node selector / taint chặn → kiểm tra affinity.
8. Checklist nhanh
- Mọi container có
requests(CPU + memory). - Memory có
limit, không quá sát request. - CPU limit: không đặt (hoặc rất rộng), trừ khi có lý do.
- QoS phù hợp với tầm quan trọng workload (API chính → Guaranteed).
- Dashboard có panel throttling + OOM events.
- HPA/VPA cấu hình đúng (hoặc tắt đúng).
- LimitRange + ResourceQuota cho namespace shared.
- Review requests/limits mỗi quý với data mới.
9. Tóm tắt
requestscho scheduler và HPA;limitscho cgroup/kernel.- CPU limit thường gây throttling không cần thiết — cân nhắc bỏ.
- Memory limit nên có; vượt → OOMKilled, không warning.
- QoS class quyết định eviction và oom_score; Guaranteed cho service quan trọng.
- Đặt giá trị dựa trên metric thật, không đoán; review định kỳ.
- Khi sự cố: bắt đầu từ
describe pod, throttle metric, memory working set, logs kernel.
Làm đúng requests/limits là đòn bẩy đơn giản mà tác động lớn: cluster tận dụng tốt hơn, pod ít bị killed bí ẩn, latency ổn định. Hầu hết cluster production “chạy kém” đều có ít nhất một trong ba lỗi: CPU limit quá chặt, memory request sai, QoS không phù hợp.