Automated Placement 是 Kubernetes 中 scheduler 的核心功能,負(fù)責(zé)將新的 Pod 分配給合適的節(jié)點(diǎn),滿足容器的資源需求,同時(shí)遵守設(shè)定好的調(diào)度策略。
基于微服務(wù)的系統(tǒng)通常會(huì)包含數(shù)十個(gè)甚至數(shù)百個(gè)隔離的進(jìn)程,容器和 Pod 為它們提供了很好的打包和部署機(jī)制,但并沒有解決將眾多的進(jìn)程分配給適當(dāng)?shù)墓?jié)點(diǎn)這項(xiàng)工作。
容器之間存在依賴關(guān)系,有些還需要關(guān)聯(lián)到特定的節(jié)點(diǎn),容器自身也有一定的資源需求。這些都會(huì)隨著時(shí)間發(fā)生變化。同時(shí)集群本身的資源也不是恒定的,它會(huì)執(zhí)行縮容或者擴(kuò)容,其特定時(shí)刻下的容量也取決于已經(jīng)放置的容器數(shù)量。
這些因素都會(huì)左右容器的調(diào)度。
Available Node Resources
首先需要考慮的就是節(jié)點(diǎn)上是否有足夠的可用資源。Scheduler 會(huì)確保 Pod 申請(qǐng)的資源總和小于可分配節(jié)點(diǎn)上的可用容量。節(jié)點(diǎn)可用容量的計(jì)算公式:
Allocatable [capacity for application pods] =
Node Capacity [available capacity on a node]
- Kube-Reserved [Kubernetes daemons like kubelet, container runtime]
- System-Reserved [OS system daemons like sshd, udev]
Container Resource Demands
Pod 在調(diào)度時(shí),另一個(gè)重要的考慮因素就是容器有著自己的運(yùn)行時(shí)依賴和資源需求。
比如:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
Placement Policies
Scheduler 配置了一組默認(rèn)的優(yōu)先級(jí)策略,適用于絕大多數(shù)場(chǎng)景。這個(gè)策略可以在 scheduler 啟動(dòng)時(shí)被替換掉。
scheduler 策略示例:
{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
{"name" : "PodFitsHostPorts"},
{"name" : "PodFitsResources"},
{"name" : "NoDiskConflict"},
{"name" : "NoVolumeZoneConflict"},
{"name" : "MatchNodeSelector"},
{"name" : "HostName"}
],
"priorities" : [
{"name" : "LeastRequestedPriority", "weight" : 2},
{"name" : "BalancedResourceAllocation", "weight" : 1},
{"name" : "ServiceSpreadingPriority", "weight" : 2},
{"name" : "EqualPriority", "weight" : 1}
]
}
其中 Predicate 規(guī)則用于過(guò)濾掉不合格的節(jié)點(diǎn)。比如 PodFitsHostsPorts
關(guān)注特定的固定主機(jī)端口,只有在這些端口可用時(shí)對(duì)應(yīng)的節(jié)點(diǎn)才會(huì)作為候選。
Priorities 用于根據(jù)一些偏好設(shè)置來(lái)對(duì)候選的節(jié)點(diǎn)進(jìn)行排序。比如 LeastRequestedPriority
會(huì)賦予請(qǐng)求了較少資源的節(jié)點(diǎn)更高的優(yōu)先級(jí)。
可以同時(shí)運(yùn)行多個(gè) scheduler,讓 Pod 自己去指定使用哪一個(gè)。只需要在 Pod 的配置中添加一條 .spec.schedulerName
,其值為自定義 scheduler 的名字。
調(diào)度流程
只要 Pod 創(chuàng)建完成且還沒有被分配給任何節(jié)點(diǎn),scheduler 就會(huì)挑選出該 Pod,連同所有可用的節(jié)點(diǎn)及優(yōu)先級(jí)策略。第一階段借助過(guò)濾策略移除所有不滿足要求的節(jié)點(diǎn),剩余的節(jié)點(diǎn)在第二階段有權(quán)重地排序。最后一個(gè)階段得到最終的勝出節(jié)點(diǎn)。
在絕大多數(shù)情況下,最好都只讓 scheduler 去做 Pod-to-Node 的分配工作,不要去嘗試“微操”調(diào)度邏輯。
在某些特殊場(chǎng)景下,如果需要強(qiáng)制某個(gè) Pod 只能分配給特定的一個(gè)或一組節(jié)點(diǎn),可以借助 Pod 的 .spec.nodeSelector
字段。
該字段可以指定一些鍵值對(duì),對(duì)應(yīng)節(jié)點(diǎn)身上的標(biāo)簽。比如想要 Pod 運(yùn)行在擁有 SSD 磁盤的硬件上:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
nodeSelector:
disktype: ssd
除了通過(guò)自定義標(biāo)簽指定節(jié)點(diǎn),還可以通過(guò)每個(gè)節(jié)點(diǎn)上都有的默認(rèn)標(biāo)簽來(lái)篩選,比如 kubernetes.io/hostname
。
Node Affinity
Kubernetes 還支持更為靈活的配置調(diào)度流程的方式,比如 node affinity。其實(shí)它相當(dāng)于 nodeSelector 機(jī)制的泛化,其規(guī)則可以被指定為“必需”或者“優(yōu)先”。
“必需”表示相應(yīng)的規(guī)則必須被滿足,否則節(jié)點(diǎn)無(wú)法作為候選;“優(yōu)先”則并不強(qiáng)制,只是提高匹配節(jié)點(diǎn)的權(quán)重。
此外,node affinity 支持多種操作符,如 In
、NotIn
、Exists
、DoesNotExist
、Gt
、Lt
等,從而獲得更強(qiáng)的表達(dá)能力。
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: numberCores
operator: Gt
values: [ "3" ]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchFields:
- key: metadata.name
operator: NotIn
values: [ "master" ]
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
其中 requiredDuringSchedulingIgnoredDuringExecution
用來(lái)指定節(jié)點(diǎn)必須具備的條件,此規(guī)則不會(huì)在執(zhí)行過(guò)程中重新計(jì)算。結(jié)合后面的 nodeSelectorTerms
配置,篩選出核心數(shù)大于 3 的節(jié)點(diǎn)。
preferredDuringSchedulingIgnoredDuringExecution
用于指定非必須的條件,表現(xiàn)為一個(gè)帶有權(quán)重的 selector 列表。對(duì)于每一個(gè)節(jié)點(diǎn),計(jì)算出所有匹配項(xiàng)的權(quán)重總和,結(jié)果最高的節(jié)點(diǎn)被選中,只要該節(jié)點(diǎn)已經(jīng)滿足了前面的“必需”條件。
PS:matchFields
只支持 In
和 NotIn
操作符,values
指定的列表中也只允許有一個(gè)值。
誠(chéng)然,node affinity 相比于 nodeSelector
功能更為強(qiáng)大。它允許通過(guò)標(biāo)簽或者字段為 Pod 選擇合適的節(jié)點(diǎn),但不能夠用來(lái)表達(dá) Pod 之間的依賴關(guān)系,比如無(wú)法根據(jù)某個(gè)節(jié)點(diǎn)上已經(jīng)部署的 Pod 判斷某個(gè)新 Pod 是否也應(yīng)該部署到該節(jié)點(diǎn)。這類需求可以通過(guò) Pod affinity 實(shí)現(xiàn)。
Pod Affinity
Node affinity 工作在節(jié)點(diǎn)層級(jí)上,Pod affinity 則可以在多個(gè)拓?fù)鋵蛹?jí)上表達(dá)規(guī)則,達(dá)到粒度更細(xì)的控制。
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
confidential: high
topologyKey: security-zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
confidential: none
topologyKey: kubernetes.io/hostname
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
其中 podAffinity
部分的配置表示,符合條件的節(jié)點(diǎn)上必須有帶有 confidential=high
標(biāo)簽的 Pod 在運(yùn)行,且該節(jié)點(diǎn)有 security-zone
標(biāo)簽。
podAntiAffinity
定義的規(guī)則用于過(guò)濾掉匹配的節(jié)點(diǎn)。結(jié)合其配置,即節(jié)點(diǎn)上有帶 confidential=none
標(biāo)簽的 Pod 在運(yùn)行時(shí),該節(jié)點(diǎn)不會(huì)用來(lái)部署當(dāng)前 Pod。
Taints and Tolerations
Taints 和 Tolerations 是一類更高級(jí)的用于控制調(diào)度策略的特性。簡(jiǎn)單來(lái)說(shuō),node affinity 允許 Pod 根據(jù)規(guī)則選擇合適的節(jié)點(diǎn),taints 和 tolerations 則正相反,它允許節(jié)點(diǎn)自身去控制 Pod 是否應(yīng)該分配給自己。
Taint 是節(jié)點(diǎn)自身的一種屬性,當(dāng)它存在時(shí),會(huì)阻止 Pod 分配給自己,除非該 Pod 擁有針對(duì) taint 的 tolerations。
Taint 可以使用 kubectl
命令添加。如 kubectl taint nodes master noderole.kubernetes.io/master="true":NoSchedule
,等效于下面的配置。
Tainted 節(jié)點(diǎn):
apiVersion: v1
kind: Node
metadata:
name: master
spec:
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
擁有此 taint 的節(jié)點(diǎn)不會(huì)被分配任何 Pod,除非有 Pod 指定了對(duì)應(yīng)的 toleration。比如:
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
在生產(chǎn)級(jí)別的集群中,帶有 noderole.kubernetes.io/master
配置的 taint 一般會(huì)指定給 master 節(jié)點(diǎn),阻止 Pod 部署到 master 上。
這里給 Pod 添加的 toleration 會(huì)覆蓋 taint 的 NoSchedule
效果,即無(wú)論如何都允許此 Pod 分配給 master 節(jié)點(diǎn)。
Taint 可以是硬性的,阻止節(jié)點(diǎn)作為候選(effect=NoSchedule
),也可以是軟性的,嘗試避免節(jié)點(diǎn)作為候選(effect=PreferNoSchedule
),還可以強(qiáng)制移除節(jié)點(diǎn)上已經(jīng)在運(yùn)行的 Pod(effect=NoExecute
)。
當(dāng) Pod 已經(jīng)分配給某個(gè)節(jié)點(diǎn),scheduler 的工作就已經(jīng)算完成了,它不會(huì)再對(duì)完成的分配進(jìn)行調(diào)整。除非該 Pod 被刪除或者重建。隨著時(shí)間的推移,這一定會(huì)導(dǎo)致資源的碎片化,集群利用率降低。
另一個(gè)潛在的問題是,Pod 被創(chuàng)建后具體分配給哪一個(gè)節(jié)點(diǎn),依賴于當(dāng)時(shí)集群的狀態(tài)。而集群本身是動(dòng)態(tài)的,節(jié)點(diǎn)的資源配置會(huì)更改,或者有新的節(jié)點(diǎn)加入進(jìn)來(lái),scheduler 并不會(huì)糾正已經(jīng)存在的部署。此外,節(jié)點(diǎn)上的標(biāo)簽也有可能會(huì)變動(dòng),影響到之后的調(diào)度,但之前已經(jīng)完成的調(diào)度依舊保持不變。
以上所有的場(chǎng)景都可以通過(guò) descheduler 去解決。Kubernetes 的 descheduler 是一個(gè)可選的特性,通常作為 Job 執(zhí)行,當(dāng)管理員覺得是時(shí)候通過(guò)重新調(diào)度 Pod 來(lái)整理集群的碎片。
Descheduler 有一些預(yù)先定義的策略,可以被啟用或者禁用:
- RemoveDuplicates:該策略會(huì)確保 ReplicaSet 或 Deployment 關(guān)聯(lián)的單一 Pod 只運(yùn)行在唯一一個(gè)節(jié)點(diǎn)上。當(dāng)某個(gè)節(jié)點(diǎn)不健康時(shí),controller 會(huì)在其他健康的節(jié)點(diǎn)上啟動(dòng)新的 Pod。此時(shí)若之前不健康的節(jié)點(diǎn)恢復(fù)正常重新加入集群,正在運(yùn)行的 Pod 就會(huì)大于需要的數(shù)量。此策略就可以應(yīng)用于這類的場(chǎng)景。同時(shí) RemoveDuplicates 還可以在策略或集群架構(gòu)發(fā)生變化后,將 Pod 更均勻地分散在更多的節(jié)點(diǎn)上
- LowNodeUtilization:該策略會(huì)找到使用率低的節(jié)點(diǎn),并將高使用率節(jié)點(diǎn)上的 Pod 移除掉,希望這些移除的 Pod 可以重新分配到未充分利用的節(jié)點(diǎn)上。使用率低指 CPU、內(nèi)存或 Pod 數(shù)量小于
thresholds
配置;使用率高指的是 CPU、內(nèi)存或 Pod 數(shù)量大于targetThresholds
配置 - RemovePodsViolatingInterPodAntiAffinity:該策略會(huì)移除違反了 pod antiaffinity 規(guī)則的 Pod。這種情況可能發(fā)生在,添加規(guī)則時(shí)一些不符合規(guī)則的 Pod 就已經(jīng)存在了
- RemovePodsViolatingNodeAffinity:移除違反了 node affinity 規(guī)則的 Pod
不管使用何種配置的策略,descheduler 會(huì)避免移除如下類型的 Pod:
- 在 annotation 中標(biāo)記為
scheduler.alpha.kubernetes.io/criticalpod
的關(guān)鍵 Pod - 不由 ReplicaSet、Deployment 或 Job 管理的 Pod
- 由 DaemonSet 管理的 Pod
- 擁有本地存儲(chǔ)的 Pod
- 配置了
PodDisruptionBudget
的 Pod,且移除時(shí)會(huì)違反此規(guī)則 - Descheduler Pod 本身
總結(jié)
容器調(diào)度是一個(gè)我們希望盡可能少干預(yù)的領(lǐng)域。從簡(jiǎn)單到復(fù)雜,以下方法控制著調(diào)度的具體策略:
- nodeName:最簡(jiǎn)單的分配方式,將 Pod 到節(jié)點(diǎn)的關(guān)系硬編碼到配置中。理想的情況下,此字段應(yīng)該由 scheduler 填充,策略去驅(qū)動(dòng),而不是手動(dòng)指定
- nodeSelector:鍵值對(duì)映射。符合條件的節(jié)點(diǎn)必須包含此鍵值對(duì)指向的標(biāo)簽。在控制調(diào)度策略的可接受的方式中,最簡(jiǎn)單的一種
- Default scheduling alteration:必要的情況下,可以修改 default scheduler 的過(guò)濾規(guī)則和優(yōu)先級(jí)策略、順序、權(quán)重等
- Pod affinity 和 antiaffinity:此機(jī)制允許 Pod 表達(dá)自身對(duì)其他 Pod 的依賴關(guān)系
- Node affinity:允許 Pod 表達(dá)自身對(duì)節(jié)點(diǎn)的依賴關(guān)系,比如節(jié)點(diǎn)的硬件配置、地理位置等
- Taints 和 tolerations:允許節(jié)點(diǎn)去控制哪些 Pod 允許哪些不允許分配給自己。比如為一組 Pod 分配一個(gè)專用節(jié)點(diǎn),甚至在運(yùn)行時(shí)移除 Pod
- Custom scheduler:若上述方案都不能符合需求,還可以編寫自定義的 scheduler。自定義 scheduler 可以替換掉標(biāo)準(zhǔn)的 Kubernetes scheduler,也可以兩者一起運(yùn)行