概述
什麼是 kube-scheduler ?
Kubernetes 叢集的核心元件之一,它負責為新建立的 Pods 分配節點。它根據多種因素進行決策,包括:
- 資源需求和限制:考慮每個 Pod 請求的資源量(如 CPU 和記憶體)以及節點上可用的資源。
- 親和性和反親和性規則:根據 Pod 的親和性設定選擇最適合的節點。
- 健康檢查:確保選擇的節點健康且能夠執行 Pod。
- 負載均衡:儘量平衡叢集中各個節點的負載。
使用
limits 和 reuqests
在部署物件中的 spec 中常常會見到關於 limits
和 requests
的宣告 ,例如:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: 1Gi
cpu: 1
requests:
memory: 256Mi
cpu: 100m
這裡的 limits 和 requests 是與 Pod 容器資源管理相關的兩個關鍵概念:
- Limits:指定容器執行時能夠使用的最大資源量
- Requests:指定容器啟動時最低需要的資源量
limits 和 requests 跟 scheduler 有什麼關係 ?
在叢集中 kube-scheduler 一直是默默無聞的幕後工作者,它主要工作內容如下:
- 當你建立一個 Deployment,如這個
nginx
,是由 kube-scheduler 決策將其排程到哪個 Node 上執行的 - kube-scheduler 會監聽 apiserver 獲取叢集全域性檢視,然後根據 Pod 的資源請求(requests 和 limits)分析
- 最終 kube-scheduler 會結合資源請求和叢集的實際情況來排程 Pod
總之,kube-scheduler 會保證 Pod 會排程到滿足其執行資源需求的 Node 節點上。
LimitRange
描述
LimitRange 是資源描述物件,主要用於限制名稱空間內資源的使用。它可以設定預設的資源請求和限制,以及資源使用的最大和最小值。它可以確保每個 Pod 或容器在資源使用上遵循特定的策略,從而避免單個 Pod 或容器佔用過多資源。使用示例如下:
建立一個 YAML 檔案儲存 LimitRange 內容,例如:mem-limit-range.yaml
:
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
defaultRequest:
memory: 256Mi
type: Container
應用到叢集:
$ kubectl apply -f mem-limit-range.yaml
檢視建立的 LimitRange 物件:
$ kubectl describe limitrange mem-limit-range
輸出:
Name: mem-limit-range
Namespace: default
Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio
---- -------- --- --- --------------- ------------- -----------------------
Container memory - - 256Mi 512Mi -
說明:
- Kind:設定為 LimitRange,用於限制名稱空間內資源的使用。
- Metadata:設定資源的名稱
- Spec:
- Limits:
- default:指定沒有明確資源限制的容器的預設記憶體限制為 512Mi
- defaultRequest:指定沒有明確資源請求的容器的預設記憶體請求。這裡設定為 256Mi
- type:應用這些限制的資源型別,在這裡是
Container
驗證
定義一個沒有宣告資源請求的部署物件,檔案命名為: nginx-without-resource.yaml
,如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
應用部署到叢集:
$ kubectl apply -f nginx-without-resource.yaml
等 Pod 建立後,可以透過檢查它們的配置來確認 LimitRange
是否生效。
$ kubectl describe pod [POD_NAME]
輸出:
Containers:
#.. ignore
Limits:
memory: 512Mi
Requests:
memory: 256Mi
initContainers
initContainers 用於在主應用容器啟動之前執行一些預備任務。常見於以下場景:
- 準備工作:設定需要的配置檔案、資料庫遷移、等待其他服務就緒等。
- 安全性:許可權提升操作,如改變檔案許可權或者執行特定的安全檢查。
- 服務依賴性:等待其他服務或資料庫可用。
initContainers 在執行完其任務後會停止,且必須成功完成才能啟動主容器。非常適合用於啟動前的初始化任務。
示例:
在部署物件中宣告 initContainers 屬性:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 10']
containers:
- name: nginx
image: nginx
將部署物件應用到叢集:
$ kubectl apply -f init-container.yaml
當 Pod 啟動後,可以透過檢視事件日誌驗證容器的載入順序:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m20s default-scheduler Successfully assigned default/nginx-deployment-6445f86ddc-fmmzw to docker-desktop
Normal Pulling 2m20s kubelet Pulling image "busybox:1.28"
Normal Pulled 116s kubelet Successfully pulled image "busybox:1.28" in 23.099396719s (23.099404677s including waiting)
Normal Created 116s kubelet Created container init-myservice
Normal Started 116s kubelet Started container init-myservice
Normal Pulling 106s kubelet Pulling image "nginx"
Normal Pulled 88s kubelet Successfully pulled image "nginx" in 18.382000675s (18.382006008s including waiting)
Normal Created 88s kubelet Created container nginx
Normal Started 88s kubelet Started container nginx
可以看到 initContainers 宣告的容器已經載入,然後檢視特定的日誌,來檢查 Pod 日誌輸出:
$ kubectl logs [POD_NAME] -c init-myservice
輸出:
The app is running!
驗證完成。
initContainers 和 kube-scheduler 的關係 ?
如果 initContainers 沒有宣告資源需求,預設也會使用 LimitRange 宣告的預設資源,這也意味著,initContainers 也是由 kube-scheduler 來排程建立的。所以在 initContainers 中加上資源需求也會影響著 kube-scheduler 的排程決策。
nodeSelector
在部署物件中,nodeSelector
屬性的作用是用於把指定 Pod 排程到具有特定標籤的節點上。如果沒有滿足要求的 Node 節點,則 Pod 會持續等待,示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
disktype: ssd
在這個例子中 nodeSelector 屬性值為:disktype: ssd
。這表明這個 Pod 應該被排程到標籤為 disktype=ssd
的 Node 節點上。kube-scheduler 在排程時,會選擇合適的節點以執行這個 Pod 時。
先將部署物件應用到叢集中:
$ kubectl apply -f node-selector.yaml
然後檢視 Pod 狀態:
$ kubectl get pod
輸出:
NAME READY STATUS RESTARTS AGE
nginx-deployment-f5bc98d57-pmq9v 0/1 Pending 0 2m17s
可以看到建立的 Pod 一直保持在 "Pending" 狀態。透過事件日誌檢視具體原因:
$ kubectl describe pod [POD_NAME]
輸出:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 4m38s default-scheduler 0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..
從事件日誌可以看出,這個 Pod 不能被排程,因為沒有節點滿足其設定的節點選擇條件。因為我的叢集中確實沒有任何標記為 disktype: ssd
的節點在執行。
Affinity 親和性
NodeSelector 的演進版本,提供了更復雜的選擇規則。除了簡單的匹配,它們還支援更豐富的條件表示式,如 "存在"、"不等於"、"在集合中" 等,並且支援對 Pod 之間(Pod Affinity/Anti-Affinity)以及 Pod 與節點之間(Node Affinity)的親和性/反親和性設定。在 Kubernetes 後續版本中 Affinity 也逐漸替代了 NodeSelector。
podAffinity
podAffinity 用於定義 Pods 之間的親和性。使得某個 Pod 被排程到與其他特定標籤的 Pod 相同的節點上。
使用場景:當希望一組服務緊密地協同工作時,比如一個應用的不同元件需要低延遲通訊。
示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-anti
spec:
replicas: 2
selector:
matchLabels:
app: anti-nginx
template:
metadata:
labels:
app: anti-nginx
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: a
operator: In
values:
- b
topologyKey: kubernetes.io/hostname
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- anti-nginx
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: nginx
部署檔案展示親和性(Affinity)設定:
- PodAffinity:要求排程的 Pod 必須與具有特定標籤(鍵
a
,值b
)的 Pod 在相同的節點上。 - PodAntiAffinity:要求排程的 Pod 不能與具有相同標籤(鍵
app
,值anti-nginx
)的 Pod 在相同的節點上。
將上面部署檔案應用到叢集后,檢視 Pods 的分佈情況:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-anti-5656fcbb98-62mds 0/1 Pending 0 5s <none> <none> <none> <none>
nginx-anti-5656fcbb98-wxphs 0/1 Pending 0 5s <none> <none> <none> <none>
可以 Pod 因為親和性規則無法排程一直處於等待狀態,檢視特定 Pod 的事件日誌可以驗證:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 27s default-scheduler 0/1 nodes are available: 1 node(s) didn't match pod affinity rules. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..
利用 Pod 親和性和反親和性規則來控制 Pod 的排程位置,以實現特定的排程需求和負載分佈。
nodeAffinity
用於定義 Pod 與節點之間的親和性。控制 Pod 被排程到具有特定標籤或屬性的節點上。
適用場景:當您需要根據硬體特性(如 GPU、高效能儲存)或其他自定義標籤(如環境標籤)排程 Pod 時。
示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
部署檔案的親和性(Affinity)設定:
nodeAffinity
被設定為要求 Pod 被排程到具有disktype: ssd
標籤的節點上。
將上面部署檔案應用到叢集后,檢視 Pod 的執行情況:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-565d7797dc-jf5nk 0/1 Pending 0 14s <none> <none> <none> <none>
可以 Pod 因為親和性規則無法排程一直處於等待狀態,檢視特定 Pod 的事件日誌可以驗證:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 89s default-scheduler 0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..
preferredDuringSchedulingIgnoredDuringExecution
和之前的 requiredDuringScheduling 排程型別不同,preferredDuringScheduling 表明其是一個偏好性的排程,排程器會根據偏好優先選擇滿足對應規則的節點來排程Pod。但如果找不到滿足規則的節點,排程器則會選擇其他節點來排程Pod。
示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: disktype
operator: In
values:
- ssd
containers:
- name: nginx
image: nginx
配置說明:這裡使用的是 preferredDuringSchedulingIgnoredDuringExecution
型別,這意味著排程器會盡量但不強制將 Pod 排程到具有 disktype: ssd
標籤的節點上。
將上面部署檔案應用到叢集后,檢視 Pod 的執行情況:
NAME READY STATUS RESTARTS AGE
nginx-deployment-69c654d896-7qh8t 1/1 Running 0 28s
可以看到雖然我本地沒有滿足親和性規則的 Node 節點,但是 Pod 依然可以排程起來了。
總結:
podAffinity
關注的是 Pod 之間的關係不同nodeAffinity
更關注 Pod 與節點特性之間的關係- requiredDuringScheduling:硬親和,強制型排程規則,必須滿足親和性設定,否則不能排程
- preferredDuringScheduling:軟親和,偏好型排程規則,首先找到滿足設定的節點,沒有則會排程到其他節點
Taints 汙點
Taints 和 Tolerations 是 Kubernetes 中用於控制 Pod 排程到特定節點的一種機制,相比 Affinity 親和性 **相似性 **的機制,Taints 的規則是屬於 排斥性 的機制,用來“排斥”不滿足特定條件的 Pod。
Taints 有三種效果:
NoSchedule
(不會排程新 Pod)PreferNoSchedule
(儘量避免排程新 Pod)NoExecute
(新 Pod 不會排程且已存在 Pod 可能會被遷移)
Taints 常見的應用場景:
- 對於叢集中不想共享的 Node,可以加上 Taints 標籤表示獨享
- 用於多租戶 Kubernetes 計算資源隔離
- Kubernetes 本身使用 Taints 機制驅除不可用的 Node
使用示例:
給節點新增 Taint,防止所有 Pod 自動排程到該節點,除非它們具有匹配的 Tolerations:
$ kubectl taint nodes docker-desktop for-special-user=cadmin:NoSchedule
先定義一個沒有任何 Tolerations 的 Pod 來驗證:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
將它應用到叢集,檢視 Pod 狀態會一直處於 Pending:
NAME READY STATUS RESTARTS AGE
nginx-deployment-77b4fdf86c-wm5f9 0/1 Pending 0 23s
從事件日誌可以看到是 Taints 在發揮作用:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 56s default-scheduler 0/1 nodes are available: 1 node(s) had untolerated taint {for-special-user: cadmin}. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling..
然後再 Pod 定義中新增 Tolerations,允許它被排程到帶有特定 Taint 的節點上:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
tolerations:
- key: "for-special-user"
operator: "Equal"
value: "docker-desktop"
effect: "NoSchedule"
這個部署檔案設定了一個 容忍度 (Tolerations) 規則:允許 Pod 被排程到標記為 for-special-user=docker-desktop
並且具有 NoSchedule
效果的節點上。
將它應用到叢集,檢視 Pod 狀態:
NAME READY STATUS RESTARTS AGE
nginx-deployment-dd7d69c9c-77qlf 1/1 Running 0 31s
Pod 已經正常排程,這也是 Taints 發揮作用。
如果節點不在需要 Tanints 作為排除,可以移除 :
$ kubectl taint nodes docker-desktop for-special-user=cadmin:NoSchedule-
輸出:
node/docker-desktop untainted
PriorityClass
PriorityClass
用於定義 Pod 的排程優先順序。常見的場景包括:
- 確保關鍵服務優先排程:對於關鍵元件,如資料庫、核心應用服務,可以設定更高的優先順序。
- 管理資源爭用:在資源有限的環境中,透過設定不同的優先順序,管理不同 Pod 的排程順序。
使用 PriorityClass
的步驟:
-
建立 PriorityClass:
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: high-priority value: 1000000 globalDefault: false description: "This priority class should be used for XYZ service pods only."
說明:
- value:這是一個整數,表示該 PriorityClass 的優先順序。較高的數值表示更高的優先順序。
- globalDefault:表示為叢集中所有沒有指定優先順序的 Pod 的預設優先順序。
- 在 Pod 中指定 PriorityClass:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
priorityClassName: high-priority
containers:
- name: mycontainer
image: myimage
透過 priorityClassName
應用剛才建立的 PriorityClass,從而確保該 Pod 具有更高的排程優先順序。
自定義 scheduler
預設的排程器是面向通用的使用場景設計的,如果預設的 Kubernetes 排程器無法滿足需求,也可以透過自定義的排程器來滿足更加個性化的需求,示例:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
schedulerName: my-custom-scheduler
containers:
- name: mycontainer
image: myimage
社群也有很多成熟開源的自定義排程器,例如:
- 騰訊 TKE 的排程器
- 華為 volcano 排程器
另外也可以參考 kube-scheduler
原始碼實現一個自己的排程器。