iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Kubernetes

從零到 CKA:30 天掌握 Kubernetes 核心觀念與實作系列 第 13

Day 13|Node Affinity 與 Taints & Tolerations — 精準控制 Pod 的排程位置

  • 分享至 

  • xImage
  •  

前言

昨天我們認識了 kube-scheduler 的排程流程:Filtering → Scoring → Binding,也學會用 requestslimits 來管理資源。

還記得我們用 nodeSelector: gpu: "true",讓 Pod 因為找不到符合條件的 Node 而卡在 Pending 嗎?

nodeSelector 雖然簡單好用,但它只能做完全匹配 —— 「有這個標籤才能排」,缺乏更細緻的排程彈性。

如果你的需求是:

  • 優先排到有 SSD 的 Node,沒有的話其他 Node 也可以」
  • 「這台 Node 只給特定工作負載使用,其他 Pod 不准進來」

nodeSelector 就不夠用了。

今天我們會從 nodeSelector 開始,進一步認識 Node AffinityTaints & Tolerations 這兩種排程控制機制:

  1. nodeSelector 的侷限 — 為什麼需要更靈活的排程方式?
  2. Node Affinity — Pod 說:「我想去哪裡」
  3. Taints & Tolerations — Node 說:「你不能來」,Pod 說:「我可以容忍」
  4. Node Affinity vs Taints & Tolerations — 兩者有什麼差別?什麼時候該用?
  5. 實戰組合應用 — GPU Node 只跑 ML 工作負載

以下操作皆在 master 節點 執行。


一、nodeSelector 的侷限

先快速回顧 Day 12 用過的 nodeSelector

spec:
  nodeSelector:
    gpu: "true"    # Node 必須有 gpu=true 標籤

它的侷限:

  • 只能完全匹配:標籤的 key/value 必須符合,無法表達「不要這個值」、「數值要大於某個條件」這類更複雜的規則
  • 沒有偏好機制:條件要嘛符合、要嘛不符合,無法表達「優先選這個 Node,但其他 Node 也可以」
  • 無法表達排除條件:不能直接指定「不要排到具有某些標籤的 Node」

Node Affinity 可以提供更豐富的條件與「必要 / 偏好」規則,可以把它看成是 nodeSelector 更靈活、更進階的版本。


二、Node Affinity — 更靈活的「我想去哪」

用比喻理解

想像你在找餐廳:

  • nodeSelector:「我只吃米其林三星」
    → 條件必須完全符合,找不到就沒得選

  • Node Affinity(required):「我只吃日式料理,而且還可以加上更多條件」
    → 屬於硬性要求,只會選符合條件的餐廳

  • Node Affinity(preferred):「我優先吃日式料理,沒有的話其他餐廳也可以」
    → 屬於偏好條件,找不到符合偏好的選項時,仍然可以接受其他選擇

兩種模式

模式 行為 類比
requiredDuringSchedulingIgnoredDuringExecution 硬性條件:排程時一定要符合,不符合就會 Pending 「沒有日式料理我就不吃」
preferredDuringSchedulingIgnoredDuringExecution 偏好條件:排程時會盡量滿足,但不是強制要求 「優先日式,沒有就算了」

名字為什麼這麼長?

  • DuringScheduling:Scheduler 在排程 Pod 時會檢查這個規則
  • IgnoredDuringExecution:Pod 已經執行後,即使 Node 的 Label 發生變化,也不會因此自動被驅逐

支援的 Operator

nodeSelector 只能做精確比對不同,Node Affinity 支援多種條件:

Operator 意思 範例
In Label 的值在指定列表中 disktype In [ssd, nvme]
NotIn Label 的值不在指定列表中 env NotIn [prod]
Exists 只要這個 Label 存在即可,不在乎值 gpu Exists
DoesNotExist 這個 Label 不存在 maintenance DoesNotExist
Gt Label 的整數值大於指定數字 core-count Gt 4
Lt Label 的整數值小於指定數字 core-count Lt 16

實作:用 Node Affinity 排程

Step 1:先幫 Node 加上標籤

kubectl get nodes
kubectl label nodes <worker-node-name> disktype=ssd

確認標籤加上了:

kubectl get nodes --show-labels | grep disktype

https://ithelp.ithome.com.tw/upload/images/20260815/20181928yMMe6k1NtH.png

Step 2:建立使用 required 的 Pod

vim affinity-required.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity-required
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
            - nvme
  containers:
  - name: nginx
    image: nginx
kubectl apply -f affinity-required.yaml
kubectl get pod affinity-required -o wide

Pod 應該會被排到有 disktype=ssd 標籤的 Node 上。

https://ithelp.ithome.com.tw/upload/images/20260815/20181928RutW8lfm3V.png

Step 3:建立使用 preferred 的 Pod

vim affinity-preferred.yaml
apiVersion: v1
kind: Pod
metadata:
  name: affinity-preferred
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80        # 權重 1-100,越高越優先
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
  containers:
  - name: nginx
    image: nginx

驗證偏好行為:可以移除 worker Node 上的 disktype=ssd 標籤,再建立一個新的 preferred Pod,觀察它仍然會被排程(而不是 Pending):

# 移除標籤
kubectl label nodes <worker-node-name> disktype-

# 建立新 Pod 測試
kubectl run affinity-preferred-test --image=nginx --dry-run=client -o yaml > /dev/null
kubectl delete pod affinity-preferred
kubectl apply -f affinity-preferred.yaml
kubectl get pod affinity-preferred -o wide

Pod 會順利進入 Running 狀態。

即使目前沒有任何 Node 具有 disktype=ssd 這個 Label,Pod 也不會因此停留在 Pending,因為 preferred 只是代表「優先選擇」,而不是硬性要求。

https://ithelp.ithome.com.tw/upload/images/20260815/20181928qkFE9q9d0V.png

這就是 preferredrequired 的差別:

  • preferred:盡量選擇符合條件的 Node,沒有也可以排程
  • required:一定要符合條件,否則 Pod 會停留在 Pending

記得測完後把標籤加回來,後續實作會用到:

kubectl label nodes <worker-node-name> disktype=ssd

💡 一句話總結

required 是硬性條件,不符合就無法排程;preferred 是偏好條件,會影響 Scoring 階段的評分,但不會因為偏好條件不符合就讓 Pod 停留在 Pending


三、Taints & Tolerations — Node 的「拒絕權」

用比喻理解

如果說 Node Affinity 是 Pod 在選 Node,那 Taints & Tolerations 就是 Node 在限制哪些 Pod 可以排進來

  • Taint(污點):Node 說:「我有特殊限制,不是所有 Pod 都能來」
  • Toleration(容忍):Pod 說:「我可以容忍這個限制」

可以把它想成一個工地掛著「施工中,閒人勿近」的牌子(Taint),只有符合進場條件的工人(具有對應 Toleration 的 Pod)才有機會進去。

要注意的是,有 Toleration 不代表 Pod 一定會被排到這個 Node,它只是表示這個 Taint 不會阻止 Pod 被排到這裡。

Taint 的格式

kubectl taint nodes <node-name> key=value:effect

三種 Effect(效果)

Effect 行為 類比
NoSchedule 沒有對應 Toleration 的新 Pod 不能排上來;已經在跑的 Pod 不受影響 「施工中,新客人不能進」
PreferNoSchedule Scheduler 會盡量避免把沒有對應 Toleration 的 Pod 排上來,但不是硬性限制 「施工中,非必要別進來」
NoExecute 沒有對應 Toleration 的新 Pod 不能排上來;已經在跑的 Pod 也可能被驅逐 「施工中,不符合資格的人要離開」

實作:Taint 與 Toleration

Step 1:給 Node 加上 Taint

kubectl taint nodes <worker-node-name> env=production:NoSchedule

確認 Taint:

kubectl describe node <worker-node-name> | grep Taint

https://ithelp.ithome.com.tw/upload/images/20260815/201819282fRhKD3KsB.png

Step 2:部署一個沒有 Toleration 的 Pod

vim no-toleration.yaml
apiVersion: v1
kind: Pod
metadata:
  name: no-toleration
spec:
  containers:
  - name: nginx
    image: nginx
kubectl apply -f no-toleration.yaml
kubectl get pod no-toleration -o wide

https://ithelp.ithome.com.tw/upload/images/20260815/20181928ODeMXc5Zpc.png

如果你只有一個 worker Node,這個 Pod 會 Pending — 因為它無法容忍 env=production:NoSchedule 這個 Taint。

Step 3:部署一個有 Toleration 的 Pod

vim with-toleration.yaml
apiVersion: v1
kind: Pod
metadata:
  name: with-toleration
spec:
  tolerations:
  - key: "env"
    operator: "Equal"
    value: "production"
    effect: "NoSchedule"
  containers:
  - name: nginx
    image: nginx
kubectl apply -f with-toleration.yaml
kubectl get pod with-toleration -o wide

這次 Pod 順利排上去了!因為它有對應的 Toleration。

https://ithelp.ithome.com.tw/upload/images/20260815/20181928WR6p2iBtv7.png

Step 4:移除 Taint

kubectl taint nodes <worker-node-name> env=production:NoSchedule-

注意最後的 - 號,表示移除。

NoExecute 與 tolerationSeconds

NoExecute 比較特殊 — 它不只擋新的 Pod,還會驅逐已經在跑的 Pod

如果 Pod 有對應的 Toleration,可以加上 tolerationSeconds 來設定「能忍受多久」:

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 300    # 忍受 5 分鐘,之後還是會被驅逐

Master Node 的預設 Taint

在使用 kubeadm 建立的叢集中,Control Plane Node 通常會帶有:

node-role.kubernetes.io/control-plane:NoSchedule

這代表一般沒有對應 Toleration 的工作負載,不會被排程到 Control Plane Node。

如果是在單節點測試環境,希望 Control Plane Node 也能執行一般 Pod,可以移除這個 Taint:

kubectl taint nodes <master-node> node-role.kubernetes.io/control-plane:NoSchedule-

這種做法比較適合測試環境;正式環境通常會保留 Control Plane Node 的排程限制。


四、Node Affinity vs Taints & Tolerations 對照

比較項目 Node Affinity Taints & Tolerations
誰發起? Pod 選擇適合的 Node Node 限制哪些 Pod 可以進來
方向 吸引:「我想去那裡」 排斥:「不是所有 Pod 都能來」
硬性 / 軟性 required(硬)/ preferred(軟) NoSchedule(硬)/ PreferNoSchedule(軟)
影響已在跑的 Pod? IgnoredDuringExecution 不會因 Label 改變而自動驅逐 NoExecute 可能驅逐沒有對應 Toleration 的 Pod
典型場景 Pod 需要特定硬體或 Node 特性(SSD、GPU) Node 有專屬用途,只希望特定工作負載使用

💡 一句話總結

Node Affinity 是 Pod 的「志願表」,用來表達自己想去哪個 Node;Taints & Tolerations 則像 Node 的「門禁系統」,用來限制哪些 Pod 可以進來。

兩者搭配使用,就能更精準地控制 Pod 的排程位置。


五、實戰組合應用:GPU Node 只跑 ML 工作負載

在實際環境中,通常會同時使用 Node Affinity 和 Taints & Tolerations。以下是一個應用場景:

需求

  • 叢集中有幾台 GPU Node,價格昂貴
  • 只有 ML 訓練的 Pod 能跑在 GPU Node 上
  • ML Pod 也只能跑在 GPU Node 上(不要浪費跑到普通 Node)

解法

Step 1:幫 GPU Node 加標籤和 Taint

# 加標籤(讓 Pod 可以用 Affinity 選它)
kubectl label nodes <gpu-node> accelerator=nvidia-tesla-v100

# 加 Taint(擋住一般 Pod)
kubectl taint nodes <gpu-node> accelerator=nvidia-tesla-v100:NoSchedule

Step 2:ML Pod 同時設定 Affinity + Toleration

apiVersion: v1
kind: Pod
metadata:
  name: ml-training
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: accelerator
            operator: In
            values:
            - nvidia-tesla-v100
  tolerations:
  - key: "accelerator"
    operator: "Equal"
    value: "nvidia-tesla-v100"
    effect: "NoSchedule"
  containers:
  - name: training
    image: pytorch/pytorch:latest
    resources:
      requests:
        cpu: "4"
        memory: "8Gi"
      limits:
        cpu: "8"
        memory: "16Gi"

為什麼兩個都要?

情境 結果
只有 Taint(沒有 Affinity) ML Pod 雖然可以進入 GPU Node,但仍可能被排到其他普通 Node ❌
只有 Affinity(沒有 Taint) ML Pod 會被限制到 GPU Node,但其他普通 Pod 仍可能被排到 GPU Node,浪費昂貴的 GPU 資源 ❌
Taint + Affinity 一起用 GPU Node 只讓指定工作負載進入 ✅ ML Pod 也只會被排到 GPU Node ✅

小結

今天我們學會了兩種精準控制排程的機制:

重點 說明
Node Affinity Pod 的「志願表」— required 是硬性條件,preferred 是偏好條件
Taints Node 的「門禁系統」— NoSchedule / PreferNoSchedule / NoExecute
Tolerations Pod 的「通行證」— 讓 Pod 可以容忍對應的 Taint
組合使用 Taint 限制一般 Pod + Affinity 指定目標 Node = 更精準的排程控制
NoExecute 不只影響新 Pod,也可能驅逐沒有對應 Toleration 的既有 Pod

到目前為止,我們已經學會如何控制 Pod 要被排到哪個 Node

但在真實環境中,應用程式通常會由多個 Pod 組成,而這些 Pod 之間也可能有自己的排程需求。

例如:

  • 「這兩個 Pod 希望排在同一台 Node」
  • 「這兩個 Pod 最好不要排在一起」
  • 「同一類型的 Pod 要盡量分散到不同 Node」

明天我們來看看 Pod Affinity 與 Anti-Affinity,學會控制 Pod 彼此之間的排程關係!


參考資源


上一篇
Day 12|Kubernetes Scheduler 與資源管理 — Pod 到底會被排到哪個 Node?
下一篇
Day 14|Pod Affinity 與 Anti-Affinity — 控制 Pod 之間的排程關係
系列文
從零到 CKA:30 天掌握 Kubernetes 核心觀念與實作15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言