1. 調度器在 K8s 中的位置與核心流程
API Server ←→ etcd ←→ kube-scheduler ←→ kubelet
創建:用戶提交 Pod 描述(YAML/Helm/Operator)。
監聽:調度器通過 Watch 機制捕獲到
spec.nodeName=""
的 Pod。過濾:根據資源、污點、親和性等“硬性條件”過濾出可行節點(Feasible Nodes)。
打分:對可行節點按策略打分,最高分勝出。
綁定:將
spec.nodeName
寫入 Pod 對象,目標節點的 kubelet 開始真正啟動容器。重調度:節點故障或資源不足 → 刪除原 Pod → 回到步驟 2。
調度器是控制面唯一的“決策大腦”,但它不做網絡/存儲分配;它只是給 Pod 選“座位”。
2. 調度方式全景圖
級別 | 方法 | 典型場景 | 備注 |
---|---|---|---|
強制 | nodeName | 排障、DaemonSet | 優先級最高,繞過調度器 |
標簽 | nodeSelector | 指定 gpu=true、ssd=true | 簡單,功能有限 |
親和 | nodeAffinity | 軟/硬親和 | 支持 In/NotIn/Gt/Lt/Exists |
Pod 間 | podAffinity / podAntiAffinity | 同域部署、打散 | 需大量計算,大集群慎用 |
污點 | taint + toleration | 隔離生產/測試、驅逐 | NoSchedule / PreferNoSchedule / NoExecute |
高級 | 多調度器、擴展器、Score 插件 | 自定義算法 | 自 1.19 支持 Scheduling Framework |
3. 手把手實戰:從簡單到高階
3.1 nodeName —— 一把梭,但風險高
apiVersion: v1
kind: Pod
metadata:name: one-shot-tool
spec:nodeName: k8s-node2 # 直接綁定,不經過調度器containers:- name: debugimage: alpine:latestcommand: ["sleep", "3600"]
缺點:節點不存在或資源不足時直接 Pending,調度器不會幫你重試。
3.2 nodeSelector —— 80% 場景已夠用
# 給節點打標簽
kubectl label node k8s-node1 disktype=ssd zone=beijing
spec:nodeSelector:disktype: ssdzone: beijing
小技巧:對同一類節點批量打標簽
kubectl label node -l node-role.kubernetes.io/worker= tier=frontend
3.3 nodeAffinity —— 軟/硬策略組
nodeaffinity支持多種規則匹配條件的配置如
匹配規則 | 功能 |
---|---|
ln | label 的值在列表內 |
Notln | label 的值不在列表內 |
Gt | label 的值大于設置的值,不支持Pod親和性 |
Lt | label 的值小于設置的值,不支持pod親和性 |
Exists | 設置的label 存在 |
DoesNotExist | 設置的 label 不存在 |
affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution: # 必須滿足nodeSelectorTerms:- matchExpressions:- {key: disktype, operator: In/NotIn(#在節點中或不在), values: ["ssd"]}preferredDuringSchedulingIgnoredDuringExecution: # 盡量滿足- weight: 50preference:matchExpressions:- {key: zone, operator: In, values: ["beijing"]}
IgnoreDuringExecution:節點標簽變更后,已運行 Pod 不動。
支持
Gt/Lt
做資源范圍篩選,如 cpu 核數大于 32。
3.4 Pod 間親和與反親和
那個節點有符合條件的POD就在那個節點運行
podAffinity 主要解決POD可以和哪些POD部署在同一個節點中的問題
podAntiAffinity主要解決POD不能和哪些POD部署在同一個節點中的問題。它們處理的是Kubernetes集群內部POD和POD之間的關系。
Pod 間親和與反親和在與更高級別的集合(例如 ReplicaSets,StatefulSets,Deployments 等)一起使用時,
Pod 間親和與反親和需要大量的處理,這可能會顯著減慢大規模集群中的調度。
3.4.1 親和:主要解決POD可以和哪些POD部署在同一個節點中的問題
3.4.2 反親和:主要解決POD不能和哪些POD部署在同一個節點中的問題
經驗值:大規模集群開啟
topologyKey: topology.kubernetes.io/zone
可實現跨可用區打散。
3.5 Taint & Toleration —— 隔離與驅逐雙殺
3.5.1 概念
Taints(污點)是Node的一個屬性,設置了Taints后,默認Kubernetes是不會將Pod調度到這個Node上
Kubernetes如果為Pod設置Tolerations(容忍),只要Pod能夠容忍Node上的污點,那么Kubernetes就會忽略Node上的污點,就能夠(不是必須)把Pod調度過去
可以使用命令 kubectl taint 給節點增加一個 taint:
$ kubectl taint nodes <nodename> key=string:effect ? #命令執行方法
$ kubectl taint nodes node1 key=value:NoSchedule ? #創建
$ kubectl describe nodes server1 | grep Taints ? ? ? #查詢
$ kubectl taint nodes node1 key- ? ? ? ? ? ? ? ? #刪除
其中[effect] 可取值:
effect | 作用 | 常用場景 |
---|---|---|
NoSchedule | 新 Pod 不來 | GPU 節點僅跑 AI 任務 |
PreferNoSchedule | 盡量不調度 | 線上節點留有余量 |
NoExecute | 已運行 Pod 驅逐 | 節點維護、內核升級 |
3.5.2 實戰:
測試污點容忍,node1,node2都設置為NoSchedule
未設置容忍的則顯示為pending
4. 調度器擴展:自定義策略與性能
4.1 多調度器
當默認調度器無法滿足業務場景(如 GPU Binpack、Spark 動態資源)時,可部署自定義調度器并指定:
spec:schedulerName: volcano
4.2 Scheduling Framework(1.19+)
通過插件鏈在 PreFilter / Filter / Score / Reserve / Permit / Bind 階段插入自定義邏輯,無需重編 kube-scheduler。
示例:NodeResourcesFit、PodTopologySpread、Coscheduling(gang scheduling)。
4.3 性能調優
大規模集群建議開啟
percentageOfNodesToScore
(默認 50%),減少打分節點數量。監控指標:
scheduler_binding_duration_seconds
、scheduler_scheduling_attempt_duration_seconds
。
5. 排查 Pod 無法調度的一頁速查
現象 | 可能原因 | 排查命令 |
---|---|---|
Pending | 資源不足 | kubectl describe pod ?→ Events |
Pending | 節點污點 | kubectl get node -o json | jq '.items[].spec.taints' |
Pending | 親和沖突 | kubectl get pod -o wide ?查標簽 |
Pending | PVC 未綁定 | kubectl get pvc |
Node Lost | 節點 NotReady | kubectl get node , 查 kubelet / 網絡 / 磁盤 |
6. 總結與最佳實踐
90% 場景
nodeSelector + nodeAffinity + podAntiAffinity 即可滿足。污點策略
? 生產節點:dedicated=production:NoSchedule
? 測試節點:env=test:PreferNoSchedule
大規模集群
? 避免 topologyKey 過細,減少計算量。
? 使用 namespace + 污點做物理隔離,而非反親和暴力打散。持續治理
? 每個季度 review 節點標簽、污點、調度策略。
? 用 Gatekeeper / Kyverno 做策略即代碼(Policy as Code)。
附錄:一鍵清理實驗資源
kubectl delete pod,deploy --all --grace-period=0 --force