今天先複習一下,昨天學的 nodeSelector
他的概念很簡單
假設:
disktype=ssd
Pod 指定:
nodeSelector:
disktype: ssd
代表:
只有符合
disktype=ssd的 Node 才能跑這個 Pod。
但 Production 常常不是只有「可以」和「不可以」。
例如:
API 最好跑 SSD,但 SSD 不夠時,其他 Node 也可以。
或者:
3 個 API Pod 不要全部塞在同一台 Node。
這時就需要:
Node Affinity
Pod Affinity / Anti-Affinity
Topology Spread Constraints
Node Affinity 可以理解成:
Pod 對 Node 提出更彈性的排程條件。
例如:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
- nvme
意思是:
Pod 只能排到
disktype=ssd或disktype=nvme的 Node。
跟 nodeSelector 相比,Node Affinity 可以使用:
In
NotIn
Exists
DoesNotExist
Gt
Lt
所以它能表達更複雜的條件。
Node Affinity 最重要的是分清楚這兩種規則。
required
代表:
一定要符合。
如果要求:
disktype=nvme
但 Cluster 沒有任何 NVMe Node,Pod 就會:
Pending
另一種是:
preferred
代表:
最好符合,但不是強制。
例如:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
意思是:
Scheduler 會優先選 SSD Node,但沒有 SSD 時還是可以選其他 Node。
所以記住:
required = 一定要
preferred = 最好有
requiredDuringSchedulingIgnoredDuringExecution
其實拆開就很好懂。
required
代表一定要符合。
DuringScheduling
代表排程時檢查。
IgnoredDuringExecution
代表 Pod 跑起來之後,即使 Node Label 後來改掉,也不會因此自動把 Pod 趕走。
所以整句就是:
排程時一定要符合,但執行後條件改變,不會自動驅逐 Pod。
假設:
API replicas = 3
Scheduler 可能排成:
worker1
├── api-1
├── api-2
└── api-3
技術上沒有錯,但只要 worker1 掛掉:
3 個 API 一起消失
所以我們通常希望:
worker1 → api-1
worker2 → api-2
worker3 → api-3
這時可以使用:
Pod Anti-Affinity
例如:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api
topologyKey: kubernetes.io/hostname
這段的意思是:
如果某台 Node 已經有
app=api的 Pod,新 API Pod 就盡量不要再放同一台。
這裡:
kubernetes.io/hostname
代表:
以 Node 為分散單位。
所以:
同 hostname
=同一台 Node
如果改成:
topology.kubernetes.io/zone
就代表:
以 Availability Zone 為分散單位。
例如:
Zone A → API
Zone B → API
Zone C → API
這可以降低單一 Node 或單一 Zone 故障時的影響。
兩個概念很好記。
Affinity:
我希望跟誰靠近。
Anti-Affinity:
我希望跟誰分開。
例如 Worker 跟 Redis 如果需要低延遲,可以考慮:
Pod Affinity
API replicas 為了避免全部集中在同一台 Node,可以使用:
Pod Anti-Affinity
如果你的需求不是「完全分開」,而是:
希望 Pod 在不同 Node 上分得平均一點。
就可以使用:
topologySpreadConstraints
例如:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api
maxSkew: 1 可以簡單理解成:
不同 Node 上的 API Pod 數量,差距不要太大。
例如:
worker1 = 2
worker2 = 1
可以。
但:
worker1 = 3
worker2 = 0
就比較不平均。
而:
ScheduleAnyway
代表:
即使無法完全平均,Pod 還是可以排程。
所以 Topology Spread 關心的是:
整體 Pod 分布是否平均。
Pod Anti-Affinity 則比較像:
某些 Pod 不要靠太近。
現在可以發現,Kubernetes Scheduling 不只是:
找一台 Node 把 Pod 塞進去
而是在平衡:
Resource
Performance
Availability
Failure Domain
Business Requirement
例如:
這台 Node CPU 夠不夠?
是不是 SSD?
Replica 有沒有全部擠在一起?
是不是全部都在同一個 Zone?
所以 Scheduling 真正做的是:
先找出可以跑 Pod 的 Node,再從中選出比較適合的 Node。
故意設定:
required
disktype=nvme
但 Cluster 裡沒有任何:
disktype=nvme
重新 Deploy 後,你會看到:
Pod → Pending
這時不要急著刪 Pod。
執行:
kubectl describe pod POD_NAME -n cka-lab
查看:
Events
可能會看到:
node(s) didn't match Pod's node affinity/selector
意思就是:
Scheduler 找不到符合條件的 Node。
這是很典型的 CKA Scheduling 排錯題。
今天的 Scheduling 可以整理成:
Node 條件
│
├── nodeSelector
└── Node Affinity
Pod 與 Pod
│
├── Pod Affinity
└── Pod Anti-Affinity
Pod 整體分布
│
└── Topology Spread Constraints
最重要的差異就是:
Node Affinity
=Pod 想去哪種 Node
Pod Affinity
=Pod 想跟誰靠近
Pod Anti-Affinity
=Pod 想跟誰分開
Topology Spread
=Pod 要怎麼分得比較平均
昨天學的是:
能不能去?
今天開始學的是:
去哪裡比較好?
下一篇會進入:
cordon
drain
uncordon
PodDisruptionBudget
也就是:
如果 Node 要停機維修,要怎麼安全地把它下線?