📊 Resource requests, limits và QoS class

Mục tiêu học tập

Sau bài này, bạn phải làm được những việc sau, và làm được dưới áp lực thời gian của phòng thi:

  • Phân biệt rõ requests và limits, biết cái nào scheduler dùng, cái nào kubelet dùng lúc runtime.
  • Viết đúng YAML resources cho container, kể cả khi đề chỉ cho một câu mô tả bằng tiếng Anh.
  • Xác định QoS class của một pod chỉ bằng cách đọc YAML, không cần chạy cluster.
  • Giải thích được hai triệu chứng quen thuộc nhất: pod Pending vì thiếu tài nguyên và pod bị OOMKilled / Evicted.
  • Dùng kubectl describe node, kubectl describe pod, kubectl set resources để sửa nhanh trong đề thi.

Ôn tập nhanh

Bài trước bạn đã dựng DaemonSet, Job và CronJob. Điểm chung quan trọng: cả ba đều nhúng một pod template giống hệt Deployment, nên mọi thứ trong bài này áp dụng y nguyên cho spec.template.spec.containers[].resources.

Một lưu ý nối tiếp bài trước: DaemonSet chạy một pod trên mỗi node, nên nếu bạn đặt requests quá cao, pod DaemonSet sẽ Pending ở những node nhỏ. Đây là lỗi rất hay gặp khi làm đề CKA.

[!NOTE] Bài này chỉ nói về tài nguyên của container. Việc ép pod chạy đúng node theo nhãn hay theo taint sẽ học ở bài sau, đừng trộn hai thứ vào nhau khi làm bài thi.

Lý thuyết

requests và limits là hai cơ chế khác nhau

Nhiều bạn mới học gộp hai thứ này làm một, rồi làm sai cả loạt câu. Hãy nhớ hai câu:

  • requests là lời khai báo với scheduler: "hãy xếp tôi vào node nào còn trống ít nhất chừng này". Scheduler chỉ nhìn con số này, nó không biết container thực sự dùng bao nhiêu.
  • limits là mệnh lệnh cho kubelet và cgroup: "được dùng tối đa chừng này, vượt là bị xử lý".

Hệ quả thực tế: một pod có thể Pending dù cả cluster còn rất nhiều CPU rảnh, chỉ vì không node đơn lẻ nào còn đủ chỗ theo requests.

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

CPU và memory hành xử rất khác nhau khi vượt limit

Đây là chỗ đề thi hay hỏi và cũng là chỗ dễ mất điểm nhất.

Tài nguyên Bản chất Vượt limit thì sao?
CPU Nén được (compressible) Container bị throttle, chạy chậm lại, không bị kill
Memory Không nén được Container bị kill, reason OOMKilled, exit code 137, rồi restart theo restartPolicy

Đơn vị cần nhớ: CPU 1 = 1000m, nên 250m là một phần tư core. Memory dùng Mi/Gi (bội số 1024), đừng viết M/G khi đề cho Mi.

==Khi memory vượt limit, container bị kill ngay chứ không bị làm chậm.==

Ba QoS class

Kubernetes tự gán QoS class cho mỗi pod dựa trên resources của các container. Bạn không đặt field này, nó là read-only ở status.qosClass.

flowchart TD
    Start["Đọc YAML của Pod"] --> A{"Mọi container có cả requests và limits cho CPU và memory?"}
    A -->|Không| B{"Có container nào có ít nhất một requests hoặc limits?"}
    B -->|Có| BU["🟡<br/>Burstable"]
    B -->|Không| BE["🔴<br/>BestEffort"]
    A -->|Có| C{"requests bằng limits ở mọi container?"}
    C -->|Có| G["🟢<br/>Guaranteed"]
    C -->|Không| BU
QoS class Điều kiện chính xác Vị trí khi evict
Guaranteed Mọi container đặt cả requests và limits cho cả CPU và memory, và hai giá trị bằng nhau Bị evict cuối cùng
Burstable Không đạt Guaranteed, nhưng có ít nhất một container đặt requests hoặc limits Bị evict ở giữa
BestEffort Không container nào đặt requests hay limits Bị evict đầu tiên

Mẹo nhớ: Guaranteed là all-or-nothing. Thiếu một field memory ở một container là rớt xuống Burstable ngay.

Vì sao pod bị Evicted

Khi node bị áp lực tài nguyên (MemoryPressure, DiskPressure), kubelet phải đuổi bớt pod. Thứ tự xử lý:

  1. Pod BestEffort.
  2. Pod Burstable đang dùng vượt requests của nó (vượt càng nhiều càng bị đuổi sớm).
  3. Pod Guaranteed.

Pod bị đuổi có status.phase: Failed, reason: Evicted, và sự kiện trên pod ghi The node was low on resource: memory.

Phân biệt cho chắc, vì đề rất thích gài hai khái niệm này:

  • OOMKilled: do limit của container bị vượt. Xảy ra ở cấp container.
  • Evicted: do node hết tài nguyên. Xảy ra ở cấp pod, và eviction dựa trên mức dùng so với requests.

Vì sao pod Pending vì tài nguyên

Scheduler duyệt từng node, trừ dần requests của các pod đã chạy. Nếu không node nào còn đủ, pod nằm Pending với event:

0/3 nodes are available: 3 Insufficient cpu.

Ba cách sửa, xếp theo thứ tự nên thử trong phòng thi:

  1. Giảm requests xuống mức hợp lý.
  2. Giảm số replica nếu đề cho phép.
  3. Thêm node hoặc tăng tài nguyên node (hiếm khi đề yêu cầu vì tốn thời gian).

Ví dụ có hướng dẫn

Ví dụ 1: Đọc YAML và gọi tên QoS class

1a. Không có resources:

containers:
- name: app
  image: nginx

Không container nào đặt gì, nên đây là BestEffort.

1b. Có cả hai nhưng khác nhau:

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 200m
    memory: 256Mi

Đủ bốn field nhưng requests khác limits, nên là Burstable.

1c. Bốn field trùng khớp:

resources:
  requests:
    cpu: 200m
    memory: 256Mi
  limits:
    cpu: 200m
    memory: 256Mi

Đây là Guaranteed.

1d. Bẫy kinh điển, chỉ CPU bằng nhau:

resources:
  requests:
    cpu: 200m
  limits:
    cpu: 200m
    memory: 256Mi

Thiếu requests.memory, nên không phải Guaranteed. Kết quả là Burstable. Trong phòng thi, gặp câu hỏi QoS thì luôn đếm đủ bốn field trước khi trả lời.

Ví dụ 2: Pod Pending vì requests quá cao

Đề bài kiểu: "Deployment web có 3 replicas, mỗi pod requests.cpu: 2, toàn bộ node chỉ còn 1 CPU trống. Sửa để pod chạy được."

Bước 1, xác nhận triệu chứng:

kubectl get pods
# web-7d9f-abcde   0/1   Pending   0   45s

kubectl describe pod web-7d9f-abcde | tail -n 5
# Events:
#   Warning  FailedScheduling  default-scheduler  0/3 nodes are available: 3 Insufficient cpu.

Bước 2, sửa bằng lệnh imperative, không cần mở editor:

kubectl set resources deployment web --requests=cpu=200m

Bước 3, kiểm tra rollout và trạng thái:

kubectl rollout status deployment web
kubectl get pods -o wide

Lưu ý: kubectl set resources sẽ kích hoạt rolling update. Nếu đề yêu cầu không downtime thì đó lại là điều tốt.

Ví dụ 3: Container bị OOMKilled

kubectl describe pod api-0 | grep -A3 "Last State"
# Last State:  Terminated
#   Reason:    OOMKilled
#   Exit Code: 137

Nghĩa là container đã chạm limits.memory: 128Mi. Cách sửa:

kubectl set resources deployment api --limits=memory=512Mi --requests=memory=256Mi

Ghi nhớ: 137 = 128 + 9 = bị kill bởi SIGKILL. Gặp exit code này thì nghĩ ngay tới memory limit, đừng nghĩ tới CPU.

Luyện tập có hướng dẫn

Làm từng bài, tự viết lệnh ra giấy trước, rồi mở đáp án để so. Đây chính là nhịp độ bạn sẽ làm trong phòng thi.

Bài 1. Viết YAML một pod tên guaranteed-pod, image nginx, đạt đúng QoS class Guaranteed với CPU 250m và memory 64Mi.

Xem đáp án
apiVersion: v1
kind: Pod
metadata:
  name: guaranteed-pod
spec:
  containers:
  - name: nginx
    image: nginx
    resources:
      requests:
        cpu: 250m
        memory: 64Mi
      limits:
        cpu: 250m
        memory: 64Mi

Kiểm tra: kubectl get pod guaranteed-pod -o jsonpath='{.status.qosClass}' phải trả về Guaranteed.

Bài 2. Cluster có 3 node, mỗi node còn trống 500m CPU. Bạn tạo Deployment 5 replicas với requests.cpu: 300m cho mỗi pod. Hỏi có bao nhiêu pod chạy được, bao nhiêu Pending, và vì sao?

Xem đáp án

Mỗi node chỉ chứa được 1 pod: pod thứ hai cần thêm 300m, tổng 600m > 500m trống.

Vậy 3 pod chạy được, 2 pod Pending. Event sẽ là 0/3 nodes are available: 3 Insufficient cpu. Cách sửa nhanh nhất: giảm requests.cpu xuống 150m hoặc giảm replicas xuống 3.

Bài 3. Một Deployment tên worker đang là BestEffort. Hãy đưa nó về Burstable bằng một lệnh kubectl, không dùng file YAML.

Xem đáp án
kubectl set resources deployment worker --requests=cpu=100m,memory=128Mi

Chỉ cần đặt requests, không cần limits, là đủ rời khỏi BestEffort. Nếu muốn chắc chắn, thêm --limits=cpu=200m,memory=256Mi, khi đó vẫn là Burstable vì requests khác limits.

Bài 4. Tạo nhanh một pod tên probe bằng lệnh imperative, image nginx, requests là 100m CPU và 128Mi, limits là 200m CPU và 256Mi. Sau đó in ra QoS class.

Xem đáp án
kubectl run probe --image=nginx \
  --requests=cpu=100m,memory=128Mi \
  --limits=cpu=200m,memory=256Mi

kubectl get pod probe -o jsonpath='{.status.qosClass}'
# Burstable

Mẹo thi: cặp flag --requests và --limits nhận chuỗi dạng cpu=...,memory=..., không có dấu cách quanh dấu =.

Bài 5. Node worker-2 báo MemoryPressure. Bạn thấy nhiều pod Evicted nhưng không pod nào OOMKilled. Hãy nêu hai lệnh để xác nhận nguyên nhân và một cách giảm rủi ro cho các pod quan trọng.

Xem đáp án

Hai lệnh xác nhận:

kubectl describe node worker-2 | grep -A5 "Conditions"
kubectl get pods -A --field-selector status.phase=Failed -o wide

Hoặc xem sự kiện của một pod cụ thể: kubectl describe pod <name> | grep -i evict, sẽ thấy The node was low on resource: memory.

Cách giảm rủi ro: đặt requests bằng limits cho cả CPU và memory để đưa các pod quan trọng lên Guaranteed, và đặt requests sát với nhu cầu thật để eviction không nhắm vào chúng trước.

Góc chuyên gia

Vài thứ mình ước mình biết trước lần thi đầu:

  • Đếm đủ bốn field trước khi kết luận QoS. Câu hỏi QoS gần như luôn có một bẫy nhỏ: hoặc thiếu requests.memory, hoặc requests khác limits ở đúng một container.
  • limits không được nhỏ hơn requests. API server từ chối thẳng với lỗi must be less than or equal to. Nếu bạn gặp lỗi này khi kubectl apply, đừng đi tìm lỗi ở chỗ khác.
  • requests cao "cho chắc" là cách tự tạo pod Pending. Trong lab thì không sao, trong đề thi thì mất điểm. Hãy đặt requests bằng mức app cần khi chạy bình thường, còn limits mới là mức trần.
  • kubectl describe node là vũ khí chính. Nhìn mục Allocated resources để biết node còn bao nhiêu theo requests, nhanh hơn nhiều so với đoán:
kubectl describe node <node-name> | grep -A8 "Allocated resources"
  • LimitRange có thể phá vỡ kế hoạch của bạn. Nếu namespace có LimitRange, pod không đặt resources vẫn có thể được inject default và trở thành Burstable. Gặp trường hợp "YAML của tôi trống mà QoS không phải BestEffort", kiểm tra ngay:
kubectl get limitrange -n <namespace>
  • ResourceQuota khác Pending. Vượt quota thì pod bị từ chối ngay khi create, thông báo exceeded quota. Pod Pending là chuyện của scheduler, không phải quota. Đọc kỹ thông báo lỗi để không sửa sai hướng.
  • Một lệnh xem nhanh toàn namespace, rất hữu ích khi đề yêu cầu kiểm tra nhiều pod:
kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass,CPU_REQ:.spec.containers[*].resources.requests.cpu
  • Exit code 137 chỉ nói "bị SIGKILL". Nó thường là OOMKilled, nhưng cũng có khi là kubectl delete hoặc probe fail. Luôn đọc thêm Reason trong Last State trước khi kết luận.

[!WARNING] Đừng dành quá 3 phút cho một bài tài nguyên. Nếu sửa requests mà pod vẫn Pending, đọc lại event: có thể nguyên nhân thật là nodeSelector, taint, hoặc PVC chưa bind, chứ không phải CPU. Ghi lại bài đó và quay lại sau.

Cloze để tự kiểm tra

Scheduler chọn node dựa trên {{c1::requests}} của mọi container trong pod.
Khi container vượt CPU limit, nó bị {{c2::throttle}} chứ không bị kill.
Khi container vượt memory limit, nó bị kill với reason {{c3::OOMKilled}}.
Pod không đặt bất kỳ requests hay limits nào thuộc QoS class {{c4::BestEffort}}.
Pod có mọi container đặt requests bằng limits cho cả CPU và memory thuộc QoS class {{c5::Guaranteed}}.
Khi node bị áp lực tài nguyên, pod {{c6::BestEffort}} bị evict đầu tiên.

Flashcard ôn tập

[
  { "front": "requests dùng để làm gì?", "back": "Là con số scheduler dùng để chọn node còn đủ tài nguyên." },
  { "front": "limits dùng để làm gì?", "back": "Là mức trần kubelet và cgroup áp lên container lúc chạy." },
  { "front": "Vượt CPU limit thì chuyện gì xảy ra?", "back": "Container bị throttle, chạy chậm lại, không bị kill." },
  { "front": "Vượt memory limit thì chuyện gì xảy ra?", "back": "Container bị kill với reason OOMKilled, exit code 137, rồi restart." },
  { "front": "Ba QoS class của Kubernetes là gì?", "back": "Guaranteed, Burstable và BestEffort." },
  { "front": "Điều kiện để một pod đạt Guaranteed?", "back": "Mọi container đặt cả requests và limits cho cả CPU và memory, và requests bằng limits." },
  { "front": "Pod BestEffort được định nghĩa thế nào?", "back": "Không container nào đặt requests hay limits." },
  { "front": "Thứ tự evict khi node bị áp lực tài nguyên?", "back": "BestEffort trước, rồi Burstable vượt requests, Guaranteed sau cùng." },
  { "front": "Event 'Insufficient cpu' nghĩa là gì?", "back": "Không node nào còn đủ CPU theo requests, pod nằm Pending." },
  { "front": "Lệnh in ra QoS class của pod?", "back": "kubectl get pod <name> -o jsonpath='{.status.qosClass}'" },
  { "front": "1 CPU bằng bao nhiêu millicores?", "back": "1000m." },
  { "front": "Lệnh đổi requests và limits cho Deployment?", "back": "kubectl set resources deployment <name> --requests=... --limits=..." },
  { "front": "Xem node còn bao nhiêu tài nguyên theo requests ở đâu?", "back": "kubectl describe node <name>, mục Allocated resources." },
  { "front": "Khác nhau giữa OOMKilled và Evicted?", "back": "OOMKilled do vượt limit của container, Evicted do node hết tài nguyên." }
]

Tóm tắt và bước tiếp theo

Bốn điều cần mang ra khỏi bài này:

  1. requests là chuyện của scheduler, limits là chuyện của runtime.
  2. CPU vượt limit thì bị throttle, memory vượt limit thì bị OOMKilled.
  3. QoS class xét trên mọi container: Guaranteed cần đủ bốn field và bằng nhau, Burstable là có ít nhất một field, BestEffort là không có gì.
  4. Pending vì tài nguyên thì đọc event Insufficient cpu/memory rồi giảm requests, đừng đoán mò.

Bài sau sẽ nối tiếp tự nhiên: khi pod đã đủ tài nguyên nhưng vẫn chưa rơi vào node bạn muốn, lúc đó mới dùng nodeSelector, affinity, và taint/toleration. Bạn sẽ thấy nhiều đề CKA trộn hai chủ đề này trong cùng một câu, nên nắm chắc bài hôm nay trước đã.