GC, cgroup v2, Swap và Tắt Node Có Trật Tự

K
Kai··5 min read

Phần lớn bài về kubelet (Bài 11) nói nó chạy pod. Nhưng kubelet còn quản tài nguyên node liên tục ở phía sau: dọn rác khi đĩa đầy, đặt pod vào cgroup và ép giới hạn, xử lý swap, và dừng pod có trật tự khi node tắt. Bốn việc này âm thầm cho tới khi sai thì cụm gặp rắc rối khó đoán. Bài này soi từng cái trên worker thật.

cgroup v2: nơi limit thành hiện thực

Bài 22 khai resources.limits, nhưng giới hạn đó thật sự được ép ở cgroup của kernel. Cụm chạy trên cgroup v2 — kiểm bằng kiểu hệ thống tập tin của /sys/fs/cgroup:

ssh worker-0 'stat -fc %T /sys/fs/cgroup'
cgroup2fs

cgroup2fs là cgroup v2 (v1 trả tmpfs); từ Kubernetes 1.35 cgroup v1 đã deprecated. Kubelet xếp pod vào cây cgroup dưới kubepods.slice, tách theo QoS (Bài 22):

ssh worker-0 'ls -d /sys/fs/cgroup/kubepods.slice/*/ | xargs -n1 basename'
kubepods-besteffort.slice
kubepods-burstable.slice

Pod Guaranteed nằm thẳng dưới kubepods.slice, Burstable và BestEffort vào nhánh riêng — đúng ba lớp QoS. Tạo một pod Burstable (limit 256Mi, 500m CPU) rồi tìm cgroup của nó:

# pod uid=0905ccf2-..., qos=Burstable
ssh worker-0 'D=$(find /sys/fs/cgroup/kubepods.slice -type d -name "*pod0905ccf2*")
  cat $D/memory.max; cat $D/cpu.max'
dir: kubepods-burstable-pod0905ccf2_8a12_445b_a13b_43a8b4a15427.slice
memory.max = 268435456        # = 256Mi
cpu.max    = 50000 100000     # = 0.5 CPU (quota 50000 / chu kỳ 100000)

memory.max đúng bằng 256Mi tính ra byte, cpu.max là cặp quota/period cho 0.5 CPU. Đây là chỗ limits từ YAML thành luật kernel: vượt memory.max thì OOM kill (Bài 22), vượt cpu.max thì bị throttle. Kubelet tự ghi các file này khi tạo pod, không cần cấu hình.

Image garbage collection

Image kéo về chất đống trên đĩa node. Kubelet tự dọn theo ngưỡng đĩa. Xem config hiệu lực qua /configz của kubelet:

kubectl get --raw /api/v1/nodes/worker-0/proxy/configz | jq '.kubeletconfig |
  {imageGCHighThresholdPercent, imageGCLowThresholdPercent, imageMinimumGCAge}'
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s

Cơ chế: khi đĩa dùng vượt 85%, kubelet xóa image không dùng (cũ nhất trước) cho tới khi tụt dưới 80%, bỏ qua image trẻ hơn 2 phút. worker-0 đang giữ 30 image; chừng nào đĩa chưa chạm 85% thì kubelet không đụng. Container đã chết cũng được dọn tương tự. Đây là lý do không nên coi image trên node là bền — kubelet xóa khi cần chỗ.

Swap: vì sao mặc định bị chặn

Kiểm swap trên node:

ssh worker-0 'free -h | grep -i swap'
kubectl get --raw .../configz | jq '.kubeletconfig.failSwapOn'
Swap:   0B   0B   0B
failSwapOn: true

Swap tắt (0B), và failSwapOn: true nghĩa là kubelet từ chối khởi động nếu node có swap bật. Lý do lịch sử: swap phá vỡ giả định tài nguyên của scheduler và QoS — một pod tưởng bị giới hạn RAM lại âm thầm tràn ra swap, làm hiệu năng khó đoán và phá ngữ nghĩa OOM. Kubernetes có tính năng NodeSwap (beta) cho phép dùng swap có kiểm soát, nhưng phải cấu hình memorySwap rõ ràng; mặc định vẫn là không swap. Cụm dựng từ đầu nên tắt swap trên node trước khi cài kubelet — nếu không, kubelet không lên.

Graceful node shutdown

Khi node tắt (bảo trì, scale down), pod trên đó nên được dừng tử tế chứ không bị cắt phụt. Xem config:

kubectl get --raw .../configz | jq '.kubeletconfig | {shutdownGracePeriod, shutdownGracePeriodCriticalPods}'
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s

0s nghĩa là tắt — đây là mặc định, và là một khoảng trống của cụm tự dựng. Khi tính năng bật (đặt shutdownGracePeriod > 0), kubelet bắt tín hiệu tắt máy của systemd (qua inhibitor lock), ngừng nhận pod mới, rồi gửi SIGTERM cho pod theo thứ tự — pod thường trước, pod critical sau — chờ trong khoảng grace rồi mới để node tắt. Không bật thì khi node tắt, pod bị giết đột ngột và Service phải đợi health check phát hiện mới gỡ endpoint, gây lỗi tạm. Phân biệt với drain (Bài 63): drain là chủ động dời pod trước khi can thiệp; graceful shutdown là kubelet phản ứng khi node đang tắt mà chưa kịp drain.

🧹 Dọn dẹp

kubectl delete namespace cg-demo

Bài này chỉ tạo một pod để xem cgroup, phần còn lại là đọc cấu hình node. Không sửa gì trên node. Lệnh dùng trong bài ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 64-node-internals.

Tổng kết

Kubelet quản node ở bốn mặt ngầm. cgroup v2 (cgroup2fs) là nơi limits thành luật kernel: kubelet xếp pod vào kubepods.slice theo QoS và ghi memory.max/cpu.max đúng bằng giới hạn (256Mi → 268435456, 500m → 50000 100000). Image GC dọn image không dùng khi đĩa vượt 85%, về dưới 80%, bỏ qua image dưới 2 phút. Swap mặc định bị chặn (failSwapOn: true) vì nó phá giả định tài nguyên của scheduler và QoS; dùng swap cần NodeSwap có cấu hình. Graceful node shutdown (shutdownGracePeriod) mặc định 0s — tắt — nên cụm tự dựng nên bật để pod được SIGTERM theo thứ tự khi node tắt, thay vì bị cắt phụt; nó bù cho drain ở những lần tắt không kịp drain.

Tới đây cụm đã được sao lưu (Bài 62), biết nâng cấp (Bài 63), và quản tài nguyên node (bài này). Phần còn lại của Part XIII là quan sát: làm sao biết bên trong cụm đang xảy ra gì. Bài 65 bắt đầu với logging — log container nằm đâu, kubelet xoay nó thế nào, và mô hình thu log toàn cụm.

Related Posts