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 的設置:

Kubernetes 的調度由處於兩個不同 Goroutine 的循環構成:

我們先看下 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 時包括以下兩步:

  1. 從所有的節點中選擇滿足條件的 Node;

  2. 使用算法計算第一步選出的 Node 的分數,並選出最高值的 Node 爲最終結果。

在第一步中,Kubernetes 早期是從 ETCD 中取 Node 的信息的,這樣每一次的調度都得發起 API 請求;在後續的版本中進行了優化,在本地維護了一個 cache 來存儲 Node 的信息,每一次調度只用從緩存中獲取 Node 的信息。

在第二步執行完後,此時會直接將 Pod 與 Node 進行綁定(設置 Pod 的 nodeName)。而此時 kubeket 只會同步寫本地緩存,並不會同步的將該 Pod 的綁定操作同步到 API Server,而是創建了個 Goroutine 來異步處理的。

由於是異步的操作,所以在 Pod 真正的調度到某個 Node 上時,還會進行一次資源的檢查和校驗。

這裏帶來一個問題,如果此時檢查不通過,調度失敗了怎麼辦?由於本地的 Cache 是會定時更新的,所以在下一次同步過來的數據會是正確的,所以也不會造成數據不一致問題。

在第一步獲取符合條件的 Node 列表通過以下四個條件進行:

  1. 根據定義的 cpu、limit 的 request 值過濾,當宿主機加上該 Pod 佔用的資源後,是否小於或者等於機器的容量;

  2. Volume 相關的過濾,還記得在 Kubernetes 系列 13 一 持久化存儲 PV、PVC、StorageClass 這一節講的本地存儲這種方式嗎,如果 Pod 有使用本地存儲,則需要將 Pod 調度到該本地存儲掛載的宿主機;

  3. 宿主機的過濾,例如是否滿足污點條件等;有關污點的內容也在前面的小節中有提到;

  4. 與 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