Kubernetes 系列 一 容器的調度
我們知道 Kubernetes 的最小調度單位是 Pod,這一節我們來了解 Pod 是如何被調度的?
Kubernetes 調度 Pod 到哪一個 Pod 的依據是根據 Pod 內的容器需要的資源,以及 Node 的可用資源計算出來的結果。而 Pod 通過 yaml 定義的 rsources 節點來配置 Pod 的 CPU、內存資源。
在 Kubernetes 系列 7 一 最小編排單位 Pod 這一節中的 yaml 例子有說明如何配置 CPU、內存:
...
spec:
containers:
- name: nginx-1
image: nginx:latest
imagePullPolicy: Always
resources:
requests:
cpu: 0.1
memory: 1GB
limits:
cpu: 0.3
memory: 2GB
...
其中對於 CPU 的配置指的是 CPU 的個數,0.1 表示分配到一個 CPU10% 的計算能力,也可以配置成 100m(100millicpu)。
而 limit 和 request 的區別是在 Kubernetes 計算資源時是使用的 request,而進行 Cgroups 限制資源的時候實際上使用的是 limit。
這裏需要注意 CPU 爲可壓縮資源。當 CPU 資源不足時,Pod 只會運行效率變慢;而內存是不可壓縮資源,當內存資源不足時 Pod 會被清理掉再重新調度。
當一臺宿主機的資源緊張時會觸發 Kubernetes 的驅逐機制(Eviction),即從當前機器趕走一些 Pod。Kubernetes 並不會隨機的驅逐 Pod,而是有驅逐的先後順序的。
宿主機資源緊張的判斷條件如下:
memory.available<100Mi
nodefs.available<10%
nodefs.inodesFree<5%
imagefs.available<15%
也可以使用 kubelet 配置:
kubelet --eviction-hard=imagefs.available<10%,memory.available<500Mi,nodefs.available<10%,nodefs.inodesFree<5% --eviction-soft=imagefs.available<5%,nodefs.available<15% --eviction-soft-grace-period=imagefs.available=2m,nodefs.available=2m --eviction-max-pod-grace-period=600
驅逐的順序依賴 limit、request 的設置:
-
首先驅逐的是既沒有指定 limit、又沒有指定 request 的 Pod,這一類稱爲 BestEffort 類型。
-
其次驅逐的是僅僅只設定了 request,而沒有設置 limit 的 Pod,同時這個 Pod 的資源使用已經超過了 request,這一類稱爲 burstable 類型。
-
最後是即設置了 limit 又設置了 request 的 Pod(如果僅設置了 limit 沒有設置 request,Kubernetes 會默認設置 requst=limit),這一類稱爲 Guaranteed 類型。
Kubernetes 的調度由處於兩個不同 Goroutine 的循環構成:
-
第一個爲 informer 循環,負責監聽 ETCD 中的 Pod、Node、Service 等的變化,並將需要調度的 Pod 寫到一個優先級隊列裏(這表明調度新 Pod 的順序與創建的順序並不是一致的)。
-
第二個爲調度的主循環 Scheduling,從隊列裏取出 Pod 的定義,然後進行調度,而調度的過程也是經過兩個階段,我們再後面會講到。
我們先看下 Pod 的優先級定義, PriorityClass 對象指定優先級,如下示例定義了兩個優先級:
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high
value: 100
globalDefault: false
---
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: middle
value: 50
globalDefault: false
在 spec.priorityClassName 指定 Pod 的優先級:
spec:
containers:
...
priorityClassName: high
在選定了調度哪個 Pod 後,也就到了 Kubernetes 該進行資源計算調度 Pod 了。
調度某個 Pod 至某個 Node,當然是選擇一個最合適的 Node,在 kubernetes 選擇 Node 時包括以下兩步:
-
從所有的節點中選擇滿足條件的 Node;
-
使用算法計算第一步選出的 Node 的分數,並選出最高值的 Node 爲最終結果。
在第一步中,Kubernetes 早期是從 ETCD 中取 Node 的信息的,這樣每一次的調度都得發起 API 請求;在後續的版本中進行了優化,在本地維護了一個 cache 來存儲 Node 的信息,每一次調度只用從緩存中獲取 Node 的信息。
在第二步執行完後,此時會直接將 Pod 與 Node 進行綁定(設置 Pod 的 nodeName)。而此時 kubeket 只會同步寫本地緩存,並不會同步的將該 Pod 的綁定操作同步到 API Server,而是創建了個 Goroutine 來異步處理的。
由於是異步的操作,所以在 Pod 真正的調度到某個 Node 上時,還會進行一次資源的檢查和校驗。
這裏帶來一個問題,如果此時檢查不通過,調度失敗了怎麼辦?由於本地的 Cache 是會定時更新的,所以在下一次同步過來的數據會是正確的,所以也不會造成數據不一致問題。
在第一步獲取符合條件的 Node 列表通過以下四個條件進行:
-
根據定義的 cpu、limit 的 request 值過濾,當宿主機加上該 Pod 佔用的資源後,是否小於或者等於機器的容量;
-
Volume 相關的過濾,還記得在 Kubernetes 系列 13 一 持久化存儲 PV、PVC、StorageClass 這一節講的本地存儲這種方式嗎,如果 Pod 有使用本地存儲,則需要將 Pod 調度到該本地存儲掛載的宿主機;
-
宿主機的過濾,例如是否滿足污點條件等;有關污點的內容也在前面的小節中有提到;
-
與 Pod 的關係,如果定義了與某個 Pod 的親密性(affinity)或者反親密性(anti-affinity)關係,或者其他的 Pod 有定義與該 Pod 的親密性或者反親密性,則會根據該條件來選擇宿主機。
在第二步計算 Node 評分的算法叫做 Priorities,大家可以自己查閱資料去了解。這裏還是很有趣的,並不是資源最足的 Node 算出來的評分就是最高的,還需要考慮 Node 資源的碎片化等因素。
如果此時 Pod 調度失敗了,kubelet 也會根據 PriorityClass 的優先級來將低優先級的 Pod “擠出去”。我們可以利用這個機制來部署業務中相對重要的服務,保證每一次發佈後調度的成功,避免業務的主流程受到影響。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/UET14zyV31qV0_4aWoJwyQ