面試官:Go 調(diào)度了解嗎?Kubernetes 調(diào)度和資源管理呢?
作者?| 子譽(yù)? 螞蟻金服高級(jí)技術(shù)專(zhuān)家
關(guān)注“Go語(yǔ)言中文網(wǎng)”公眾號(hào),回復(fù)關(guān)鍵詞“入門(mén)”,即可下載從零入門(mén) K8s 系列文章 PPT。

Kubernetes 調(diào)度過(guò)程

我們來(lái)看一下,假如要向這個(gè) Kubernetes 集群提交一個(gè) pod,它的調(diào)度過(guò)程是什么樣的一個(gè)流程?
假設(shè)我們已經(jīng)寫(xiě)好了一個(gè) yaml 文件,就是下圖中的橙色圓圈 pod1,然后往 kube-ApiServer 里提交這個(gè) yaml 文件。

此時(shí) ApiServer 會(huì)先把這個(gè)待創(chuàng)建的請(qǐng)求路由給我們的 webhook Controllers 進(jìn)行校驗(yàn)。

通過(guò)校驗(yàn)之后,ApiServer 會(huì)在集群里面生成一個(gè) pod,此時(shí)生成的 pod,它的 nodeName 是空的,并且它的 phase 是 Pending 狀態(tài)。在生成了這個(gè) pod 之后,kube-Scheduler 以及 kubelet 都能 watch 到這個(gè) pod 的生成事件,kube-Scheduler 發(fā)現(xiàn)這個(gè) pod 的 nodeName 是空的之后,會(huì)認(rèn)為這個(gè) pod 是處于未調(diào)度狀態(tài)。

接下來(lái),它會(huì)把這個(gè) pod 拿到自己里面進(jìn)行調(diào)度,通過(guò)一系列的調(diào)度算法,包括一系列的過(guò)濾和打分的算法后,Schedule 會(huì)選出一臺(tái)最合適的節(jié)點(diǎn),并且把這一臺(tái)節(jié)點(diǎn)的名稱(chēng)綁定在這個(gè) pod 的 spec 上,完成一次調(diào)度的過(guò)程。
此時(shí)我們發(fā)現(xiàn),pod 的 spec 上,nodeName 已經(jīng)更新成了 Node1 這個(gè) node,更新完 nodeName 之后,在 Node1 上的這臺(tái) kubelet 會(huì) watch 到這個(gè) pod 是屬于自己節(jié)點(diǎn)上的一個(gè) pod。

然后它會(huì)把這個(gè) pod 拿到節(jié)點(diǎn)上進(jìn)行操作,包括創(chuàng)建一些容器 storage 以及 network,最后等所有的資源都準(zhǔn)備完成,kubelet 會(huì)把狀態(tài)更新為 Running,這樣一個(gè)完整的調(diào)度過(guò)程就結(jié)束了。
通過(guò)剛剛一個(gè)調(diào)度過(guò)程的演示,我們用一句話來(lái)概括一下調(diào)度過(guò)程:它其實(shí)就是在做一件事情,即把 pod 放到合適的 node 上。
這里有個(gè)關(guān)鍵字“合適”,什么是合適呢?下面給出幾點(diǎn)合適定義的特點(diǎn):
首先要滿足 pod 的資源要求;
其次要滿足 pod 的一些特殊關(guān)系的要求;
再次要滿足 node 的一些限制條件的要求;
最后還要做到整個(gè)集群資源的合理利用。
做到以上的要求后,可以認(rèn)為我們把 pod 放到了一個(gè)合適的節(jié)點(diǎn)上了。
接下來(lái)我會(huì)為大家介紹 Kubernetes 是怎么做到滿足這些 pod 和 node 的要求的。

Kubernetes 基礎(chǔ)調(diào)度力
下面為大家介紹一下 Kubernetes 的基礎(chǔ)調(diào)度能力,Kubernetes 的基礎(chǔ)調(diào)度能力會(huì)以兩部分來(lái)展開(kāi)介紹:
第一部分是資源調(diào)度——介紹一下 Kubernetes 基本的一些 Resources 的配置方式,還有 Qos 的概念,以及 Resource Quota 的概念和使用方式;
第二部分是關(guān)系調(diào)度——在關(guān)系調(diào)度上,介紹兩種關(guān)系場(chǎng)景:第一種是?pod 和 pod 之間的關(guān)系場(chǎng)景,包括怎么去親和一個(gè) pod,怎么去互斥一個(gè) pod;第二種是?pod 和 node 之間的關(guān)系場(chǎng)景,包括怎么去親和一個(gè) node,以及有一些 node 怎么去限制 pod 調(diào)度上來(lái)。
1. 如何滿足 Pod 資源要求
pod 的資源配置方法
?

?
上圖是 pod spec 的一個(gè) demo,我們的資源其實(shí)是填在 pod spec 中,具體在 containers 的 resources 里。
resources 包含兩個(gè)部分:
第一部分是 requests;
第二部分是 limits。
這兩部分里面的內(nèi)容是一模一樣的,但是它代表的含義有所不同:request 代表的是對(duì)這個(gè) pod 基本保底的一些資源要求;limit 代表的是對(duì)這個(gè) pod 可用能力上限的一種限制。request、limit 的實(shí)現(xiàn)是一個(gè) map 結(jié)構(gòu),它里面可以填不同的資源的 key/value。
我們可以大概分成四大類(lèi)的基礎(chǔ)資源:
第一類(lèi)是 CPU 資源;
第二類(lèi)是 memory;
第三類(lèi)是 ephemeral-storage,是一種臨時(shí)存儲(chǔ);
第四類(lèi)是通用的擴(kuò)展資源,比如說(shuō)像 GPU。
CPU 資源,比如說(shuō)上面的例子填的是2,申請(qǐng)的是兩個(gè) CPU,也可以寫(xiě)成 2000m 這種十進(jìn)制的轉(zhuǎn)換方式,來(lái)表達(dá)有些時(shí)候可能對(duì) CPU 可能是一個(gè)小數(shù)的需求,比如說(shuō)像 0.2 個(gè)CPU,可以填 200m。而這種方式在 memory 和 storage 之上,它是一個(gè)二進(jìn)制的表達(dá)方式,如上圖右側(cè)所示,申請(qǐng)的是 1GB 的 memory,同樣也可以填成一個(gè) 1024mi 的表達(dá)方式,這樣可以更清楚地表達(dá)我們對(duì) memory 的需求。
在擴(kuò)展資源上,Kubernetes 有一個(gè)要求,即擴(kuò)展資源必須是整數(shù)的,所以我們沒(méi)法申請(qǐng)到 0.5 的 GPU 這樣的資源,只能申請(qǐng) 1 個(gè) GPU 或者 2 個(gè) GPU。
這里為大家介紹完了基礎(chǔ)資源的申請(qǐng)方式。
接下來(lái),我會(huì)詳細(xì)給大家介紹一下 request 和 limit 到底有什么區(qū)別,以及如何通過(guò) request/limit 來(lái)表示 QoS。
Pod QoS 類(lèi)型
K8S 在 pod resources 里面提供了兩種填寫(xiě)方式:第一種是 request,第二種是 limit。
它其實(shí)是為用戶提供了對(duì) Pod 一種彈性能力的定義。比如說(shuō)我們可以對(duì) request 填 2 個(gè) CPU,對(duì) limit 填 4 個(gè) CPU,這樣代表了我希望是有 2 個(gè) CPU 的保底能力,但其實(shí)在閑置的時(shí)候,可以使用 4 個(gè) GPU。
說(shuō)到這個(gè)彈性能力,我們不得不提到一個(gè)概念:QoS 的概念。什么是 QoS呢?QoS 全稱(chēng)是 Quality of Service,它是 Kubernetes 用來(lái)表達(dá)一個(gè) pod 在資源能力上的服務(wù)質(zhì)量的標(biāo)準(zhǔn),Kubernetes 提供了三類(lèi) QoS Class:
?
第一類(lèi)是 Guaranteed,它是一類(lèi)高 QoS Class,一般拿 Guaranteed 配置給一些需要資源保障能力的 pods;
第二類(lèi)是 Burstable,它是中等的一個(gè) QoS label,一般會(huì)為一些希望有彈性能力的 pod 來(lái)配置 Burstable;
第三類(lèi)是 BestEffort,它是低QoS Class,通過(guò)名字我們也知道,它是一種盡力而為式的服務(wù)質(zhì)量,K8S不承諾保障這類(lèi)Pods服務(wù)質(zhì)量。
K8s 其實(shí)有一個(gè)不太好的地方,就是用戶沒(méi)法直接指定自己的 pod 是屬于哪一類(lèi) QoS,而是通過(guò) request 和 limit 的組合來(lái)自動(dòng)地映射上 QoS Class。
通過(guò)上圖的例子,大家可以看到:假如我提交的是上面的一個(gè) spec,在 spec 提交成功之后,Kubernetes 會(huì)自動(dòng)給補(bǔ)上一個(gè) status,里面是 qosClass: Guaranteed,用戶自己提交的時(shí)候,是沒(méi)法定義自己的 QoS 等級(jí)。所以將這種方式稱(chēng)之為隱性的 QoS class 用法。
Pod QoS 配置
接下來(lái)介紹一下,我們?cè)趺赐ㄟ^(guò) request 和 limit 的組合來(lái)確定我們想要的 QoS level。
Guaranteed Pod

首先我們?nèi)绾蝿?chuàng)建出來(lái)一個(gè) Guaranteed Pod?
Kubernetes 里面有一個(gè)要求:如果你要?jiǎng)?chuàng)建出一個(gè) Guaranteed Pod,那么你的基礎(chǔ)資源(包括 CPU 和 memory),必須它的 request==limit,其他的資源可以不相等。只有在這種條件下,它創(chuàng)建出來(lái)的 pod 才是一種 Guaranteed Pod,否則它會(huì)屬于 Burstable,或者是 BestEffort Pod。
Burstable Pod
?
然后看一下,我們?cè)趺磩?chuàng)建出來(lái)一個(gè) Burstable Pod,Burstable Pod 的范圍比較寬泛,它只要滿足 CPU/Memory ?的 request 和 limit 不相等,它就是一種 Burstable Pod。

比如說(shuō)上面的例子,可以不用填寫(xiě) memory 的資源,只要填寫(xiě) CPU 的資源,它就是一種 Burstable Pod。
BestEffort Pod
?

?
第三類(lèi) BestEffort Pod,它也是條件比較死的一種使用方式。它必須是所有資源的 request/limit 都不填,才是一種 BestEffort Pod。
所以這里可以看到,通過(guò) request 和 limit 不同的用法,可以組合出不同的 Pod QoS。
不同的 QoS 表現(xiàn)
接下來(lái),為大家介紹一下:不同的 QoS 在調(diào)度和底層表現(xiàn)有什么樣的不同?
不同的 QoS,它其實(shí)在調(diào)度和底層表現(xiàn)上都有一些不一樣。比如說(shuō)調(diào)度表現(xiàn),調(diào)度器只會(huì)使用 request 進(jìn)行調(diào)度,也就是說(shuō)不管你配了多大的 limit,它都不會(huì)進(jìn)行調(diào)度使用。
在底層上,不同的 Qos 表現(xiàn)更不相同。比如說(shuō) CPU,它是按 request 來(lái)劃分權(quán)重的,不同的 QoS,它的 request 是完全不一樣的,比如說(shuō)像 Burstable 和 BestEffort,它可能 request 可以填很小的數(shù)字或者不填,這樣的話,它的時(shí)間片權(quán)重其實(shí)是非常低的。像 BestEffort,它的權(quán)重可能只有 2,而 Burstable 或 Guaranteed,它的權(quán)重可以多到幾千。
另外,當(dāng)我們開(kāi)啟了 kubelet 的一個(gè)特性,叫 cpu-manager-policy=static 的時(shí)候,我們 Guaranteed Qos,如果它的 request 是一個(gè)整數(shù)的話,比如說(shuō)配了 2,它會(huì)對(duì) Guaranteed Pod 進(jìn)行綁核。
具體的如下圖所示,它分配 CPU0 和 CPU1 給 Guaranteed Pod。

非整數(shù)的 Guaranteed/Burstable/BestEffort,它們的 CPU 會(huì)放在一塊,組成一個(gè) CPU share pool,比如說(shuō)像上面這個(gè)例子,這臺(tái)節(jié)點(diǎn)假如說(shuō)有 8 個(gè)核,已經(jīng)分配了 2 個(gè)核給整數(shù)的 Guaranteed 綁核,那么剩下的 6 個(gè)核 CPU2~CPU7,它會(huì)被非整數(shù)的 Guaranteed/Burstable/BestEffort 共享,然后它們會(huì)根據(jù)不同的權(quán)重劃分時(shí)間片來(lái)使用 6 個(gè)核的 CPU。
另外在 memory 上也會(huì)按照不同的 QoS 進(jìn)行劃分 OOMScore。
比如說(shuō) Guaranteed Pod,會(huì)固定配置默認(rèn)的 -998 的 OOMScore;
而 Burstable Pod 會(huì)根據(jù) Pod 內(nèi)存設(shè)計(jì)的大小和節(jié)點(diǎn)內(nèi)存的比例來(lái)分配 2-999 的 OOMScore;
BestEffort Pod 會(huì)固定分配 1000 的 OOMScore,OOMScore 得分越高的話,在物理機(jī)出現(xiàn) OOM 的時(shí)候會(huì)優(yōu)先被 kill 掉。
另外在節(jié)點(diǎn)上的 eviction 動(dòng)作上,不同的 QoS?行為也是不一樣的,比如說(shuō)發(fā)生 eviction 的時(shí)候,會(huì)優(yōu)先考慮驅(qū)逐 BestEffort 的 pod。所以不同的 QoS?在底層的表現(xiàn)是截然不同的。這反過(guò)來(lái)也要求我們?cè)谏a(chǎn)過(guò)程中,根據(jù)不同業(yè)務(wù)的要求和屬性來(lái)配置資源的 Limits 和 Request,做到合理的規(guī)劃 QoS?Class。
資源 Quota
在生產(chǎn)中我們還會(huì)遇到一個(gè)場(chǎng)景:假如集群是由多個(gè)人同時(shí)提交的,或者是多個(gè)業(yè)務(wù)同時(shí)在使用,我們肯定要限制某個(gè)業(yè)務(wù)或某個(gè)人提交的總量,防止整個(gè)集群的資源都會(huì)被一個(gè)業(yè)務(wù)使用掉,導(dǎo)致另一個(gè)業(yè)務(wù)沒(méi)有資源使用。
?

Kubernetes 給我們提供了一個(gè)能力叫?ResourceQuota。它可以做到限制 namespace 資源用量。具體的做法如上圖右側(cè)的 yaml 所示,可以看到它的 spec 包括了一個(gè) hard 和 scopeSelector。
hard 內(nèi)容其實(shí)和 Resourcelist 很像,這里可以填一些基礎(chǔ)的資源。但是它比 ResourceList 更豐富一點(diǎn),它還可以填寫(xiě)一些 Pod,這樣可以限制 Pod 數(shù)量。另外,scopeSelector 還為這個(gè) ResourceQuota 提供了更豐富的索引能力。
比如上面的例子中,索引出非 BestEffort 的 pod,限制的 cpu 是 1000 個(gè),memory 是 200G,Pod 是 10 個(gè)。ScopeName 除了提供 NotBestEffort,它還提供了更豐富的索引范圍,包括 Terminating/Not Terminating, BestEffort/NotBestEffort, PriorityClass。
當(dāng)我們創(chuàng)建了這樣的 ResourceQuota 作用于集群,如果用戶真的用超了資源,表現(xiàn)的行為是:它在提交 Pod spec 時(shí),會(huì)收到一個(gè) forbidden 的 403 錯(cuò)誤,提示 exceeded quota。這樣用戶就無(wú)法再提交對(duì)應(yīng)用超的資源了。而如果再提交一個(gè)沒(méi)有包含在這個(gè) ResourceQuota 里的資源,還是能成功的。
這就是 Kubernetes 里 ResourceQuota 的基本用法。我們可以用 ResourceQuota 方法來(lái)做到限制每一個(gè) namespace 的資源用量,從而保證其他用戶的資源使用。
小結(jié):如何滿足 Pod 資源要求?
上面介紹完了基礎(chǔ)資源的使用方式,也就是我們做到了如何滿足 Pod 資源要求。下面做一個(gè)小結(jié):
Pod 要配置合理的資源要求:CPU/Memory/EphemeralStorage/GPU;
?
通過(guò) Request 和 Limit 來(lái)為不同業(yè)務(wù)特點(diǎn)的 Pod 選擇不同的 QoS。Guaranteed -?敏感型,需要業(yè)務(wù)保障;Burstable -?次敏感型,需要彈性業(yè)務(wù)BestEffort -?可容忍性業(yè)務(wù);
?
為每個(gè) NS 配置 ResourceQuota 來(lái)防止過(guò)量使用,保障其他人的資源可用。
2. 如何滿足 Pod 與 Pod 關(guān)系要求?
接下來(lái)給大家介紹一下 Pod 的關(guān)系調(diào)度,首先是 Pod 和 Pod 的關(guān)系調(diào)度。我們?cè)谄綍r(shí)使用中可能會(huì)遇到一些場(chǎng)景:比如說(shuō)一個(gè) Pod 必須要和另外一個(gè) Pod 放在一起,或者不能和另外一個(gè) Pod 放在一起。
在這種要求下, Kubernetes 提供了兩類(lèi)能力:
第一類(lèi)能力稱(chēng)之為 Pod 親和調(diào)度:PodAffinity;
第二類(lèi)就是 Pod 反親和調(diào)度:PodAntAffinity。
Pod 親和調(diào)度

首先我們來(lái)看 Pod 親和調(diào)度,假如我想把一個(gè) Pod 和另一個(gè) Pod 放在一起,這時(shí)可以看上圖中的實(shí)例寫(xiě)法,填寫(xiě)上 podAffinity,然后填上 required 要求。
在這個(gè)例子中,必須要調(diào)度到帶了 key: k1 的 Pod 所在的節(jié)點(diǎn),并且打散粒度是按照節(jié)點(diǎn)粒度去打散索引的。這種情況下,假如能找到帶 key: k1 的 Pod 所在節(jié)點(diǎn),就會(huì)調(diào)度成功。假如這個(gè)集群不存在這樣的 Pod 節(jié)點(diǎn),或者是資源不夠的時(shí)候,那就會(huì)調(diào)度失敗。這是一個(gè)嚴(yán)格的親和調(diào)度,我們叫做強(qiáng)制親和調(diào)度。

有些時(shí)候我們并不需要這么嚴(yán)格的調(diào)度策略。這時(shí)候可以把 required 改成 preferred,變成一個(gè)優(yōu)先親和調(diào)度。也就是優(yōu)先可以調(diào)度帶 key: k2 的 Pod 所在節(jié)點(diǎn)。并且這個(gè) preferred 里面可以是一個(gè) list 選擇,可以填上多個(gè)條件,比如權(quán)重等于 100 的是 key: k2,權(quán)重等于 10 的是 key: k1。那調(diào)度器在調(diào)度的時(shí)候會(huì)優(yōu)先把這個(gè) Pod 分配到權(quán)重分更高的調(diào)度條件節(jié)點(diǎn)上去。
Pod 反親和調(diào)度
上面介紹了親和調(diào)度,反親和調(diào)度與親和調(diào)度比較相似。功能上是取反的,但語(yǔ)法基本上是一樣的,僅是 podAffinity 換成了 podAntiAffinity,也是包括?required 強(qiáng)制反親和,以及一個(gè) preferred 優(yōu)先反親和。
這里舉了兩個(gè)例子:一個(gè)是禁止調(diào)度到帶了 key: k1 標(biāo)簽的 Pod 所在節(jié)點(diǎn);另一個(gè)是優(yōu)先反親和調(diào)度到帶了 key: k2 標(biāo)簽的 Pod 所在節(jié)點(diǎn)。

Kubernetes 除了 In 這個(gè) Operator 語(yǔ)法之外,還提供了更多豐富的語(yǔ)法組合來(lái)給大家使用。比如說(shuō) In/NotIn/Exists/DoesNotExist 這些組合方式。上圖的例子用的是 In,比如說(shuō)第一個(gè)強(qiáng)制反親和例子里面,相當(dāng)于我們必須要禁止調(diào)度到帶了 key: k1 標(biāo)簽的 Pod 所在節(jié)點(diǎn)。
同樣的功能也可以使用 Exists,Exists 范圍可能會(huì)比 In 范圍更大,當(dāng) Operator 填了 Exists,就不需要再填寫(xiě) values。它做到的效果就是禁止調(diào)度到帶了 key: k1 標(biāo)簽的 Pod 所在節(jié)點(diǎn),不管 values 是什么值,只要帶了 k1 這個(gè) key 標(biāo)簽的 Pod 所在節(jié)點(diǎn),都不能調(diào)度過(guò)去。
以上就是 Pod 與 Pod 之間的關(guān)系調(diào)度。
3. 如何滿足 Pod 與 Node 關(guān)系調(diào)度
Pod 與 Node 的關(guān)系調(diào)度又稱(chēng)之為 Node 親和調(diào)度,主要給大家介紹兩類(lèi)使用方法。
NodeSelector

第一類(lèi)是 NodeSelector,這是一類(lèi)相對(duì)比較簡(jiǎn)單的用法。
比如說(shuō)有個(gè)場(chǎng)景:必須要調(diào)度 Pod 到帶了 k1: v1 標(biāo)簽的 Node 上,這時(shí)可以在 Pod 的 spec 中填寫(xiě)一個(gè) nodeSelector 要求。nodeSelector 本質(zhì)是一個(gè) map 結(jié)構(gòu),里面可以直接寫(xiě)上對(duì) node 標(biāo)簽的要求,比如 k1: v1。這樣我的 Pod 就會(huì)強(qiáng)制調(diào)度到帶了 k1: v1 標(biāo)簽的 Node 上。
NodeAffinity
NodeSelector 雖然是一個(gè)非常簡(jiǎn)單的用法,但這個(gè)用法有個(gè)問(wèn)題:它只能強(qiáng)制親和調(diào)度,假如我想優(yōu)先調(diào)度,就沒(méi)法用 nodeSelector 來(lái)做。于是 Kubernetes 社區(qū)又新加了一個(gè)用法,叫做 NodeAffinity。

它和 PodAffinity 有點(diǎn)類(lèi)似,也提供了兩類(lèi)調(diào)度的策略:
第一類(lèi)是 required,必須調(diào)度到某一類(lèi) Node 上;
第二類(lèi)是 preferred,就是優(yōu)先調(diào)度到某一類(lèi) Node 上。
它的基本語(yǔ)法和上文中的 PodAffinity 以及 PodAntiAffinity 也是類(lèi)似的。在 Operator 上,NodeAffinity 提供了比 PodAffinity 更豐富的 Operator 內(nèi)容。增加了 Gt 和 Lt,數(shù)值比較的用法。當(dāng)使用 Gt 的時(shí)候,values 只能填寫(xiě)數(shù)字。
Node 標(biāo)記/容忍
?
還有第三類(lèi)調(diào)度,可以通過(guò)給 Node 打一些標(biāo)記,來(lái)限制 Pod 調(diào)度到某些 Node 上。Kubernetes 把這些標(biāo)記稱(chēng)之為 Taints,它的字面意思是污染。
?

那我們?nèi)绾蜗拗?Pod 調(diào)度到某些 Node 上呢?比如說(shuō)現(xiàn)在有個(gè) node 叫 demo-node,這個(gè)節(jié)點(diǎn)有問(wèn)題,我想限制一些 Pod 調(diào)度上來(lái)。這時(shí)可以給這個(gè)節(jié)點(diǎn)打一個(gè) taints,taints 內(nèi)容包括 key、value、effect:
key 就是配置的鍵值;
value 就是內(nèi)容;
effect 是標(biāo)記了這個(gè) taints 行為是什么。
目前 Kubernetes 里面有三個(gè) taints 行為:
NoSchedule ?禁止新的 Pod 調(diào)度上來(lái);
PreferNoSchedul ?盡量不調(diào)度到這臺(tái);
NoExecute ?會(huì) evict 沒(méi)有對(duì)應(yīng) toleration 的 Pods,并且也不會(huì)調(diào)度新的上來(lái)。這個(gè)策略是非常嚴(yán)格的,大家在使用的時(shí)候要小心一點(diǎn)。
如上圖綠色部分,給這個(gè) demo-node 打了 k1=v1,并且 effect 等于 NoSchedule 之后。它的效果是:新建的 Pod ?沒(méi)有專(zhuān)門(mén)容忍這個(gè) taint,那就沒(méi)法調(diào)度到這個(gè)節(jié)點(diǎn)上去了。
假如有些 Pod 是可以調(diào)度到這個(gè)節(jié)點(diǎn)上的,應(yīng)該怎么來(lái)做呢?這時(shí)可以在 Pod 上打一個(gè) Pod Tolerations。從上圖中藍(lán)色部分可以看到:在 Pod 的 spec 中填寫(xiě)一個(gè) Tolerations,它里面也包含了 key、value、effect,這三個(gè)值和 taint 的值是完全對(duì)應(yīng)的,taint 里面的 key,value,effect 是什么內(nèi)容,Tolerations 里面也要填寫(xiě)相同的內(nèi)容。
Tolerations 還多了一個(gè)選項(xiàng) Operator,Operator 有兩個(gè) value:Exists/Equal。Equal 的概念是必須要填寫(xiě) value,而 Exists 就跟上文說(shuō)的 NodeAffinity 一樣,不需要填寫(xiě) value,只要 key 值對(duì)上了,就認(rèn)為它跟 taints 是匹配的。
上圖中的例子,給 Pod 打了一個(gè) Tolerations,只有打了這個(gè) Tolerations 的 Pod,才能調(diào)度到綠色部分打了 taints 的 Node 上去。這樣的好處是 Node 可以有選擇性的調(diào)度一些 Pod 上來(lái),而不是所有的 Pod 都可以調(diào)度上來(lái),這樣就做到了限制某些 Pod 調(diào)度到某些 Node 的效果。
4. 小結(jié)
我們已經(jīng)介紹完了 Pod/Node 的特殊關(guān)系和條件調(diào)度,來(lái)做一下小結(jié)。
首先假如有需求是處理 Pod 與 Pod 的時(shí)候,比如 Pod 和另一個(gè) Pod 有親和的關(guān)系或者是互斥的關(guān)系,可以給它們配置下面的參數(shù):
PodAffinity
PodAntiAffinity
假如存在 Pod 和 Node 有親和關(guān)系,可以配置下面的參數(shù):
NodeSelector
NodeAffinity
假如有些 Node 是限制某些 Pod 調(diào)度的,比如說(shuō)一些故障的 Node,或者說(shuō)是一些特殊業(yè)務(wù)的 Node,可以配置下面的參數(shù):
Node -- Taints
Pod -- Tolerations

Kubernetes 高級(jí)調(diào)度能力
介紹完了基礎(chǔ)調(diào)度能力之后,下面來(lái)了解一下高級(jí)調(diào)度能力。
1. 優(yōu)先級(jí)調(diào)度
優(yōu)先級(jí)調(diào)度和搶占,主要概念有:
Priority
Preemption
首先來(lái)看一下調(diào)度過(guò)程提到的四個(gè)特點(diǎn),我們?nèi)绾巫龅郊旱暮侠砝茫?/span>當(dāng)集群資源足夠的話,只需要通過(guò)基礎(chǔ)調(diào)度能力就能組合出合理的使用方式。但是假如資源不夠,我們?cè)趺醋龅郊旱暮侠砝媚兀?/span>通常的策略有兩類(lèi):
先到先得策略 (FIFO) ?-簡(jiǎn)單、相對(duì)公平,上手快;
優(yōu)先級(jí)策略 (Priority) - 比較符合日常公司業(yè)務(wù)特點(diǎn)。
在實(shí)際生產(chǎn)中,如果使用先到先得策略,反而是一種不公平的策略,因?yàn)楣緲I(yè)務(wù)里面肯定是有高優(yōu)先級(jí)的業(yè)務(wù)和低優(yōu)先級(jí)的業(yè)務(wù),所以優(yōu)先級(jí)策略會(huì)比先到先得策略更能夠符合日常公司業(yè)務(wù)特點(diǎn)。

接下來(lái)介紹一下優(yōu)先級(jí)策略下的優(yōu)先級(jí)調(diào)度是什么樣的一個(gè)概念。
比如說(shuō)有一個(gè) Node 已經(jīng)被一個(gè) Pod 占用了,這個(gè) Node 只有 2 個(gè) CPU。另一個(gè)高優(yōu)先級(jí) Pod 來(lái)的時(shí)候,低優(yōu)先級(jí)的 Pod 應(yīng)該把這兩個(gè) CPU 讓給高優(yōu)先級(jí)的 Pod 去使用。低優(yōu)先級(jí)的 Pod 需要回到等待隊(duì)列,或者是業(yè)務(wù)重新提交。這樣的流程就是優(yōu)先級(jí)搶占調(diào)度的一個(gè)流程。
?
在 Kubernetes 里,PodPriority 和 Preemption,就是優(yōu)先級(jí)和搶占的特點(diǎn),在 v1.14 版本中變成了 stable。并且 PodPriority 和 Preemption 功能默認(rèn)都是開(kāi)啟的。
?
2. 優(yōu)先級(jí)調(diào)度配置
怎么使用?
如何使用優(yōu)先級(jí)調(diào)度呢?需要?jiǎng)?chuàng)建一個(gè) priorityClass,然后再為每個(gè) Pod 配置上不同的 priorityClassName,這樣就完成了優(yōu)先級(jí)以及優(yōu)先級(jí)調(diào)度的配置。

?
首先來(lái)看一下如何創(chuàng)建一個(gè) priorityClass。上圖右側(cè)定義了兩個(gè) demo:
一個(gè)是創(chuàng)建了名為 high 的 priorityClass,它是高優(yōu)先級(jí),得分為 10000;
另一個(gè)創(chuàng)建了名為 low 的 priorityClass,它的得分是 100。
同時(shí)在第三部分給 Pod1 配置上了 high,Pod2 上配置了 low priorityClassName,藍(lán)色部分顯示了 pod 的 spec 的配置位置,就是在 spec 里面填寫(xiě)一個(gè) priorityClassName: high。這樣 Pod 和 priorityClass 做完配置,就為集群開(kāi)啟了一個(gè) priorityClass 調(diào)度。
內(nèi)置優(yōu)先級(jí)配置
當(dāng)然 Kubernetes 里面還內(nèi)置了默認(rèn)的優(yōu)先級(jí),如 DefaultpriorityWhenNoDefaultClassExistis。如果集群中沒(méi)有配置 DefaultpriorityWhenNoDefaultClassExistis,那所有的 Pod 關(guān)于此項(xiàng)數(shù)值都會(huì)被設(shè)置成 0。
用戶可配置的最大優(yōu)先級(jí)限制為:HighestUserDefinablePriority = 10000000000(10 億),會(huì)小于系統(tǒng)級(jí)別優(yōu)先級(jí):SystemCriticalPriority = 20000000000(20 億)。
其中內(nèi)置了兩個(gè)系統(tǒng)級(jí)別優(yōu)先級(jí):
system-cluster-critical
system-node-critical
?
以上就是 K8s 優(yōu)先級(jí)調(diào)度里內(nèi)置的優(yōu)先級(jí)配置。
?
3. 優(yōu)先級(jí)調(diào)度過(guò)程
下面介紹簡(jiǎn)單的優(yōu)先級(jí)調(diào)度過(guò)程:
首先介紹只觸發(fā)優(yōu)先級(jí)調(diào)度但是沒(méi)有觸發(fā)搶占調(diào)度的流程。
假如有一個(gè) Pod1 和 Pod2,Pod1 配置了高優(yōu)先級(jí),Pod2 配置了低優(yōu)先級(jí)。同時(shí)提交 Pod1 和 Pod2 到調(diào)度隊(duì)列里。

調(diào)度器處理隊(duì)列的時(shí)候會(huì)挑選一個(gè)高優(yōu)先級(jí)的 Pod1 進(jìn)行調(diào)度,經(jīng)過(guò)調(diào)度過(guò)程把 Pod1 綁定到 Node1 上。

其次再挑選一個(gè)低優(yōu)先的 Pod2 進(jìn)行同樣的過(guò)程,綁定到 Node1 上。

這樣就完成了一個(gè)簡(jiǎn)單的優(yōu)先級(jí)調(diào)度的流程。
4. 優(yōu)先級(jí)搶占過(guò)程
假如高優(yōu)先級(jí)的 Pod 在調(diào)度的時(shí)候沒(méi)有資源,那么會(huì)是一個(gè)怎么樣的流程呢?
首先是跟上文同樣的場(chǎng)景,但是提前在 Node1 上放置了 Pod0,占去了一部分資源。同樣有 Pod1 和 Pod2 待調(diào)度,Pod1 的優(yōu)先級(jí)大于 Pod2。

假如先把 Pod2 調(diào)度上去,它經(jīng)過(guò)一系列的調(diào)度過(guò)程綁定到了 Node1 上。

緊接著再調(diào)度 Pod1,因?yàn)?Node1 上已經(jīng)存在了兩個(gè) Pod,資源不足,所以會(huì)遇到調(diào)度失敗。

在調(diào)度失敗時(shí) Pod1 會(huì)進(jìn)入搶占流程,這時(shí)會(huì)進(jìn)行整個(gè)集群的節(jié)點(diǎn)篩選,最后挑出要搶占的 Pod 是 Pod2,此時(shí)調(diào)度器會(huì)把 Pod2 從 Node1 上移除數(shù)據(jù)。

再把 Pod1 調(diào)度到 Node1 上。這樣就完成了一次搶占調(diào)度的流程。

5. 優(yōu)先級(jí)搶占策略
接下來(lái)介紹具體的搶占策略和搶占的流程:

上圖右側(cè)是整個(gè) kube-scheduler?優(yōu)先級(jí)搶占的調(diào)度流程。首先一個(gè) Pod 進(jìn)入搶占的時(shí)候,會(huì)判斷 Pod 是否擁有搶占的資格,有可能上次已經(jīng)搶占過(guò)一次。如果符合搶占資格,它會(huì)先對(duì)所有的節(jié)點(diǎn)進(jìn)行一次過(guò)濾,過(guò)濾出符合這次搶占要求的節(jié)點(diǎn),如果不符合就過(guò)濾掉這批節(jié)點(diǎn)。
接著從過(guò)濾剩下的節(jié)點(diǎn)中,挑選出合適的節(jié)點(diǎn)進(jìn)行搶占。這次搶占的過(guò)程會(huì)模擬一次調(diào)度,把上面優(yōu)先級(jí)低的 Pod 先移除出去,再把待搶占的 Pod 嘗試能否放置到此節(jié)點(diǎn)上;然后通過(guò)這個(gè)過(guò)程選出一批節(jié)點(diǎn),進(jìn)入下一個(gè)過(guò)程?ProcessPreemptionWithExtenders。這是一個(gè)擴(kuò)展的鉤子,用戶可以在這里加一些自己搶占節(jié)點(diǎn)的策略,如果沒(méi)有擴(kuò)展鉤子,這里面是不做任何動(dòng)作的。
接下來(lái)的流程叫做 PickOneNodeForPreemption,就是從上面 selectNodeForPreemption list 里面挑選出最合適的一個(gè)節(jié)點(diǎn),這是有一定的策略的。上圖左側(cè)簡(jiǎn)單介紹了一下策略:
優(yōu)先選擇打破 PDB 最少的節(jié)點(diǎn);
其次選擇待搶占 Pods 中最大優(yōu)先級(jí)最小的節(jié)點(diǎn);
再次選擇待搶占 Pods 優(yōu)先級(jí)加和最小的節(jié)點(diǎn);
接下來(lái)選擇待搶占 Pods 數(shù)目最小的節(jié)點(diǎn);
最后選擇擁有最晚啟動(dòng) Pod 的節(jié)點(diǎn)。
通過(guò)這五步串行策略過(guò)濾之后,會(huì)選出一個(gè)最合適的節(jié)點(diǎn)。然后對(duì)這個(gè)節(jié)點(diǎn)上待搶占的 Pod 進(jìn)行 delete,這樣就完成了一次待搶占的過(guò)程。
6. 小結(jié)
簡(jiǎn)單介紹了一下調(diào)度的高級(jí)策略,在集群資源緊張的時(shí)候也能合理調(diào)度資源。我們回顧一下做了哪些事情:
創(chuàng)建自定義的一些優(yōu)先級(jí)類(lèi)別 (PriorityClass);
給不同類(lèi)型 Pods 配置不同的優(yōu)先級(jí) (PriorityClassName);
通過(guò)組合不同類(lèi)型 Pods 運(yùn)行和優(yōu)先級(jí)搶占讓集群資源和調(diào)度彈性起來(lái)。
推薦閱讀
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語(yǔ)言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛(ài)好者值得關(guān)注

