Node Affinity, Taint และ Toleration
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Affinity ดึง Pod เข้าหา Node (หรือ Pod อื่น) บางตัว topologySpreadConstraints กระจาย Pod กลุ่มหนึ่งให้เท่ากันข้าม topology ส่วน taint ผลัก Pod ออกจาก Node เว้นแต่ Pod จะมี toleration ที่ match — กลไกที่มักถูกใช้ร่วมกัน
nodeSelector กับ node affinity: match บน label ของ Node
หัวข้อที่มีชื่อว่า “nodeSelector กับ node affinity: match บน label ของ Node”วิธีง่ายสุดในการจำกัดว่า Pod รันที่ไหนคือ nodeSelector — match แบบ exact กับ label ของ Node ถ้า Node ไม่มีทุก label ที่ระบุไว้ จะไม่ถูกนับเป็นตัวเลือกเลย
apiVersion: v1kind: Podmetadata: name: gpu-inferencespec: nodeSelector: hardware: gpu containers: - name: inference image: inference-server:2.1.0Node affinity ทำงานแบบเดียวกันแต่ใช้ expression ที่ยืดหยุ่นกว่า (In, NotIn, Exists และอื่น ๆ) และที่สำคัญคือเลือกได้ว่ากฎนั้นเข้มงวดแค่ไหน
requiredDuringSchedulingIgnoredDuringExecution— ข้อบังคับแบบแข็ง ถ้าไม่มี Node ไหนตรงเงื่อนไข Pod จะค้างอยู่ที่ PendingpreferredDuringSchedulingIgnoredDuringExecution— ความชอบแบบอ่อนพร้อมweightscheduler จะเลือก Node ที่ match ก่อน แต่ถ้าจำเป็นก็ยังวาง Pod ที่อื่นได้
apiVersion: v1kind: Podmetadata: name: gpu-inference-affinityspec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: hardware operator: In values: - gpu preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 preference: matchExpressions: - key: zone operator: In values: - preferred containers: - name: inference image: inference-server:2.1.0ทั้งสอง suffix ลงท้ายด้วย IgnoredDuringExecution หมายความว่าเมื่อ Pod รันอยู่แล้ว การเปลี่ยน label ของ Node ภายหลังจะไม่ทำให้ Pod ถูก evict — กฎพวกนี้มีผลแค่ตอนตัดสินใจ schedule ครั้งแรกเท่านั้น พิสูจน์ว่า preferred steers การวาง Pod จริงโดยไม่บังคับ: label worker ตัวเดียวเป็น zone=preferred แล้วสร้าง Pod ที่มีแค่ soft preference นั้น (ไม่มี nodeSelector ไม่มีข้อบังคับแบบแข็ง) มันลงที่ worker ตัวนั้นทุกครั้ง จากตัวเลือกที่เหมือนกันสามตัว
$ kubectl label node scheduling-worker2 zone=preferred$ kubectl get pod prefers-zone -o wideNAME READY STATUS NODEprefers-zone 1/1 Running scheduling-worker2Pod affinity กับ anti-affinity: schedule โดยอ้างอิง Pod อื่น พิสูจน์แบบ all-or-nothing
หัวข้อที่มีชื่อว่า “Pod affinity กับ anti-affinity: schedule โดยอ้างอิง Pod อื่น พิสูจน์แบบ all-or-nothing”Node affinity มองที่ label ของ Node ส่วน pod affinity และ pod anti-affinity มองที่ label ของ Pod อื่นที่รันอยู่แล้ว แทน โดยใช้ topologyKey เพื่อนิยามว่า “ใกล้กัน” หมายถึงอะไร (hostname, zone และอื่น ๆ)
apiVersion: apps/v1kind: Deploymentmetadata: name: web-antispec: replicas: 4 selector: matchLabels: app: web-anti template: metadata: labels: app: web-anti spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: ["web-anti"] topologyKey: kubernetes.io/hostname containers: - name: web image: registry.k8s.io/pause:3.10กฎนี้บอกว่า “ห้ามวาง Pod web-anti สองตัวบน host เดียวกัน” บน cluster ที่มี 3 worker replica 3 ตัวลงได้พอดี — ตัวละ Node — แต่ตัวที่ 4 ไม่เหลือที่ให้ไปแล้ว และ requiredDuringScheduling คือ all-or-nothing: มันค้างอยู่ที่ Pending จริง ๆ ไม่ใช่ “กระจายได้บ้าง”
$ kubectl get pods -l app=web-anti -o wideNAME READY STATUS NODEweb-anti-6476995565-5dwmp 1/1 Running scheduling-workerweb-anti-6476995565-glvpv 0/1 Pending <none>web-anti-6476995565-l9tvf 1/1 Running scheduling-worker2web-anti-6476995565-rhltf 1/1 Running scheduling-worker3
$ kubectl describe pod web-anti-6476995565-glvpvEvents: Warning FailedScheduling 0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 node(s) didn't match pod anti-affinity rules. preemption: 0/4 nodes are available: 1 Preemption is not helpful for scheduling, 3 No preemption victims found for incoming pod.เทียบกับ topologySpreadConstraints ข้างล่างที่พูดว่า “กระจายให้เท่ากัน อนุญาตได้ถึง N ตัวต่อ Node” แทน เพดาน one-per-Node แบบเข้มงวดของ anti-affinity
topologySpreadConstraints: กระจายเท่ากันด้วย skew ที่ปรับได้
หัวข้อที่มีชื่อว่า “topologySpreadConstraints: กระจายเท่ากันด้วย skew ที่ปรับได้”topologySpreadConstraints (kubectl explain pod.spec.topologySpreadConstraints บน cluster 1.37 จริง) กระจาย Pod กลุ่มหนึ่งข้าม topology domain ให้อยู่ในค่า tolerance หนึ่งคือ maxSkew แทนที่จะเป็นกฎ all-or-nothing ของ anti-affinity
apiVersion: apps/v1kind: Deploymentmetadata: name: webspec: replicas: 6 selector: matchLabels: app: web template: metadata: labels: app: web spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule nodeTaintsPolicy: Honor matchLabelKeys: - pod-template-hash labelSelector: matchLabels: app: web containers: - name: web image: registry.k8s.io/pause:3.10maxSkew— ความต่างสูงสุดที่ยอมได้ระหว่าง domain ที่มี Pod match มากสุดกับน้อยสุด1ที่นี่หมายความว่าไม่มี Node ไหนมี Pod match มากกว่า Node อื่น 2 ตัวขึ้นไปtopologyKey: kubernetes.io/hostname— กระจายต่อ Node ใช้topology.kubernetes.io/zoneเพื่อกระจายต่อ zone แทนwhenUnsatisfiable—DoNotSchedule(แข็ง เหมือนrequiredaffinity) หรือScheduleAnyway(อ่อน best-effort)matchLabelKeys: [pod-template-hash]— เช็คแล้วจริง:kubectl explain pod.spec.topologySpreadConstraints.matchLabelKeysบน API server 1.37 จริงรายงาน “This is a beta field … requires theMatchLabelKeysInPodTopologySpreadfeature gate (enabled by default)” — มันยังไม่ GA ยังเป็น beta ตั้งแต่ 1.27 ซึ่งต่างจากสมมติฐานว่ามันน่าจะ GA แล้วตอน 1.37 ในทางปฏิบัติมัน enable by default อยู่แล้ว จึงทำงานเหมือนมีอยู่เสมอ มันจำกัดขอบเขตการนับ skew ให้อยู่แค่ Pod ของ ReplicaSet generation เดียวกัน (ดูส่วน rollout ข้างล่าง)
พิสูจน์ การกระจาย 2-2-2 สะอาด ๆ ข้าม 3 worker ด้วย 6 replica
$ kubectl apply -f topology-spread-deploy.yaml$ kubectl get pods -o wideNAME READY STATUS NODEweb-b8b7dc6c7-4dpx9 1/1 Running scheduling-workerweb-b8b7dc6c7-llb6c 1/1 Running scheduling-workerweb-b8b7dc6c7-csj9b 1/1 Running scheduling-worker2web-b8b7dc6c7-l9qrl 1/1 Running scheduling-worker2web-b8b7dc6c7-hf4pl 1/1 Running scheduling-worker3web-b8b7dc6c7-nqxj9 1/1 Running scheduling-worker3กับดักของ nodeTaintsPolicy พิสูจน์แล้ว
หัวข้อที่มีชื่อว่า “กับดักของ nodeTaintsPolicy พิสูจน์แล้ว”manifest เดียวกันโดยไม่มี nodeTaintsPolicy: Honor ทำให้ 3 จาก 6 Pod ค้างที่ Pending ถาวรบน cluster ตัวนี้เป๊ะ ๆ
$ kubectl get pods -o wideNAME READY STATUS NODEweb-57c75d6bbb-7m652 0/1 Pending <none>web-57c75d6bbb-gn7s7 0/1 Pending <none>web-57c75d6bbb-k67n4 1/1 Running scheduling-worker3web-57c75d6bbb-nwc64 1/1 Running scheduling-workerweb-57c75d6bbb-xjl2m 0/1 Pending <none>web-57c75d6bbb-xz9lt 1/1 Running scheduling-worker2
$ kubectl describe pod web-57c75d6bbb-7m652Events: Warning FailedScheduling 0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 node(s) didn't match pod topology spread constraints.kubectl explain pod.spec.topologySpreadConstraints.nodeTaintsPolicy บอกเหตุผลชัด ๆ: “If this value is nil, the behavior is equivalent to the Ignore policy” — หมายความว่า Node control-plane ที่มี taint และ schedule ไม่ได้ยังถูกนับเป็น topology domain ที่ถูกต้องด้วย Pod match 0 ตัว โดย default พอมี Pod หนึ่งตัวอยู่แล้วบนแต่ละ worker ทั้ง 3 ตัว (1-1-1) และ control-plane ถูกนับเป็น domain ที่ 4 อยู่ที่ 0 การวาง Pod ตัวที่ 4 ที่ไหนก็ตามจะทำให้ skew เป็น 2 - 0 = 2 เกิน limit maxSkew: 1 — ดังนั้นไม่มีอะไรหลังจาก 3 Pod แรก schedule ได้เลย ทั่วทั้ง cluster ตลอดกาล การตั้ง nodeTaintsPolicy: Honor ตัด Node ที่ Pod ที่เข้ามาไม่ tolerate ออกจากการนับ domain ซึ่งเป็นสิ่งที่แก้ปัญหาข้างบนได้
matchLabelKeys ระหว่าง rolling update พิสูจน์แล้ว
หัวข้อที่มีชื่อว่า “matchLabelKeys ระหว่าง rolling update พิสูจน์แล้ว”trigger rolling update (maxUnavailable: 0, maxSurge: 1) บน Deployment ที่ spread โดยไม่มี matchLabelKeys แล้วดู surge Pod ลงบน Node ที่มี Pod ของ generation ก่อนหน้า อยู่แล้ว 2 ตัว เพราะ constraint นับทุก generation รวมกัน
$ kubectl set image deploy/rollout-demo web=nginx:1.27.2-alpinet+8s: rollout-demo-579bddcff9-2cpvt Running scheduling-worker (old gen 579bddcff9)t+8s: rollout-demo-579bddcff9-hmj2l Running scheduling-worker (old gen 579bddcff9)t+8s: rollout-demo-676f6cdd75-2bdwb Running scheduling-worker (NEW gen 676f6cdd75)scheduling-worker ตอนนี้มี 3 Pod จริง ๆ (old 2 + new 1) อยู่ครู่หนึ่ง — ยังอยู่ใน maxSkew: 1 โดยรวม (3 เทียบ 2 เทียบ 2) แต่การกระจายของ generation ใหม่เองไม่สม่ำเสมอเพียงเพราะบังเอิญว่า Pod เก่าอยู่ตรงไหน เพิ่ม matchLabelKeys: [pod-template-hash] แล้วทำ rollout เดิมซ้ำ: ตอนนี้ constraint นับแต่ละ generation แยกกัน ดังนั้น surge Pod ตัวแรกของ generation ใหม่เอี่ยมจะเริ่มนับ domain ของตัวเองที่ 0 ทุกที่เสมอ — ไม่ว่า Pod ของ generation ที่กำลังจะออกจะอยู่ตรงไหนก็ตาม
$ kubectl patch deploy rollout-demo --type=json -p='[{"op":"add","path":"/spec/template/spec/topologySpreadConstraints/0/matchLabelKeys","value":["pod-template-hash"]}]'$ kubectl set image deploy/rollout-demo web=nginx:1.27.3-alpinet+1s: rollout-demo-548b8ff4cb-djfk9 ContainerCreating scheduling-worker (NEW gen 548b8ff4cb, 1st of its generation)t+1s: rollout-demo-676f6cdd75-2bdwb Running scheduling-worker (old gen 676f6cdd75)t+1s: rollout-demo-7b5cbfbdb7-bdd57 Running scheduling-worker (old gen 7b5cbfbdb7)matchLabelKeys merge pod-template-hash: 548b8ff4cb เข้ากับ labelSelector ที่มีผลจริงสำหรับ admission ของ surge Pod ตัวนั้น ดังนั้นการคำนวณ skew ของมันจะเห็นแค่ Pod ของ hash เดียวกัน — ไม่มีวันถูกกันหรือถูกกระจุกตัวโดยไม่ตั้งใจจาก Node ที่ generation เก่าบังเอิญอยู่
Taint กับ toleration: Node ที่ผลัก Pod ออก
หัวข้อที่มีชื่อว่า “Taint กับ toleration: Node ที่ผลัก Pod ออก”Affinity คือ Pod เป็นฝ่ายตัดสินใจว่าอยากไปไหน taint กลับด้านตรงข้าม — คือถูกตั้งบน Node และผลัก Pod ทุกตัวที่ไม่มี toleration ที่ match ออกไป
# Taint a node so it repels ordinary Podskubectl taint nodes gpu-node-1 hardware=gpu:NoScheduletaint effect มีสามระดับ เรียงจากอ่อนไปแรง
NoSchedule— Pod ใหม่ที่ไม่มี toleration ที่ match จะไม่ถูก schedule ที่นี่ Pod ที่รันอยู่แล้วไม่ถูกแตะPreferNoSchedule— เวอร์ชันอ่อน scheduler จะพยายามเลี่ยง Node นี้ แต่ถ้าจำเป็นก็ยังใช้ได้NoExecute— Pod ใหม่ถูกกันไว้ และ Pod ที่รันอยู่บน Node นั้นที่ไม่มี toleration ที่ match จะถูก evict ด้วย
Pod เข้าร่วมได้ด้วยการใส่ tolerations ที่ match key, value และ effect ของ taint
apiVersion: v1kind: Podmetadata: name: gpu-inference-tolerationspec: tolerations: - key: hardware operator: Equal value: gpu effect: NoSchedule nodeSelector: hardware: gpu containers: - name: inference image: inference-server:2.1.0toleration แค่หักล้าง taint เท่านั้น ไม่ได้ดึง Pod ไปที่นั่น นี่คือเหตุผลที่ node pool เฉพาะสำหรับ GPU มักสร้างจากสองกลไกพร้อมกัน: Node ถูก taint ไว้ให้ Pod ทั่วไปอยู่ห่าง ๆ ส่วน Pod ของ GPU workload ทั้ง tolerate taint นั้นและใช้ nodeSelector (หรือ node affinity) เพื่อขอ schedule ไปที่นั่น พร้อมกับ request GPU resource ด้วย
NoExecute พร้อม tolerationSeconds จับเวลาจริง
หัวข้อที่มีชื่อว่า “NoExecute พร้อม tolerationSeconds จับเวลาจริง”tolerationSeconds ให้ Pod tolerate taint NoExecute ได้ แต่แค่ในช่วงเวลาผ่อนผันที่จำกัดก่อนจะถูก evict อยู่ดี — มีประโยชน์สำหรับ “ทนปัญหา Node สั้น ๆ ได้ แต่ถ้ายืดเยื้อก็ออก”
apiVersion: v1kind: Podmetadata: name: tolerates-brieflyspec: nodeSelector: kubernetes.io/hostname: scheduling-worker3 tolerations: - key: dedicated operator: Equal value: batch effect: NoExecute tolerationSeconds: 10 containers: - name: c image: registry.k8s.io/pause:3.10$ kubectl taint nodes scheduling-worker3 dedicated=batch:NoExecute$ kubectl apply -f toleration-seconds-pod.yaml # created 16:43:07, taint already present16:43:07 Running16:43:16 Running16:43:18 Succeeded (deletionTimestamp set)16:43:19 Error from server (NotFound): pods "tolerates-briefly" not foundPod รันอยู่ประมาณ 10 วินาทีหลัง taint ถูกใส่ แล้วก็ถูก evict ตรงตามกำหนด — tolerationSeconds คือนาฬิกาจริงที่บังคับใช้จริง ไม่ใช่แค่คำแนะนำ
Taint node.kubernetes.io/* ที่ built-in
หัวข้อที่มีชื่อว่า “Taint node.kubernetes.io/* ที่ built-in”Kubernetes ใส่ taint บางตัวให้อัตโนมัติ โดยไม่ต้อง operator ทำอะไรเลย — Node control-plane ของ kind มีตัวหนึ่งติดมาตั้งแต่สร้าง
$ kubectl describe node scheduling-control-plane | grep TaintsTaints: node-role.kubernetes.io/control-plane:NoScheduletaint built-in ที่สำคัญด้าน operation ที่สุดคือ node.kubernetes.io/not-ready กับ node.kubernetes.io/unreachable (ทั้งคู่ NoExecute ถูกใส่โดย node controller ทันทีที่ condition Ready ของ Node เป็น false หรือ node controller ขาดการติดต่อกับมัน) และ node.kubernetes.io/unschedulable (NoSchedule สะท้อน kubectl cordon) ทุก Pod tolerate not-ready/unreachable เป็นเวลา 300s โดย default (ผ่าน tolerationSeconds default ที่ API server ใส่ให้อัตโนมัติ) — นี่คือกลไกเบื้องหลังพอดีของ “Pod รอดจาก node ที่สะดุดสั้น ๆ แต่ถูก evict ถ้า Node ล่มค้างไว้”
flowchart LR node["Node: gpu-node-1\n(tainted hardware=gpu:NoSchedule)"] podA["Pod A: no toleration"] -- blocked --> node podB["Pod B: tolerates hardware=gpu"] -- scheduled --> node node2["Node: tainted NoExecute + tolerationSeconds: 10"] --> podC["Pod C: tolerates for 10s, then evicted"]
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”จริง ๆ แล้วมีแค่สองทิศทางในบทนี้ และทุกอย่างเป็นหนึ่งในสองแบบ ดึง: nodeSelector, node affinity, pod affinity และครึ่ง preferred ของ topologySpreadConstraints ล้วนอธิบายสิ่งที่ Pod ต้องการ — Node ที่มี label, ความใกล้กับ Pod อื่น, การกระจายที่เท่ากัน — และ Pod ที่ไม่ได้สิ่งที่ต้องการก็จะค้างที่ Pending (required) หรือยอมรับตัวเลือกรองลงมา (preferred) ผลัก: taint อธิบายสิ่งที่ Node ปฏิเสธ ไม่ขึ้นกับความต้องการของ Pod เลย และมีแค่ toleration ที่ match เท่านั้นที่หักล้างการผลักนั้นได้ — มันไม่เคยกลายเป็นการดึง topologySpreadConstraints อยู่ตรงกลาง: มันนับ Pod ข้าม topology และ nodeTaintsPolicy ของมันเองตัดสินว่า taint (การผลัก) จะทำให้ Node หลุดออกจากการถูกนับเป็นเป้าหมายการกระจายด้วยหรือเปล่า — พิสูจน์ข้างบน การตั้งค่า field นี้ผิดทำให้ “กระจายเท่ากัน” กลายเป็น “ค้างตลอดกาล” แบบเงียบ ๆ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ทางเลือก | ได้อะไร | เสียอะไร |
|---|---|---|
podAntiAffinity requiredDuringScheduling | รับประกันแน่นอน: ไม่มีทาง Pod match สองตัวอยู่ domain เดียวกัน (พิสูจน์ข้างบน) | all-or-nothing — replica ตัวที่ N+1 ค้างที่ Pending ไม่มีคะแนนบางส่วน พิสูจน์ข้างบน |
topologySpreadConstraints พร้อม maxSkew | ปรับได้: “กระจายเท่ากัน อนุญาตความต่างได้ถึง N” แทน all-or-nothing | ต้องมี nodeTaintsPolicy: Honor บน cluster ที่มี Node ถูก taint/ยังไม่ Ready ไม่งั้นค้างเงียบ ๆ ได้ (พิสูจน์ข้างบน) |
matchLabelKeys: [pod-template-hash] บน spread constraint | แต่ละ generation ของ rollout ถูกกระจายแยกกัน ไม่ถูกกันหรือ skew โดย generation ที่กำลังออก (พิสูจน์ข้างบน) | ยังเป็น beta field อยู่ (เช็คแล้ว: 1.37, enabled by default) ยังไม่ใช่การรับประกันแบบ stable ที่บันทึกไว้ |
preferredDuringScheduling (node หรือ pod affinity) | ไม่มีวันบล็อกการ schedule Pod ลงที่ไหนสักที่เสมอ | ไม่มีการรับประกันเลยเมื่อมีการแย่งชิง — พิสูจน์ข้างบน soft preference ถูกข้ามได้แบบเงียบ ๆ |
tolerationSeconds บน toleration ของ NoExecute | ทนปัญหา Node สั้น ๆ ได้แทนที่จะ evict ทันที | ถ้าตั้งนานเกินไป Node ที่แย่จริง ๆ จะเก็บ Pod ไว้เกินเวลาที่ควรย้าย (พิสูจน์ข้างบนด้วย eviction ที่จับเวลาจริง) |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “
topologySpreadConstraintsกับpodAntiAffinityคือ syntax สองแบบของการรับประกันเดียวกัน” พิสูจน์ข้างบน:requiredDuringSchedulingของ anti-affinity คือหนึ่งต่อ domain เข้มงวด (all-or-nothing) ส่วนmaxSkewของ spread อนุญาตความต่างที่ปรับได้และลดระดับได้อย่างนุ่มนวลด้วยScheduleAnyway - “Node control-plane ที่ถูก taint หรือยังไม่ Ready จะถูกละเว้นโดย scheduling feature อัตโนมัติ” พิสูจน์ข้างบน: ถ้าไม่มี
nodeTaintsPolicy: HonorNode ที่ถูก taint ยังถูกนับเป็น topology domain ที่มี 0 Pod ซึ่งอาจทำให้maxSkewเป็นไปไม่ได้ที่จะพอใจที่ไหนเลย ทั่วทั้ง cluster - “
preferredDuringSchedulingIgnoredDuringExecutionจะ evict Pod ถ้า preference ไม่จริงอีกต่อไปในภายหลัง” ทั้งrequiredและpreferredลงท้ายด้วยIgnoredDuringExecution— พิสูจน์ใน API docs และจากข้อเท็จจริงว่า affinity ทั้งสองแบบไม่เคย trigger การ evict เองเลยเมื่อ Pod รันอยู่แล้ว - “toleration ดึง Pod เข้าหา Node ที่ถูก taint เหมือนที่ affinity ทำ” พิสูจน์ข้างบน: toleration แค่หักล้างการผลักของ taint เท่านั้น — Pod ยังต้องมี
nodeSelectorหรือกฎ affinity เพื่อถูกดึงไปที่นั่นจริง ๆ - “
tolerationSecondsเป็นแค่คำแนะนำ เหมือน soft preference” พิสูจน์ข้างบนด้วย timestamp จริง: Pod ถูก evict ตรงตามกำหนดทันทีที่ window หมด ไม่ใช่ “สักวัน” หรือ “ถ้าสะดวก”
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ตอน review affinity, spread หรือ toleration block ที่ AI สร้าง เช็คสิ่งเหล่านี้
topologySpreadConstraintsที่ไม่ได้ตั้งnodeTaintsPolicyเลย บน cluster ที่มี Node control-plane หรือ Node ที่ cordon ไว้ — พิสูจน์ข้างบน อาจทำให้ scheduling ค้างเงียบ ๆ หลังจาก N Pod แรก (N = จำนวน Node ที่ไม่ถูก taint) โดยไม่มี error ที่ชี้ไปที่สาเหตุจริงเลยpodAntiAffinityrequiredDuringSchedulingที่ใช้ทั้งที่เจตนาจริงคือ “กระจายเท่ากัน แต่อย่าบล็อกเพราะมัน” — พิสูจน์ข้างบนrequiredanti-affinity คือ all-or-nothingtopologySpreadConstraintsพร้อมwhenUnsatisfiable: ScheduleAnywayคือเครื่องมือที่อ่อนกว่าสำหรับเจตนานั้น- Deployment ที่ทำ rolling update พร้อม
topologySpreadConstraintsแต่ไม่มีmatchLabelKeys: [pod-template-hash]— พิสูจน์ข้างบน ถ้าไม่มีมัน spread constraint จะผสม generation เข้าด้วยกันระหว่าง rollout ซึ่งไม่ใช่เจตนาส่วนใหญ่ nodeSelectorหรือtolerationsที่อ้างถึง label/taint key ที่ไม่มี Node ไหนใน cluster มีจริง — นี่คือ no-op แบบเงียบ ๆ: toleration ไม่ match อะไรเลย และ Pod ก็ schedule ได้ (หรือไม่ได้) เหมือนไม่มีมันอยู่เลย ซึ่งเข้าใจผิดได้ง่ายว่า “มันกำลังป้องกัน taint นี้อยู่” ทั้งที่มันไม่ได้ป้องกันอะไรเลยtolerationSecondsที่ตั้งบน taint ที่ Pod ไม่ได้เจตนาจะทนจริง ๆ (เช่น copy มาจากตัวอย่างที่ไม่เกี่ยวข้อง) — พิสูจน์ข้างบน มันคือ timer จริงที่บังคับใช้ ค่าที่ผิดจะทำให้ evict เร็วเกินไปหรือปล่อยให้ Pod อยู่บน Node ที่แย่นานเกินตั้งใจ- toleration ของ Node เฉพาะทาง GPU/dedicated ที่ไม่มี
nodeSelector/affinity คู่กัน — Pod แค่หยุดถูกผลักออก มันไม่ได้ถูกดึงไปที่ Node ที่ถูก taint จึงลงที่ไหนใน cluster ก็ได้พอ ๆ กัน
🐛 หา bug ในโค้ดนี้
apiVersion: apps/v1kind: Deploymentmetadata:name: webspec:replicas: 6selector: matchLabels: app: webtemplate: metadata: labels: app: web spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web containers: - name: web image: registry.k8s.io/pause:3.10เฉลย
ดูเหมือนถูกต้อง — spread maxSkew: 1 มาตรฐานข้าม hostname — แต่มันขาด nodeTaintsPolicy: Honor พิสูจน์ข้างบนบน cluster 4 Node จริง (1 control-plane + 3 worker): kubectl explain ยืนยันว่า default เทียบเท่ากับ Ignore ดังนั้น Node control-plane ที่ถูก taint และ schedule ไม่ได้ยังถูกนับเป็น topology domain ที่ถูกต้องอยู่ที่ 0 Pod match พอแต่ละ worker ทั้ง 3 ตัวมี 1 Pod แล้ว Pod เพิ่มเติมใด ๆ จะทำให้ skew เป็น 2 - 0 = 2 เกิน maxSkew: 1 — replica ครึ่งหนึ่งพอดี (3 จาก 6) เลยค้างที่ Pending ถาวรด้วย didn't match pod topology spread constraints ทั่วทั้ง cluster โดยไม่มีปัญหา quota หรือ resource ขาดแคลนที่ไหนเลย ทางแก้คือเพิ่มบรรทัดเดียว: nodeTaintsPolicy: Honor ซึ่งตัด Node ที่ Pod นี้ tolerate ไม่ได้ออกจากการนับ domain คืนการกระจาย 2-2-2 ที่ตั้งใจไว้กลับมา