CPU limits trên Kubernetes — vì sao thường không nên set
CPU nén được → bỏ limit để pod burst CPU rảnh; MEM không nén được → luôn set limit = request.
Vì sao "không nên set CPU limit" trên Kubernetes
Tài liệu kiến thức nền: vì sao right-sizing nên bỏ CPU limit nhưng giữ MEM request=limit. Tổng hợp các blog kinh điển + phản biện.
TL;DR (1 phút)
| Tài nguyên | Request | Limit | Lý do ngắn gọn |
|---|---|---|---|
| CPU | ✅ LUÔN set, đo chính xác | ❌ Thường KHÔNG nên set | CPU nén được (compressible). Limit chỉ gây throttle vô ích kể cả khi node còn rảnh CPU. |
| MEM | ✅ LUÔN set | ✅ LUÔN set, và = request | MEM không nén được (non-compressible). Vượt limit là OOMKill, không "mượn tạm" được. |
1. Khái niệm cốt lõi: compressible vs non-compressible
Đây là chìa khóa để hiểu toàn bộ tranh luận. Hai loại tài nguyên hành xử khác hẳn nhau:
CPU = tài nguyên NÉN ĐƯỢC (compressible) — và TÁI TẠO (renewable)
- CPU tính theo thời gian (core-giây). Phút này dùng 100% CPU không "tiêu hết" CPU của phút sau — mỗi khoảnh khắc CPU tự làm mới.
- Khi thiếu CPU, kernel chỉ làm chậm (throttle) tiến trình, không giết nó. Khi có CPU trở lại, tiến trình chạy tiếp bình thường.
- → Vì "lấy lại" CPU không gây hại, ta có thể cho pod mượn CPU rảnh của node thoải mái.
MEM = tài nguyên KHÔNG NÉN ĐƯỢC (non-compressible)
- RAM đã cấp cho tiến trình thì không lấy lại được mà không giết tiến trình (OOMKill).
- Khi pod vượt MEM limit → kernel OOMKill ngay. Không có khái niệm "throttle RAM".
- → MEM phải được quản chặt: set limit để 1 pod rò rỉ RAM không kéo sập cả node.
2. Bài kinh điển: "Stop Using CPU Limits" (Natan Yellin, Robusta)
Nguồn: home.robusta.dev/blog/stop-using-cpu-limits (bản tiếng Việt giới thiệu: nimtechnology.com).
Ẩn dụ "2 nhà thám hiểm trong sa mạc"
Marcus và Teresa đi giữa sa mạc, có 1 bình nước thần tạo 3 lít/ngày. Mỗi người cần 1 lít/ngày để sống. (Nước = CPU; chết khát = CPU starvation; throttle = bị chặn không cho uống.)
| Tình huống | Cấu hình | Kết quả |
|---|---|---|
| Chuyện 1 | KHÔNG request, KHÔNG limit | Marcus tham, uống sạch nước trước. Teresa chết khát. (1 pod ăn hết CPU, pod kia starvation.) |
| Chuyện 2 | CÓ limit (1 lít/người) | Teresa ốm, cần thêm nước. Còn dư 1 lít trong bình nhưng limit của cô là 1 lít/ngày → không được uống → chết. Tài nguyên có sẵn mà không được dùng. ← đây chính là tác hại của CPU limit. |
| Chuyện 3 | CÓ request, KHÔNG limit | Marcus ốm, cần thêm. Uống gần hết nhưng bị chặn khi còn đúng 1 lít (phần reserved theo request của Teresa). Teresa vẫn uống đủ 1 lít. Cả hai sống. ← cấu hình lý tưởng. |
Điểm mấu chốt (Chuyện 3): chỉ cần request là đủ để chống "pod tham ăn hết CPU". Mỗi pod luôn được đảm bảo phần CPU theo request của nó (kernel reserve sẵn). Limit không cần thiết cho mục đích cô lập này — nó chỉ chặn pod tận dụng CPU rảnh.
Trích tài liệu gốc Kubernetes (design proposal)
"Pods always get the CPU requested by their CPU request. They can take advantage of excess CPU too if they have no limit."
Nghĩa là: request = sàn được đảm bảo. Không có limit = pod được phép burst lên dùng CPU dư của node khi cần. Có limit = chặn cứng ở trần, dù node còn rảnh.
Kết luận của bài
- Dùng CPU request cho mọi workload.
- Đảm bảo request chính xác (phải đo).
- KHÔNG dùng CPU limit.
Và với MEM thì ngược lại hoàn toàn: luôn set mem limit, luôn set mem request, và để mem request = limit.
3. Phe phản biện — "Think twice before dropping CPU limits"
Bỏ CPU limit không phải luôn đúng 100%. Có vài trường hợp limit lại có ích. Để cân bằng:
3a. Runtime đọc cgroup để tự cấu hình số thread
Nguồn: hansihe.com/blog/kubernetes-cpu-limits
Vấn đề thật khi bỏ CPU limit với một số runtime:
- Go đọc CPU limit để đặt mặc định
GOMAXPROCS. - BEAM (Erlang/Elixir), và nhiều JVM cũ, đọc CPU limit để đặt số scheduler/worker thread.
- Nếu KHÔNG có CPU limit → runtime mặc định lấy theo SỐ CORE CỦA NODE.
Hệ quả: pod request 1 core chạy trên node 48 core → runtime spawn 48 thread. Dù chỉ thực sự dùng 1 core, 48 thread vẫn tạo overhead context-switch lớn → "lực cản nền" âm thầm, khó thấy trên CPU graph. Nhiều container như vậy trên 1 host → hàng trăm thread giành vài core.
GOMAXPROCS rõ ràng) ở mức ≥ CPU request và < số core node.
Khi đó dù có hay không CPU limit cũng không quá quan trọng — cái thật sự cần kiểm soát là thread pool, không phải cgroup quota.3b. Phản biện chính thức từ Kubernetes blog
Nguồn: The Case for Kubernetes Resource Limits (Predictability vs. Efficiency)
Lập luận: trong môi trường multi-tenant hoặc cần tính dự đoán được (predictability), limit giúp mỗi workload hành xử nhất quán giữa các môi trường (dev/staging/prod có cùng trần), đổi lại một phần hiệu suất. Đánh đổi là Predictability vs Efficiency.
Nhưng ngay cả phe này cũng đồng ý một anti-pattern cần tránh: đừng set CPU limit "khít" với mức sử dụng thực tế của workload — đó là cách chắc chắn gây throttle.
4. Vì sao điều này QUAN TRỌNG với bin-pack / Karpenter
Đây là lý do chủ đề này dính trực tiếp tới việc right-sizing requests:
- Karpenter pack node theo
request, KHÔNG theolimit. Request sai = Karpenter tính sai sức chứa node → hoặc phí tiền (CPU over-request), hoặc OOM (mem under-request). - CPU limit gây throttle "ảo". Nhiều service Java bursty — p95 cao gấp nhiều lần p50. Nếu kẹp CPU limit khít, chúng bị throttle đúng lúc cần xử lý spike → chậm/timeout, dù node còn CPU rảnh. → Bỏ CPU limit, để chúng burst.
- MEM phải req=limit khi dùng consolidation. Karpenter consolidation pack pod chặt theo request.
Nếu mem limit > request, pod có thể burst RAM vượt request → khi node bị pack sát, không còn RAM dư → OOMKill.
Đặt mem
request=limitkhiến "mức Karpenter nhìn" = "mức pod thực sự có thể ăn" → pack an toàn.
→ Tóm lại, nguyên tắc right-sizing áp dụng đúng điều này:
- CPU: chỉ chỉnh request (nâng cho service throttle, hạ cho service idle), không đặt limit.
- MEM: nâng request cho nhóm under-provisioned và đặt req=limit để chống OOM.
5. Cảnh báo & sắc thái (đừng làm máy móc)
| Cạm bẫy | Giải thích |
|---|---|
| "Bỏ limit = bỏ luôn request" | SAI. Request là bắt buộc — nó là sàn đảm bảo + là thứ Karpenter pack theo. |
Bỏ CPU limit mà không chỉnh GOMAXPROCS/thread pool | Runtime (Go/JVM/BEAM) có thể spawn thread = số core node → overhead context-switch. Set thread pool rõ ràng. |
| Đặt CPU limit khít p95 để "tiết kiệm" | Anti-pattern kinh điển → throttle thường trực. Kể cả phe ủng hộ limit cũng phản đối. |
| Bỏ MEM limit cho "linh hoạt" | Nguy hiểm: 1 pod rò RAM kéo sập node. MEM luôn cần limit. |
| Set request chỉ bằng "cảm tính"/copy default | Phải đo p95/max thực tế rồi mới set. |
6. Kết luận áp dụng
- CPU: set request theo usage thật (p95), không đặt CPU limit cho app workload.
- MEM: set request theo peak thật (×1.5 buffer), đặt limit = request.
- Service Java bursty: tuyệt đối không kẹp CPU limit; cân nhắc set
GOMAXPROCS/thread pool nếu thấy overhead. - Đo trước, chỉnh từ từ, để 1–2 cửa sổ bin-pack rồi đánh giá lại — không "big bang".
References
- Natan Yellin (Robusta) — For the Love of God, Stop Using CPU Limits on Kubernetes: home.robusta.dev/blog/stop-using-cpu-limits
- Bản giới thiệu tiếng Việt — nimtechnology.com: nimtechnology.com
- Robusta — Kubernetes memory limits best practices (mem req=limit): home.robusta.dev/blog/kubernetes-memory-limit
- hansihe (phản biện, cgroups/GOMAXPROCS) — Think Twice Before Dropping CPU Limits: hansihe.com/blog/kubernetes-cpu-limits
- K8s blog (phản biện chính thức) — The Case for Kubernetes Resource Limits: kubernetes.io/blog/2023/11/16
- Eliran (trung dung) — Learn when to use CPU limits: medium.com/@eliran89c
- Hacker News thảo luận: news.ycombinator.com/item?id=40856297
- K8s docs — Manage Resources for Containers: kubernetes.io/docs/.../manage-resources-containers
- EKS Best Practices — Karpenter (requests=limits cho non-CPU; accurate requests): docs.aws.amazon.com/eks/.../karpenter