昨天我們認識了 kube-scheduler 的排程流程:Filtering → Scoring → Binding,也學會用 requests 和 limits 來管理資源。
還記得我們用 nodeSelector: gpu: "true",讓 Pod 因為找不到符合條件的 Node 而卡在 Pending 嗎?
nodeSelector 雖然簡單好用,但它只能做完全匹配 —— 「有這個標籤才能排」,缺乏更細緻的排程彈性。
如果你的需求是:
那 nodeSelector 就不夠用了。
今天我們會從 nodeSelector 開始,進一步認識 Node Affinity 與 Taints & Tolerations 這兩種排程控制機制:
以下操作皆在 master 節點 執行。
先快速回顧 Day 12 用過的 nodeSelector:
spec:
nodeSelector:
gpu: "true" # Node 必須有 gpu=true 標籤
它的侷限:
而 Node Affinity 可以提供更豐富的條件與「必要 / 偏好」規則,可以把它看成是 nodeSelector 更靈活、更進階的版本。
想像你在找餐廳:
nodeSelector:「我只吃米其林三星」
→ 條件必須完全符合,找不到就沒得選
Node Affinity(required):「我只吃日式料理,而且還可以加上更多條件」
→ 屬於硬性要求,只會選符合條件的餐廳
Node Affinity(preferred):「我優先吃日式料理,沒有的話其他餐廳也可以」
→ 屬於偏好條件,找不到符合偏好的選項時,仍然可以接受其他選擇
| 模式 | 行為 | 類比 |
|---|---|---|
| requiredDuringSchedulingIgnoredDuringExecution | 硬性條件:排程時一定要符合,不符合就會 Pending |
「沒有日式料理我就不吃」 |
| preferredDuringSchedulingIgnoredDuringExecution | 偏好條件:排程時會盡量滿足,但不是強制要求 | 「優先日式,沒有就算了」 |
名字為什麼這麼長?
- DuringScheduling:Scheduler 在排程 Pod 時會檢查這個規則
- IgnoredDuringExecution:Pod 已經執行後,即使 Node 的 Label 發生變化,也不會因此自動被驅逐
跟 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 |
kubectl get nodes
kubectl label nodes <worker-node-name> disktype=ssd
確認標籤加上了:
kubectl get nodes --show-labels | grep disktype

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 上。

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 只是代表「優先選擇」,而不是硬性要求。

這就是 preferred 和 required 的差別:
preferred:盡量選擇符合條件的 Node,沒有也可以排程required:一定要符合條件,否則 Pod 會停留在 Pending
記得測完後把標籤加回來,後續實作會用到:
kubectl label nodes <worker-node-name> disktype=ssd
💡 一句話總結
required是硬性條件,不符合就無法排程;preferred是偏好條件,會影響 Scoring 階段的評分,但不會因為偏好條件不符合就讓 Pod 停留在Pending。
如果說 Node Affinity 是 Pod 在選 Node,那 Taints & Tolerations 就是 Node 在限制哪些 Pod 可以排進來。
可以把它想成一個工地掛著「施工中,閒人勿近」的牌子(Taint),只有符合進場條件的工人(具有對應 Toleration 的 Pod)才有機會進去。
要注意的是,有 Toleration 不代表 Pod 一定會被排到這個 Node,它只是表示這個 Taint 不會阻止 Pod 被排到這裡。
kubectl taint nodes <node-name> key=value:effect
三種 Effect(效果):
| Effect | 行為 | 類比 |
|---|---|---|
| NoSchedule | 沒有對應 Toleration 的新 Pod 不能排上來;已經在跑的 Pod 不受影響 | 「施工中,新客人不能進」 |
| PreferNoSchedule | Scheduler 會盡量避免把沒有對應 Toleration 的 Pod 排上來,但不是硬性限制 | 「施工中,非必要別進來」 |
| NoExecute | 沒有對應 Toleration 的新 Pod 不能排上來;已經在跑的 Pod 也可能被驅逐 | 「施工中,不符合資格的人要離開」 |
kubectl taint nodes <worker-node-name> env=production:NoSchedule
確認 Taint:
kubectl describe node <worker-node-name> | grep Taint

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

如果你只有一個 worker Node,這個 Pod 會 Pending — 因為它無法容忍 env=production:NoSchedule 這個 Taint。
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。

kubectl taint nodes <worker-node-name> env=production:NoSchedule-
注意最後的 - 號,表示移除。
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 | 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 的排程位置。
在實際環境中,通常會同時使用 Node Affinity 和 Taints & Tolerations。以下是一個應用場景:
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 Affinity 與 Anti-Affinity,學會控制 Pod 彼此之間的排程關係!