requestslimits 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.

CPU bị bóp, bộ nhớ bị giết, và ba lớp QoS

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.

requestslimits 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 requestslimits:

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:

  1. BestEffort — đuổi đầu tiên
  2. Burstable — đuổi khi đang dùng vượt requests
  3. Guaranteed — đuổi cuối cùng

Không khai requests khô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)

GiG 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.

LimitRangeResourceQuota

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.