# 📊 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`.

```yaml
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`.

```mermaid
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`:**

```yaml
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:**

```yaml
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:**

```yaml
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:**

```yaml
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:

```bash
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:

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

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

```bash
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

```bash
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:

```bash
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`.

<details><summary>Xem đáp án</summary>

```yaml
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`.
</details>

**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?

<details><summary>Xem đáp án</summary>

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.
</details>

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

<details><summary>Xem đáp án</summary>

```bash
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.
</details>

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

<details><summary>Xem đáp án</summary>

```bash
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 `=`.
</details>

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

<details><summary>Xem đáp án</summary>

Hai lệnh xác nhận:

```bash
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.
</details>

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

```bash
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:

```bash
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:

```bash
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

```cloze
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

```flashcard
[
  { "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 đã.
