kube-scheduler làm đúng một việc: chọn node cho pod. Phần 43 đo được tắt nó thì pod mới nằm Pending mãi. Bài này đo cách nó chọn.

Tốc độ xếp chỗ, vai trò của requests, và cơ chế ưu tiên

Nhanh, và rải đều

9 pod trên cụm 3 worker:

3 sc-worker      3 sc-worker2      3 sc-worker3

Đều tuyệt đối. Bộ lập lịch chấm điểm node theo mức đã dùng, nên node trống được ưu tiên.

Thời gian từ lúc tạo pod tới lúc spec.nodeName có giá trị:

81 ms    75 ms    75 ms

Đã gồm 24 ms binary kubectl khởi động (phần 46), nên bộ lập lịch quyết định trong khoảng 50 mili giây.

Với cụm nhỏ thì việc xếp chỗ không bao giờ là nút thắt. Trên cụm hàng nghìn node nó tốn hơn, và Kubernetes có percentageOfNodesToScore để chỉ chấm điểm một phần node thay vì tất cả.

requests quyết định, không phải mức dùng thật

Đây là điều quan trọng nhất về bộ lập lịch, và cũng là chỗ hay bị hiểu sai nhất.

3 worker × 16 CPU cấp phát được = 48 CPU. Sáu pod nginx, mỗi pod requests: cpu: "10" → cần 60 CPU:

3 Running   (một pod mỗi worker)
3 Pending

0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 Insufficient cpu

nginx thật sự dùng vài phần nghìn CPU. Bộ lập lịch không quan tâm — nó chỉ cộng requests và so với allocatable.

Hai hệ quả đi ngược nhau:

Khai requests quá cao → node trông đầy trong khi thật ra rỗng, và bạn trả tiền cho máy không dùng tới. Đây là dạng lãng phí phổ biến nhất trên cụm Kubernetes.

Khai requests quá thấp hoặc không khai → node bị nhồi quá nhiều pod, và khi tất cả cùng bận thì chúng tranh nhau CPU và bộ nhớ. Pod không khai requests được xếp vào lớp BestEffort — cái đầu tiên bị giết khi node hết RAM.

Con số đúng đến từ đo mức dùng thật (kubectl top pods, hoặc VPA ở chế độ khuyến nghị), không đến từ đoán.

Chú ý thêm dòng lỗi: 1 node(s) had untolerated taint(s) là node control plane. Nó bị loại vì có vết, không phải vì thiếu tài nguyên — và thông báo nói rõ từng lý do cho từng nhóm node.

nodeSelector: ép cứng

Gắn nhãn lên một worker rồi ép pod lên đó:

kubectl label node sc-worker2 dia=ssd
# nodeSelector: {dia: ssd}
4 sc-worker2

Cả bốn pod lên một node, hai worker khác để trống.

nodeSelector là ràng buộc cứng: không node nào có nhãn thì pod nằm Pending vô thời hạn. Với ràng buộc mềm — "ưu tiên node có nhãn, nhưng không có cũng được" — phải dùng nodeAffinity với preferredDuringSchedulingIgnoredDuringExecution.

Cụm thật dùng nhãn cho ba việc: loại phần cứng (gpu=true, disk=ssd), vùng khả dụng (topology.kubernetes.io/zone), và tách môi trường.

Ưu tiên: pod quan trọng giết pod đang chạy

Đây là hành vi bất ngờ nhất với người mới.

Trạng thái ban đầu — sáu pod thường, mỗi pod xin 10 CPU:

3 Running   3 Pending

Thêm một pod với priorityClassName: cao (value: 1000000), cũng xin 10 CPU:

pod ưu tiên cao:  Running
pod thường:       2 Running   4 Pending

event:  Preempted by pod ... on node sc-worker2

Một pod đang chạy bị giết để nhường chỗ.

Đây là hành vi mặc định khi có PriorityClass, và nó đúng — pod quan trọng phải chạy được kể cả khi cụm đầy. Nhưng nó có nghĩa là:

  • Pod của bạn có thể bị giết bất cứ lúc nào bởi một pod ưu tiên cao hơn.
  • Việc đó không hiện ra như một lỗi của ứng dụng; nó là một event Preempted.
  • Ứng dụng phải chịu được bị giết đột ngột — cùng yêu cầu như khi node chết.

Tắt bằng preemptionPolicy: Never trên PriorityClass: pod đó vẫn được xếp trước trong hàng đợi nhưng không đuổi ai.

PodDisruptionBudget giới hạn số pod bị đuổi cùng lúc:

apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
  minAvailable: 2
  selector: {matchLabels: {app: quan-trong}}

Hai giai đoạn

Bộ lập lịch chạy hai bước cho mỗi pod:

Lọc — node nào chạy được pod này. Đủ tài nguyên chưa, có vết không chịu được không, nodeSelector có khớp không, volume có gắn được không.

Chấm điểm — trong số node còn lại, node nào tốt nhất. Ít tải nhất, đã có sẵn ảnh, phù hợp với podAntiAffinity.

Thông báo FailedScheduling nói rõ từng nhóm node trượt ở bước lọc vì sao. Đó là dòng hữu ích nhất khi pod kẹt ở Pending:

kubectl get events --field-selector reason=FailedScheduling -o custom-columns=MSG:.message

Ba lý do Pending thường gặp

Insufficient cpu / Insufficient memory — như đo ở trên. Kiểm requests trước khi kiểm cụm.

had untolerated taint — node có vết mà pod không khai toleration. Control plane luôn có vết; node đang bị drain cũng vậy.

node(s) didn't match Pod's node affinity/selector — nhãn không khớp. Thường là gõ sai tên nhãn.

Không có lý do nào trong ba cái này là "cụm hỏng". Cả ba đều là cấu hình, và cả ba đều nằm trong một dòng event.

Thử ba mươi giây

Xem cụm của bạn còn bao nhiêu chỗ theo cách bộ lập lịch nhìn:

kubectl describe nodes | grep -A5 "Allocated resources"

So Requests với Allocatable. Nếu Requests gần 100% mà kubectl top nodes cho thấy mức dùng thật dưới 20%, bạn đang trả tiền cho tài nguyên bị requests giữ chỗ mà không ai dùng — và đó là chỗ tiết kiệm lớn nhất trên hầu hết cụm.

Phần sau đo kubelet: từ lúc bộ lập lịch ghi tên node tới lúc container thật sự chạy.