Node Affinity, Taint และ Toleration
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”Affinity ดึง Pod เข้าหา Node (หรือ Pod อื่น) บางตัว ส่วน 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: topology.kubernetes.io/zone operator: In values: - us-central1-a containers: - name: inference image: inference-server:2.1.0ทั้งสอง suffix ลงท้ายด้วย IgnoredDuringExecution หมายความว่าเมื่อ Pod รันอยู่แล้ว การเปลี่ยน label ของ Node ภายหลังจะไม่ทำให้ Pod ถูก evict — กฎพวกนี้มีผลแค่ตอนตัดสินใจ schedule ครั้งแรกเท่านั้น
Pod affinity กับ anti-affinity: schedule โดยอ้างอิง Pod อื่น
หัวข้อที่มีชื่อว่า “Pod affinity กับ anti-affinity: schedule โดยอ้างอิง Pod อื่น”Node affinity มองที่ label ของ Node ส่วน pod affinity และ pod anti-affinity มองที่ label ของ Pod อื่นที่รันอยู่แล้ว แทน โดยใช้ topologyKey เพื่อนิยามว่า “ใกล้กัน” หมายถึงอะไร (hostname, zone และอื่น ๆ) การใช้ที่พบบ่อยที่สุดคือกระจาย replica ของ app เดียวกันออกจากกันเพื่อความทนทาน
apiVersion: apps/v1kind: Deploymentmetadata: name: web-appspec: replicas: 3 selector: matchLabels: app: web-app template: metadata: labels: app: web-app spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - web-app topologyKey: kubernetes.io/hostname containers: - name: web-app image: web-app:3.2.0กฎ anti-affinity นี้บอก scheduler ว่า “ห้ามวาง Pod ของ web-app สองตัวบน host เดียวกัน” — เสีย Node ไปหนึ่งตัวจะไม่มีทางกระทบเกินหนึ่ง replica กรณีตรงข้ามคือ pod affinity ซึ่งเอา Pod มาอยู่ใกล้กันแทน เช่น ปัก cache sidecar ไว้บน Node เดียวกับ service ที่อ่าน cache นั้น เพื่อตัด traffic นั้นออกจาก network ไปเลย
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 ด้วย
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