Node-pressure eviction
Part VII gần khép. Bài 37 cho thấy preemption — scheduler đá pod ưu tiên thấp lúc xếp lịch. Nhưng còn một kiểu "đuổi pod" khác, xảy ra khi cluster đang chạy ổn định bỗng một node cạn tài nguyên thật: node-pressure eviction. Kubelet chủ động giết pod để cứu node, khác hẳn preemption (scheduler, vì priority) và OOM kill của Bài 22 (kernel, vì pod vượt limit của chính nó). Phân biệt được ba cơ chế "pod bị giết" này là mục tiêu của bài.
Kubelet canh tài nguyên node
Tài liệu định nghĩa: "Node-pressure eviction is the process by which the kubelet proactively terminates pods to reclaim resource on nodes." và "The kubelet monitors resources like memory, disk space, and filesystem inodes ... When one or more of these resources reach specific consumption levels, the kubelet can proactively fail one or more pods on the node to reclaim resources and prevent starvation." Khi evict, "the kubelet sets the phase for the selected pods to Failed, and terminates the Pod."
Kubelet theo dõi qua các eviction signal, chính là vế bên phải của ngưỡng:
memory.available— RAM còn trống (chính làCapacity − workingSetcủa Bài 32).nodefs.available/nodefs.inodesFree— dung lượng / inode còn của filesystem node.imagefs.available— dung lượng cho image/layer.pid.available— số PID còn (nối Bài 33).
Ngưỡng eviction có dạng [signal][toán-tử][lượng], ví dụ memory.available<100Mi. Hai loại:
- hard — "the kubelet uses a
0sgrace period (immediate shutdown)" — vượt là giết ngay. - soft — tôn trọng
eviction-soft-grace-period/eviction-max-pod-grace-period, cho pod chút thời gian.
Mặc định kubelet có sẵn hard threshold memory.available<100Mi (chính cái cắt 100Mi khỏi Allocatable mà ta thấy ở Bài 32).
Tạo áp lực bộ nhớ thật
Để thấy eviction thật, ta phải làm node thật sự thiếu RAM. Làm an toàn: đặt một ngưỡng demo trên worker-0 sao cho việc trục xuất đúng pod gây áp lực sẽ giải tỏa được (không lan sang pod khác). worker-0 đang có memory.available ≈ 3390Mi. Đặt ngưỡng hard memory.available<2500Mi:
ssh worker-0 'printf "evictionHard:\n memory.available: \"2500Mi\"\n" | sudo tee -a /var/lib/kubelet/kubelet-config.yaml
sudo systemctl restart kubelet'
kubectl get node worker-0 -o jsonpath='MemoryPressure={..conditions[?(@.type=="MemoryPressure")].status}'
# MemoryPressure=False (3390Mi > 2500Mi, chưa áp lực)
Node vẫn yên (available 3390 > ngưỡng 2500). Giờ thả một pod BestEffort (không khai resources, Bài 22) ghi 1500Mi vào tmpfs, ghim cứng vào worker-0:
apiVersion: v1
kind: Pod
metadata: {name: memhog}
spec:
nodeName: worker-0 # ghim thẳng, bỏ qua scheduler
restartPolicy: Never
containers:
- name: c
image: busybox:1.36
command: ["sh","-c","dd if=/dev/zero of=/m/f bs=1M count=1500; sleep 600"]
volumeMounts: [{name: m, mountPath: /m}]
volumes:
- {name: m, emptyDir: {medium: Memory}} # tmpfs -> tính vào RAM node
Poll node và pod:
t=1: worker-0.MemoryPressure=False memhog=Running
t=2: worker-0.MemoryPressure=False memhog=Running
t=3: worker-0.MemoryPressure=True memhog=Failed/Evicted
memhog ghi 1500Mi tmpfs → memory.available tụt xuống dưới 2500Mi → kubelet đặt node MemoryPressure=True → và evict memhog. Pod chuyển Failed với reason Evicted. Thông điệp nói rõ mọi thứ:
kubectl get pod memhog -o jsonpath='{.status.message}'
The node was low on resource: memory. Threshold quantity: 2500Mi, available: 2543640Ki.
Container c was using 1369520Ki, request is 0, has larger consumption of memory.
Đọc kỹ: ngưỡng 2500Mi, lúc đo available 2543640Ki (đã sát ngưỡng), và lý do chọn memhog — Container c was using 1369520Ki, request is 0, has larger consumption of memory. Đó là thứ tự xếp hạng kubelet dùng để chọn nạn nhân: pod nào dùng vượt request nhất bị giết trước. memhog là BestEffort (request is 0) lại đang ngốn 1.3Gi — vượt request "vô cùng", nên đứng đầu danh sách hi sinh.
Thứ tự eviction và vì sao CoreDNS sống sót
Kubelet xếp hạng nạn nhân theo: (1) pod có vượt request hay không, (2) Pod Priority (Bài 37), (3) mức dùng so với request. Ánh xạ ra QoS của Bài 22: BestEffort (không request) bị đuổi trước, rồi Burstable vượt request, cuối cùng Guaranteed và Burstable trong request. Trên worker-0 cũng có một pod CoreDNS — kiểm tra nó sau cú evict:
kubectl get pods -n kube-system -o wide | grep coredns
coredns-...-pqzsx Running worker-0 # KHÔNG bị đụng
CoreDNS không bị evict, dù ở chung node đang MemoryPressure. Vì: nó dùng rất ít RAM (trong request), lại mang priority system-cluster-critical (Bài 37) — xếp cuối danh sách. Và quan trọng: trục xuất memhog đã giải phóng 1.3Gi → available vọt lại trên 2500Mi → áp lực tan → kubelet dừng, không cần đụng tới ai khác. Đây là lý do ta thiết kế ngưỡng nằm giữa "available khi không có hog" và "available khi có hog": evict đúng thủ phạm là đủ.
Ba kiểu "pod bị giết" — đừng lẫn
Giờ gom ba cơ chế ta đã gặp, vì chúng hay bị nhầm:
AI giết? VÌ SAO? TÔN TRỌNG?
OOM kill (B22) kernel container vượt MEMORY LIMIT — (tức thì, exitCode 137)
preemption (B37) scheduler pod priority CAO cần chỗ graceful termination
node-pressure kubelet NODE cạn tài nguyên (signal) KHÔNG PDB, KHÔNG
eviction (B38) vượt ngưỡng eviction terminationGracePeriodSeconds
Tài liệu nhấn điểm cuối: node-pressure eviction "is not the same as API-initiated eviction" và "The kubelet does not respect your configured PodDisruptionBudget or the pod's terminationGracePeriodSeconds." — khác hẳn kubectl drain (Bài 23) vốn đi qua Eviction API và tôn trọng PDB. Khi node đang cháy, kubelet không có thời gian lịch sự. Và nó self-heal: nếu pod bị evict thuộc một Deployment/StatefulSet, controller tạo pod thay thế (thường trên node khác) — đúng vòng reconcile của Bài 17.
🧹 Dọn dẹp
ssh worker-0 'sudo mv /var/lib/kubelet/kubelet-config.yaml.bak /var/lib/kubelet/kubelet-config.yaml && sudo systemctl restart kubelet'
kubectl delete pod memhog --now
Trả ngưỡng eviction về mặc định (đã revert — node worker-0 trở lại MemoryPressure=False), xóa pod thử. Cụm về lại hai pod CoreDNS, hai node Ready. Manifest ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 38-node-pressure-eviction.
Tổng kết
Node-pressure eviction là kubelet chủ động giết pod khi node cạn tài nguyên thật — theo các eviction signal (memory.available, nodefs.available, pid.available...) so với ngưỡng (hard = giết ngay, soft = có grace). Ta đặt ngưỡng memory.available<2500Mi trên worker-0, thả một pod BestEffort ghi 1500Mi tmpfs → node MemoryPressure=True → kubelet evict đúng pod đó (Failed/Evicted), với message chỉ rõ ngưỡng, available, và lý do "request is 0, has larger consumption" — tức xếp hạng theo vượt-request → priority → mức dùng (BestEffort trước, critical sau, nên CoreDNS sống sót). Khác preemption (scheduler/priority) và OOM kill (kernel/limit), eviction không tôn trọng PDB hay terminationGracePeriodSeconds. Đây là tuyến phòng thủ cuối của node, và self-heal nhờ controller tạo lại pod.
Hết Part VII. Part VIII sang autoscaling — thay vì giết pod khi quá tải, thêm pod (hoặc node): Bài 39 cài metrics-server (add-on đầu tiên ta thêm vào cụm) rồi dựng HorizontalPodAutoscaler để tự tăng/giảm số bản sao theo tải CPU thật.