requests và limits trông như một cặp tham số. Chúng là hai cơ chế khác nhau, và CPU với bộ nhớ lại được xử lý khác nhau nữa. Bài này đo cả bốn tổ hợp.
Vượt CPU: bị bóp, không bị giết
Một vòng lặp bận dùng 100% CPU, đổi limits.cpu:
limits.cpu |
Chu kỳ bị bóp | Tỉ lệ | RESTARTS |
|---|---|---|---|
100m |
360/360 | 100% | 0 |
500m |
406/406 | 100% | 0 |
1 |
33/348 | 10% | 0 |
Với limit=100m, container bị bóp ở mọi chu kỳ — nó chỉ được chạy 10% thời gian. Nhưng nó vẫn Running, và RESTARTS vẫn là 0.
Kernel dùng cgroup cpu.max để cấp một hạn ngạch cho mỗi chu kỳ 100 mili giây. Hết hạn ngạch thì tiến trình bị đình lại tới chu kỳ sau.
Vượt CPU không bao giờ làm container chết. Nó chỉ làm mọi thứ chậm lại — và chậm theo cách rất khó chẩn đoán, vì mọi chỉ số sức khoẻ đều xanh.
Vượt bộ nhớ: bị giết
Cùng cụm, giới hạn memory: 128Mi, ứng dụng cấp phát dần:
pod: 0/1 OOMKilled RESTARTS=2
lần trước kết thúc: OOMKilled exitCode=137
RESTARTS tăng đều — 2 rồi 3. Bộ nhớ không bóp được: khi tiến trình xin thêm mà cgroup không cho, cách duy nhất là giết nó.
Đây là khác biệt cốt lõi và nó quyết định cách bạn đặt hai loại giới hạn.
requests và limits làm hai việc khác nhau
requests — con số bộ lập lịch cộng lại để chọn node (phần 47), và là con số quyết định thứ tự bị đuổi khi node cạn tài nguyên. Nó không giới hạn gì lúc chạy.
limits — con số kernel áp lúc chạy. CPU thì bóp, RAM thì giết.
Phần 47 đã đo hệ quả của vế đầu: node có 16 CPU nhưng pod requests: cpu: "10" thì chỉ một pod mỗi node — dù nginx thật sự dùng vài phần nghìn CPU. Bộ lập lịch không nhìn mức dùng thật.
Ba lớp QoS
Kubernetes tự gán lớp dựa trên quan hệ giữa requests và limits:
| Pod | Cấu hình | Lớp |
|---|---|---|
qos1 |
requests = limits |
Guaranteed |
qos2 |
requests < limits |
Burstable |
qos3 |
không khai gì | BestEffort |
Thứ tự bị đuổi khi node cạn RAM:
BestEffort— đuổi đầu tiênBurstable— đuổi khi đang dùng vượtrequestsGuaranteed— đuổi cuối cùng
Không khai
requestskhông phải là "linh hoạt". Nó là tự nhận vé chết trước.
Đây là lý do thực dụng nhất để khai requests, và nó thuyết phục hơn mọi lập luận về lập lịch.
Khuyến nghị
requests: đặt bằng mức dùng thật ở p95. Đo, đừng đoán:
kubectl top pods --containers
Quá cao thì lãng phí node; quá thấp thì pod bị đuổi sớm và bị tranh tài nguyên.
limits.memory: luôn đặt, và đặt rộng. Nó là lưới chặn rò rỉ bộ nhớ làm chết cả node. Đặt khoảng 1,5–2 lần mức dùng đỉnh — vì vượt là chết, không có cảnh báo.
limits.cpu: cân nhắc bỏ hẳn. Đây là khuyến nghị gây tranh cãi, và phép đo ở trên là lý do.
Bóp CPU gây độ trễ mà không cứu ai cả: nếu node còn CPU rảnh, việc chặn pod dùng nó chẳng bảo vệ ai. Còn nếu node hết CPU thì kernel đã tự chia theo requests rồi — đó chính là việc cpu.weight làm.
Với requests khai đúng, bỏ limits.cpu cho ứng dụng dùng CPU rảnh khi cần, và vẫn được bảo đảm phần của mình khi node bận. Đổi lại, một pod xấu tính có thể ăn hết CPU rảnh — nên nó không phải quy tắc tuyệt đối, mà là đánh đổi đáng cân nhắc.
Trường hợp limits.cpu nên có: môi trường nhiều bên dùng chung mà bạn cần chi phí đoán trước được, hoặc ứng dụng có lỗi làm CPU tăng vọt.
Bốn đơn vị hay nhầm
cpu: "1" = 1 nhân
cpu: "500m" = nửa nhân (m = milli, 1000m = 1)
memory: "1Gi" = 1.073.741.824 byte (Gi = 2^30)
memory: "1G" = 1.000.000.000 byte (G = 10^9)
Gi và G chênh nhau 7%. Với giới hạn bộ nhớ, 7% là khoảng cách giữa "chạy được" và OOMKilled — và nó là lỗi gõ rất khó thấy khi đọc lại manifest.
LimitRange và ResourceQuota
Hai công cụ để không phụ thuộc vào việc mọi người nhớ khai:
apiVersion: v1
kind: LimitRange
metadata: {name: mac-dinh}
spec:
limits:
- type: Container
default: {cpu: 500m, memory: 512Mi} # limits nếu không khai
defaultRequest: {cpu: 100m, memory: 128Mi} # requests nếu không khai
LimitRange gán giá trị mặc định cho pod không khai — nghĩa là không còn pod BestEffort nào lọt vào namespace.
ResourceQuota chặn trần cho cả namespace, và nó có một tác dụng phụ hữu ích: khi namespace có quota về CPU hoặc RAM, Kubernetes bắt buộc mọi pod phải khai requests tương ứng, nếu không sẽ từ chối tạo.
Thử ba mươi giây
Tìm pod BestEffort trong cụm của bạn:
kubectl get pods -A -o custom-columns=\
NS:.metadata.namespace,TEN:.metadata.name,QOS:.status.qosClass --no-headers \
| grep BestEffort
Mỗi dòng là một pod sẽ bị đuổi đầu tiên khi node cạn bộ nhớ — kể cả khi nó là thứ quan trọng nhất bạn đang chạy.
Phần sau đo OOMKilled kỹ hơn: ứng dụng không nhận được tín hiệu nào, và Kubernetes không sinh event nào.