Node Allocatable: tài nguyên thật pod được dùng

K
Kai··5 min read

Bài 22 đặt requests/limits lên pod và nói scheduler dùng request để xếp lịch. Câu hỏi bỏ ngỏ: scheduler so request với cái gì trên node? Trực giác nói "với tổng tài nguyên máy", nhưng sai. Một worker 2 vCPU / 4 GiB không cho pod dùng trọn 2 vCPU / 4 GiB đó, vì bản thân node cần tài nguyên để chạy: hệ điều hành (sshd, udev...), các daemon Kubernetes (kubelet, containerd), và một khoản đệm để kubelet kịp xử lý khi RAM gần cạn. Phần còn lại sau khi trừ hết mới là thứ pod được dùng, gọi là Allocatable. Đây là bài nhìn resource management từ phía node, bổ cho góc nhìn pod của Bài 22.

Công thức Allocatable

Tài liệu định nghĩa Allocatable và công thức tính:

"'Allocatable' on a Kubernetes node is defined as the amount of compute resources that are available for pods. The scheduler does not over-subscribe 'Allocatable'."

Allocatable = Capacity − kube-reserved − system-reserved − eviction-threshold

Bốn thành phần:

  • Capacity — tổng tài nguyên vật lý node nhìn thấy.
  • kube-reserved — phần dành cho daemon của Kubernetes: "resource reservation for kubernetes system daemons like the kubelet, container runtime, etc."
  • system-reserved — phần dành cho daemon hệ điều hành: "resource reservation for OS system daemons like sshd, udev, etc."
  • eviction-threshold — khoản đệm: "By reserving some memory via evictionHard setting, the kubelet attempts to evict pods whenever memory availability on the node drops below the reserved value."

Và điểm cốt lõi cho scheduling: "The scheduler treats 'Allocatable' as the available capacity for pods." Scheduler chia Allocatable, không phải Capacity. Đặt một pod request 2 CPU lên node 2 CPU sẽ không lên được, vì Allocatable < 2.

Đọc Capacity vs Allocatable trên node thật

Hai con số này nằm ngay trong v1.Node. Đọc worker-0 của cụm:

kubectl get node worker-0 -o jsonpath='CAPACITY    cpu={.status.capacity.cpu} mem={.status.capacity.memory} pods={.status.capacity.pods}{"\n"}ALLOCATABLE cpu={.status.allocatable.cpu} mem={.status.allocatable.memory} pods={.status.allocatable.pods}{"\n"}'
CAPACITY    cpu=2 mem=3926020Ki pods=110
ALLOCATABLE cpu=2 mem=3823620Ki pods=110

Đọc kỹ: CPU Capacity = Allocatable = 2 (không cắt gì), nhưng memory Allocatable thiếu so với Capacity: 3926020 − 3823620 = 102400 Ki = đúng 100 Mi. Vì sao chỉ memory bị cắt, đúng 100 Mi? Vì ở Bài 11 khi dựng kubelet, ta không khai kubeReserved/systemReserved — nên hai khoản đó bằng 0, CPU không bị trừ. Nhưng kubelet có một eviction-hard mặc định memory.available<100Mi; chính nó cắt 100 Mi khỏi Allocatable memory. Kiểm chứng là node chẳng khai gì:

ssh worker-0 'sudo grep -E "Reserved|eviction|maxPods" /var/lib/kubelet/kubelet-config.yaml || echo "(không khai -> dùng mặc định)"'
# (không khai -> dùng mặc định)

Vậy con số 100 Mi kia hoàn toàn là default eviction threshold. pods=110 cũng là giới hạn mặc định số pod mỗi node.

Thêm reservation và xem Allocatable tụt

Lý thuyết công thức nên được thấy động. Ta thêm kubeReserved vào kubelet worker-0 — giả lập việc dành tài nguyên cho daemon Kubernetes — rồi xem Allocatable thay đổi (sao lưu config trước để revert):

ssh worker-0 'sudo cp /var/lib/kubelet/kubelet-config.yaml{,.bak}
  printf "kubeReserved:\n  cpu: \"200m\"\n  memory: \"256Mi\"\n" | sudo tee -a /var/lib/kubelet/kubelet-config.yaml
  sudo systemctl restart kubelet'
# chờ node báo lại status...
kubectl get node worker-0 -o jsonpath='ALLOCATABLE cpu={.status.allocatable.cpu} mem={.status.allocatable.memory}{"\n"}CAPACITY    cpu={.status.capacity.cpu} mem={.status.capacity.memory}{"\n"}'
ALLOCATABLE cpu=1800m mem=3561476Ki
CAPACITY    cpu=2 mem=3926020Ki

Công thức nghiệm đúng tới từng Ki. CPU: Allocatable = 2000m − 200m (kube-reserved) = 1800m. Memory: Allocatable = 3926020 − 262144 (256Mi kube-reserved) − 102400 (100Mi eviction) = 3561476 Ki. Capacity không đổi (vẫn 2 / 3926020Ki) vì phần cứng không suy chuyển; chỉ phần chia cho pod co lại đúng bằng khoản ta vừa dành ra. Đó là cách một quản trị viên "để dành" tài nguyên cho hệ thống, tránh tình huống pod ngốn sạch khiến kubelet/containerd đói rồi node lăn ra NotReady.

Trả config về cũ cho cụm sạch:

ssh worker-0 'sudo mv /var/lib/kubelet/kubelet-config.yaml.bak /var/lib/kubelet/kubelet-config.yaml && sudo systemctl restart kubelet'
# ALLOCATABLE trở lại cpu=2 mem=3823620Ki

Nối lại với requests, QoS và eviction

Giờ ghép với Bài 22. Khi scheduler đặt pod, nó cộng requests của các pod đã có trên node và chỉ thêm pod mới nếu tổng request ≤ Allocatable (đây là plugin NodeResourcesFit, sẽ đào ở phần Scheduling) — Allocatable chứ không phải Capacity, nên phần reserved thực sự "khóa" khỏi tầm với của pod. kubectl describe node worker-0 cho thấy bảng "Allocated resources" — tổng request/limit các pod đang chiếm so với Allocatable.

Và khi node vẫn cạn RAM dù đã tính toán (pod dùng vượt request, tới sát eviction-threshold), kubelet kích hoạt node-pressure eviction, đuổi pod theo thứ tự QoS của Bài 22 (BestEffort trước, Guaranteed sau). Eviction-threshold trong công thức Allocatable chính là cái mức kích hoạt đó: dành sẵn 100 Mi để kubelet còn "đất thở" mà ra tay dọn pod trước khi OOM killer của kernel (Bài 22) nhảy vào một cách thô bạo. Hai cơ chế bổ sung nhau: eviction là kubelet chủ động và lịch sự (tôn trọng QoS, có grace period), OOM kill là kernel bị động và tàn nhẫn (giết ngay tiến trình vượt limit). (Chi tiết thứ tự và tín hiệu eviction để dành bài eviction ở phần Scheduling.)

🧹 Dọn dẹp

Bài này không tạo object nào trong cluster, chỉ sửa kubelet worker-0 và đã revert về config gốc ở trên. Không có gì để xóa; cụm vẫn hai pod CoreDNS, hai node Ready với Allocatable như cũ.

Tổng kết

Node không cho pod dùng trọn Capacity. Allocatable = Capacity − kube-reserved − system-reserved − eviction-threshold, và scheduler chia Allocatable, không phải Capacity (không over-subscribe). Trên worker-0 ta thấy CPU Allocatable = Capacity = 2 (cụm không khai reserved), còn memory thiếu đúng 100 Mi = eviction-hard mặc định của kubelet. Thêm kubeReserved {cpu:200m, memory:256Mi} rồi restart kubelet, Allocatable tụt chính xác xuống 1800m / 3561476Ki (Capacity giữ nguyên), chứng minh công thức tới từng Ki. Phần reserved là cách bảo vệ daemon hệ thống/Kubernetes khỏi bị pod bỏ đói; eviction-threshold là khoản đệm để kubelet chủ động evict theo QoS (Bài 22) trước khi kernel OOM kill. Đây là mặt node của câu chuyện tài nguyên mà Bài 22 mở ra từ phía pod.

Bài 33 khép Part VI bằng các chính sách giới hạn ở tầng namespace: LimitRange (đặt mặc định/min/max request-limit cho pod trong một namespace) và ResourceQuota (chặn tổng tài nguyên/số object một namespace được dùng), công cụ để chia cụm cho nhiều team mà không ai giẫm chân ai.