ข้ามไปยังเนื้อหา

Scheduler กับ Resource Requests

Scheduler วาง Pod ลงบน Node ที่มี capacity ว่างพอจะรองรับ requests ของตัวเอง ส่วน limits คือเพดานที่จำกัดว่า Pod ใช้ resource ได้มากแค่ไหนตอนที่รันอยู่จริง — และเมื่อไม่มี capacity เหลือพอที่ไหนเลย priority กับ preemption จะเป็นตัวตัดสินว่า Pod ที่ priority ต่ำกว่าจะถูก evict เพื่อเปิดที่ว่างหรือไม่

ทุก container ใน Pod ประกาศ resources.requests และ resources.limits สำหรับ CPU และ memory ได้ และตัวเลขสองตัวนี้ทำหน้าที่ต่างกันโดยสิ้นเชิง

  • requests คือขั้นต่ำที่ container ต้องการแน่ ๆ scheduler จะนับ Node เป็นตัวเลือกก็ต่อเมื่อ Node นั้นมี capacity ที่ ยังไม่ถูกจอง พอจะครอบคลุม requests ของทุก container — ไม่สนใจ limits เลยตอนตัดสินใจว่า Pod ไปลงที่ไหนได้
  • limits คือเพดานแข็งที่ถูกบังคับใช้ตอน runtime หลังจาก Pod รันไปแล้ว CPU เป็น resource ที่บีบอัดได้ ดังนั้นการใช้เกิน limit ของ CPU จะถูก throttle ไม่ใช่ถูกฆ่า ส่วน memory บีบอัดไม่ได้ container ที่ใช้ memory เกิน limit จะโดน OOMKilled แล้ว restart
apiVersion: v1
kind: Pod
metadata:
name: payments-worker
spec:
containers:
- name: worker
image: payments-worker:1.4.0
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "256Mi"
Terminal window
# See which node a Pod landed on, and check the node's spare capacity
kubectl get pod payments-worker -o jsonpath='{.spec.nodeName}{"\n"}'
kubectl describe node <node-name> | grep -A5 "Allocated resources"

เพราะ scheduler มองแค่ requests เท่านั้น Pod ที่ไม่ตั้ง requests เลยจึงมองไม่เห็นในสายตาของ capacity planning คือถูก schedule ไปที่ไหนก็ได้ และยังแย่ง resource จน Pod ข้าง ๆ ที่ตั้ง requests ไว้ถูกต้องอาจอดได้ด้วย

สองรูปแบบความล้มเหลวข้างบนไม่สมมาตรกัน คุ้มค่าที่จะเห็นทั้งคู่จริง ๆ ไม่ใช่แค่เชื่อตามคำบอก

CPU ที่เกิน limit จะถูก throttle ไม่มีวันถูกฆ่า container ที่ตั้ง limits.cpu: "200m" แล้วรัน stress --cpu 1 (ที่พยายามใช้ core เต็ม) แสดง cgroup throttling accounting จริงจากข้างในกันเอง

k8s/the-scheduler-and-resource-requests/cpu-hog.yaml
apiVersion: v1
kind: Pod
metadata:
name: cpu-hog
spec:
restartPolicy: Never
containers:
- name: hog
image: polinux/stress
command: ["stress"]
args: ["--cpu", "2"]
resources:
requests:
cpu: "100m"
limits:
cpu: "200m"
$ kubectl exec cpu-hog -- sh -c 'cat /sys/fs/cgroup/cpu.max; cat /sys/fs/cgroup/cpu.stat'
20000 100000
usage_usec 2739831
user_usec 2723945
system_usec 15885
nr_periods 136
nr_throttled 136
throttled_usec 12185810

cpu.max อ่านได้ 20000 100000 คือการยืนยันของ kernel เองว่า limit 200m (quota 20ms ต่อ period 100ms) nr_throttled 136 จาก nr_periods 136 แปลว่า ทุก accounting period เดียว โดนใช้ quota จนหมดและถูก throttle — throttled_usec 12185810 คือมากกว่า 12 วินาทีของเวลาจริงที่ container นี้หยุดพัก ไม่ใช่ถูกฆ่า รอ quota window ถัดไป container ยังคงเป็น Running ตลอดเวลา

Memory ที่เกิน limit จะโดน OOMKilled พร้อม exit code จริง

k8s/the-scheduler-and-resource-requests/mem-hog.yaml
apiVersion: v1
kind: Pod
metadata:
name: mem-hog
spec:
restartPolicy: OnFailure
containers:
- name: hog
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "300M", "--vm-hang", "1"]
resources:
requests:
memory: "64Mi"
limits:
memory: "100Mi"
$ kubectl get pod mem-hog
NAME READY STATUS RESTARTS AGE
mem-hog 0/1 OOMKilled 2 (16s ago) 20s
$ kubectl describe pod mem-hog
Last State: Terminated
Reason: OOMKilled
Exit Code: 137

Exit code 137 คือ 128 + 9 (SIGKILL) — kernel’s OOM killer ไม่ใช่ Kubernetes เป็นคนจบ process ทันทีที่ cgroup memory limit ถูกข้าม kubelet แค่รายงานผลแล้วด้วย restartPolicy: OnFailure ก็ restart container ซึ่งก็โดน OOMKilled ซ้ำอีกด้วย workload เดิม

Kubernetes คำนวณ Quality of Service (QoS) class ให้ทุก Pod อัตโนมัติจาก requests และ limits ของตัวเอง — คุณไม่ต้องตั้งค่า field นี้เอง

  • Guaranteed — ทุก container มี requests เท่ากับ limits ทั้ง CPU และ memory
  • Burstable — มีอย่างน้อยหนึ่ง container ตั้ง requests ไว้ แต่ค่าต่างจาก limits หรือมีแค่บาง resource ที่ตั้งทั้งคู่
  • BestEffort — ไม่มี container ไหนตั้ง requests หรือ limits เลย

พิสูจน์ ด้วย Pod 3 ตัวที่ตรงตาม spec ข้างบนพอดี อ่านค่ากลับ

k8s/the-scheduler-and-resource-requests/qos-pods.yaml
apiVersion: v1
kind: Pod
metadata:
name: qos-guaranteed
spec:
containers:
- name: c
image: registry.k8s.io/pause:3.10
resources:
requests: { cpu: "200m", memory: "128Mi" }
limits: { cpu: "200m", memory: "128Mi" }
---
apiVersion: v1
kind: Pod
metadata:
name: qos-burstable
spec:
containers:
- name: c
image: registry.k8s.io/pause:3.10
resources:
requests: { cpu: "100m", memory: "64Mi" }
limits: { cpu: "500m", memory: "256Mi" }
---
apiVersion: v1
kind: Pod
metadata:
name: qos-besteffort
spec:
containers:
- name: c
image: registry.k8s.io/pause:3.10
$ for p in qos-guaranteed qos-burstable qos-besteffort; do
echo -n "$p: "; kubectl get pod $p -o jsonpath='{.status.qosClass}{"\n"}'
done
qos-guaranteed: Guaranteed
qos-burstable: Burstable
qos-besteffort: BestEffort

QoS class จะมีผลก็ต่อเมื่อ Node เริ่มขาด resource เมื่อ kubelet ต้องเรียก memory คืนภายใต้ pressure จะ evict Pod ระดับ BestEffort ก่อน ตามด้วย Burstable และจะแตะ Pod ระดับ Guaranteed ก็ต่อเมื่อไม่เหลือทางเลือกอื่นแล้วเท่านั้น

LimitRange ตั้งค่า default ต่อ container ให้ namespace ดังนั้น Pod ที่ไม่มี resources block เลยจะไม่กลายเป็น BestEffort แบบเงียบ ๆ — มันจะได้ default ของ namespace ถูกฉีดเข้ามาตอน admission พิสูจน์

k8s/the-scheduler-and-resource-requests/limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
spec:
limits:
- type: Container
defaultRequest:
cpu: "250m"
memory: "128Mi"
default:
cpu: "500m"
memory: "256Mi"
k8s/the-scheduler-and-resource-requests/no-resources-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: no-resources
spec:
containers:
- name: c
image: registry.k8s.io/pause:3.10
$ kubectl get pod no-resources -o jsonpath='{.spec.containers[0].resources}{"\n"}'
{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"250m","memory":"128Mi"}}

manifest ของ Pod ไม่ได้พูดถึง resources เลยแม้แต่นิดเดียว — LimitRange ใน namespace ของมันฉีด requests 250m/128Mi และ limits 500m/256Mi เข้าไปก่อนที่ object จะถูก persist ด้วยซ้ำ นี่คือเหตุผลที่ Pod ตัวนี้เป็น Burstable ไม่ใช่ BestEffort และทำไม “ไม่มี resources block” ไม่ได้แปลว่า “มองไม่เห็นในสายตา capacity planning” เสมอไป — ขึ้นอยู่กับว่า namespace นั้นมี LimitRange หรือเปล่า

kubectl describe node รายงานตัวเลขสองชุดแยกกัน และมีแค่ตัวเดียวที่ scheduler ใช้จริง

$ kubectl describe node scheduling-worker
Capacity:
cpu: 11
memory: 9144196Ki
Allocatable:
cpu: 11
memory: 9144196Ki

Capacity คือ hardware ทั้งหมดของ Node Allocatable คือ capacity ลบด้วยส่วนที่ kubelet กับ system daemon จองไว้ให้ตัวเอง (--system-reserved/--kube-reserved) และ Allocatable ไม่ใช่ Capacity ที่ Filter phase ของ scheduler ใช้เทียบกับ requests ของ Pod บน kind node นี้ทั้งสองค่าเท่ากันเพราะ kind ไม่ได้ตั้ง flag การจองใด ๆ — บน cloud-managed node pool จริง Allocatable มักต่ำกว่า Capacity 5-10% เป็นปกติ และ Pod ที่คำนวณขนาดจากตัวเลขผิดตัวอาจดูเหมือนพอดีบนกระดาษแล้วไม่เคย schedule ได้เลยจริง ๆ

ทุก Pod มี priority — 0 โดย default PriorityClass ที่ตั้ง value สูงกว่าทำสองอย่าง: ดัน Pod ข้ามหน้า Pod priority ต่ำกว่าใน scheduling queue และถ้า cluster ยังไม่มีที่ว่างให้ scheduler จะพยายาม preempt (evict) Pod priority ต่ำกว่าที่อื่นเพื่อเปิดที่ว่างนั้น

k8s/the-scheduler-and-resource-requests/priority-classes.yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 100
globalDefault: false
description: "Low priority batch work, preemptable."
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "High priority workload, may preempt lower-priority pods."

globalDefault: true บน PriorityClass ทำให้มันมีผลกับทุก Pod ที่ไม่ระบุ priorityClassName — มีได้แค่ PriorityClass เดียวใน cluster ที่ตั้งค่านี้เป็น true และ API จะปฏิเสธตัวที่สอง

พิสูจน์ pin Pod low-priority สองตัวที่ request cpu: "5" แต่ละตัวไปที่ Node เดียว (10 จาก 11 CPU ของมัน) แล้วสร้าง Pod high-priority บน Node เดียวกันที่ request cpu: "3" — มากกว่า 1 CPU ที่เหลืออยู่

$ kubectl get pods -o wide
NAME READY STATUS NODE
filler-54bbb658fb-lpwpj 1/1 Running scheduling-worker
filler-54bbb658fb-xs2pg 1/1 Running scheduling-worker
important-pod 1/1 Running scheduling-worker
$ kubectl get events --sort-by=.lastTimestamp
Warning FailedScheduling pod/important-pod 0/4 nodes are available: 1 Insufficient cpu, 1 node(s) had untolerated taint(s), 2 node(s) didn't match Pod's node affinity/selector. preemption: found a potential placement for pod on node scheduling-worker, preempting 1 victims
Normal Killing pod/filler-54bbb658fb-xs2pg Stopping container filler
Normal Preempted pod/filler-54bbb658fb-xs2pg Preempted by pod bd29493f-c306-4440-bded-95dc1aa81181 on node scheduling-worker
Normal Scheduled pod/important-pod Successfully assigned sched-demo/important-pod to scheduling-worker
Warning FailedScheduling pod/filler-54bbb658fb-5db89 0/4 nodes are available: 1 Insufficient cpu, ... preemption: 0/4 nodes are available: 1 No preemption victims found for incoming pod, 3 Preemption is not helpful for scheduling.

important-pod ได้ Scheduled หลังจาก Pod filler ตัวหนึ่งถูก Preempted (event จริง reason จริง) เพื่อเปิด 5 CPU ของมัน — แล้ว Pod replacement ของ ReplicaSet ก็ค้างอยู่ที่ Pending จริง ๆ เพราะตอนนี้มันเองกลายเป็น Pod priority ต่ำกว่าที่ไม่เหลืออะไรให้ preempt แล้ว

preemptionPolicy: Never ให้ Pod priority สูงเลือกที่จะ ไม่ evict ใครเลย — มันยังข้ามหน้า queue แต่รอ capacity ว่างขึ้นเองแทน

apiVersion: v1
kind: Pod
metadata:
name: patient-pod
spec:
priorityClassName: high-priority-nonpreempting
containers:
- name: c
image: registry.k8s.io/pause:3.10
resources:
requests:
cpu: "5"
$ kubectl describe pod patient-pod
Events:
Warning FailedScheduling 0/4 nodes are available: 1 Insufficient cpu, ... preemption: not eligible due to preemptionPolicy=Never.

reason ที่แม่นยำนั้น — not eligible due to preemptionPolicy=Never — คือหลักฐานจริง: Pod ค้างที่ Pending เต็ม ๆ แทนที่จะ evict เพื่อนบ้าน priority ต่ำกว่าที่ปกติแล้วเป็นเป้าได้

สำหรับทุก Pod ที่ยังไม่ถูก schedule scheduler จะรัน pipeline ของ extension point (scheduling framework) ก่อนจะฟันธง แต่ละจุดถูก implement ด้วย plugin หนึ่งตัวหรือมากกว่า

  1. PreFilter/Filter — ตัด Node ที่รัน Pod นี้ไม่ได้เด็ดขาดออกไปทั้งหมด NodeResourcesFit filter ตาม requests เทียบ Allocatable, TaintToleration filter ตาม taint ที่ไม่ tolerate, InterPodAffinity กับ PodTopologySpread filter ตามกฎ affinity/anti-affinity และ spread ที่เหลือคือชุด Node ที่ เป็นไปได้
  2. PostFilter — รันก็ต่อเมื่อไม่มี Node ไหนเป็นไปได้เลย นี่คือจุดที่ DefaultPreemption มองหา victim ที่ต่ำกว่าให้ evict ตามที่พิสูจน์ข้างบน
  3. PreScore/Score/NormalizeScore — จัดอันดับ Node ที่เป็นไปได้ (กระจายข้าม zone, อัด density แบบ bin-pack และอื่น ๆ) แล้วเลือกตัวที่คะแนนสูงสุด
  4. Reserve/Permit/PreBind/Bind/PostBind — การจอง Node ที่ชนะจะถูก commit แล้ว scheduler จะ bind Pod เข้ากับ Node นั้นโดยเขียน spec.nodeName แล้ว kubelet บน Node นั้นจะรับช่วงต่อและสั่ง start container
flowchart LR
  pod["Pod spec\n(requests + limits + priority)"] --> filter["Filter:\nwhich Nodes CAN run it"]
  filter -->|feasible Nodes exist| score["Score:\nrank the feasible Nodes"]
  filter -->|none feasible| postfilter["PostFilter:\nDefaultPreemption looks for victims"]
  postfilter -->|victim found| score
  score --> bind["Bind:\nPod assigned to winning Node"]
  bind --> node["Node"]
requests กับ limits ของ Pod ไหลผ่านขั้นตอน filter, score และ bind ของ scheduler พร้อม preemption บนเส้นทางที่ไม่มี Node ไหนเป็นไปได้

ทุกอย่างในบทนี้คือเรื่องเดียวกัน: scheduler ให้เหตุผลแค่กับ requests เท่านั้น ไม่เคยดู limits ตอนตัดสินใจวาง Pod ดังนั้น requests คือหน่วยจริงของ “Pod ตัวนี้ใหญ่แค่ไหน” ในมุมของ scheduling และทุกอย่างที่ตามมาก็ไหลจากตรงนี้ QoS class คือชื่อเรียกของ รูปร่าง ของ requests/limits ของ Pod (เท่ากัน ต่างกัน หรือไม่มีเลย) และจะเห็นผลก็ต่อเมื่อ memory ตึงเท่านั้น priority กับ preemption มีไว้สำหรับตอนที่ requests อย่างเดียวไม่พอจะหาที่ว่างได้ — Pod priority สูงกว่าบังคับให้ Pod priority ต่ำกว่าหลีกทางได้ เว้นแต่จะปฏิเสธไว้ชัดเจนด้วย preemptionPolicy: Never LimitRange กับ QoS ต่างก็กำหนดรูปร่างว่า requests/limits ของ Pod จะกลายเป็นอะไร — scheduler กับ kubelet ทำงานกับตัวเลขสุดท้ายที่ resolve แล้วเท่านั้น ไม่ใช่สิ่งที่คุณพิมพ์หรือไม่พิมพ์ใน manifest

ทางเลือกได้อะไรเสียอะไร
requests == limits (Guaranteed QoS)ไม่ถูก throttle ต่ำกว่า request เลย ถูก evict หลังสุดภายใต้ pressure (พิสูจน์ข้างบน)ไม่มีที่ว่างให้ burst; over-provision สำหรับ workload ที่ spike
requests < limits (Burstable QoS)burst เกิน baseline ได้เมื่อ Node มี capacity เหลือถูก evict ก่อน Guaranteed Pod ภายใต้ memory pressure; CPU ยังถูก throttle ที่ limit ได้ (พิสูจน์ข้างบน)
ไม่มี requests/limits เลย (BestEffort)ไม่ต้องตั้งค่าอะไรเลยถูก evict ก่อนใครเมื่อมี pressure ใด ๆ; มองไม่เห็นในสายตา capacity planning; LimitRange ใน namespace override ได้โดยไม่เตือน (พิสูจน์ข้างบน)
PriorityClass value สูงพร้อม preemptionPolicy defaultถูก schedule ได้แม้ cluster เต็ม โดย evict Pod priority ต่ำกว่า (พิสูจน์ข้างบน)Deployment priority สูงที่ตั้งค่าผิดอาจ cascade-evict workload priority ต่ำที่ไม่เกี่ยวข้องทั่วทั้ง Node
preemptionPolicy: Never บน priority class สูงไม่เคย disrupt Pod อื่นที่กำลังรันเพื่อให้ตัวเองถูก schedule (พิสูจน์ข้างบน)อาจรอ capacity ที่ไม่มีวันว่างขึ้นเองไปตลอดกาล
  • “scheduler เช็ค limits ด้วย ไม่ใช่แค่ requests ตอนตัดสินใจว่า Pod ไปลงที่ไหน” พิสูจน์ข้างบน (และใน kubectl explain/scheduler docs): มีแค่ requests เท่านั้นที่ถูกเทียบกับ Allocatable capacity ของ Node Pod ที่ตั้ง limits.memory สูงมากยังถูก schedule ไปที่ Node ที่ memory ว่างน้อยได้ ถ้า requests.memory เล็ก
  • “container ที่เกิน CPU limit จะถูกฆ่า เหมือน memory” พิสูจน์ข้างบนด้วย cgroup accounting จริง: CPU ถูก throttle (nr_throttled/throttled_usec) container ยังรันต่อ มีแค่เกิน memory limit เท่านั้นที่ทำให้ถูกฆ่า (OOMKilled, exit 137)
  • “Pod ที่ไม่มี resources block เลยเป็น BestEffort เสมอ” พิสูจน์ข้างบน: ถ้า namespace มี LimitRange พร้อม defaultRequest/default admission controller จะฉีด requests/limits จริงก่อน object ถูกสร้าง ทำให้กลายเป็น Burstable แทน
  • “preemption หาทางเปิดที่ว่างให้ Pod priority สูงกว่าได้เสมอ” พิสูจน์ข้างบนด้วย preemptionPolicy: Never: Pod priority สูงตั้งค่าให้ไม่ evict ใครเลยได้ แล้วมันจะค้างที่ Pending — reason จริง not eligible due to preemptionPolicy=Never — แทนที่จะบังคับให้ capacity ว่างขึ้นมา
  • “Allocatable กับ Capacity คือสิ่งเดียวกัน แค่คำต่างกัน” พิสูจน์ข้างบน: มันเท่ากันแค่บังเอิญบน kind node ที่ไม่ได้ตั้งค่าอะไร scheduler filter เทียบกับ Allocatable ซึ่งบน cluster จริงคือ capacity ลบด้วยการจองของ kubelet/system

ตอน review Pod spec, Deployment หรือ PriorityClass ที่ AI สร้าง เช็คสิ่งเหล่านี้

  • container ที่ตั้ง limits.memory แต่ไม่ตั้ง requests.memory (หรือกลับกัน) — มันเป็น Burstable ไม่ใช่ Guaranteed และจะถูก evict ก่อน Pod ที่ตั้งทั้งคู่เท่ากัน พิสูจน์ข้างบน
  • โค้ดหรือ docs ที่สมมติว่า container ที่เกิน CPU limit จะถูกฆ่า — พิสูจน์ข้างบนแล้วว่ามันถูก throttle ไม่ใช่ถูกฆ่า มีแค่ memory เท่านั้นที่ทำให้ถูกฆ่าจริง (OOMKilled, exit 137)
  • globalDefault: true ที่ copy ใส่ PriorityClass ใหม่โดยไม่เช็คว่า cluster มีตัวหนึ่งอยู่แล้วหรือเปล่า — API จะปฏิเสธ globalDefault: true ตัวที่สอง และถ้าใช้กับทุก Pod ที่มีอยู่แบบไม่ตั้งใจ นั่นมักไม่ใช่เจตนาจริง
  • PriorityClass ที่ตั้งไว้เพื่อรับประกันการ schedule โดยไม่คิดถึง preemptionPolicy เลย — ค่า default (PreemptLowerPriority) evict workload อื่นที่แชร์ Node เดียวกันได้ ถ้าเจตนาคือ “สำคัญแต่ไม่รบกวนใคร” ต้องใช้ preemptionPolicy: Never พิสูจน์ข้างบน
  • สมมติฐานเรื่อง capacity planning ที่อ้างอิงจากบรรทัด Capacity ของ kubectl describe node แทนที่จะเป็น Allocatable — พิสูจน์ข้างบนว่ามันเท่ากันแค่บังเอิญบน kind node ที่ไม่ได้ตั้งค่า

🐛 หา bug ในโค้ดนี้

k8s/the-scheduler-and-resource-requests/web-app.yaml
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: web
image: web-app:3.2.0
resources:
limits:
cpu: "500m"
memory: "512Mi"
เฉลย

Pod นี้ตั้ง limits แต่ไม่ตั้ง requests เลย ดูเหมือนปลอดภัย — มีเพดาน memory อยู่ — แต่ไม่มี requests ทำให้มันไม่ใช่ Guaranteed API server จะ default requests ให้เท่ากับ limits ก็ต่อเมื่อทั้งคู่ถูกตั้งพร้อมกันใน client tooling รุ่นใหม่เท่านั้น และถ้า namespace นี้ไม่มี LimitRange QoS class จริงของ Pod จะกลายเป็น Burstable โดย scheduler มอง requests ของมันเป็นค่าที่ resolve จาก limits ตอน admission ซึ่งง่ายที่จะเข้าใจผิดว่า “ฉันตั้ง limits แล้ว ก็อธิบายครบแล้ว” พิสูจน์ข้างบน: วิธีที่เชื่อถือได้ในการได้ QoS Guaranteed (evict หลังสุด ไม่ถูก throttle ต่ำกว่าพื้น) คือตั้ง requests กับ limits ให้เป็นค่าเดียวกันชัดเจนสำหรับทุก resource ในทุก container ไม่ใช่ตั้งแค่ limits แล้วหวังว่าจะได้ผล

ตอน scheduler ตัดสินใจว่า Pod รันที่ไหนได้ scheduler เทียบอะไรกับอะไร
container ใช้ memory เกิน limit จะเกิดอะไรขึ้น
เมื่อ Node ขาด memory QoS class ไหนโดน evict ก่อน
นี่คือ cgroup output จริงจาก container ที่ตั้ง limits.cpu: "200m" แล้วรัน process ที่กิน CPU เต็มที่ มันพิสูจน์อะไร
$ kubectl exec cpu-hog -- cat /sys/fs/cgroup/cpu.stat
nr_periods 136
nr_throttled 136
throttled_usec 12185810
manifest ของ Pod no-resources ไม่มี resources block เลย ค่าพวกนี้มาจากไหน
$ kubectl get pod no-resources -o jsonpath='{.spec.containers[0].resources}'
{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"250m","memory":"128Mi"}}
ทำไม Pod priority สูงตัวนี้ถึงค้างที่ Pending แทนที่จะ preempt Pod priority ต่ำกว่า
Warning  FailedScheduling  pod/patient-pod  0/4 nodes are available: 1 Insufficient cpu, ...
preemption: not eligible due to preemptionPolicy=Never.
ความสัมพันธ์ระหว่าง Capacity กับ Allocatable ของ Node คืออะไร และ scheduler ใช้ตัวไหน