OpenKruise v0.10.0 新特性 WorkloadSpread 解讀


背景

Workload 分布在不同 zone,不同的硬件類型,甚至是不同的集群和雲廠商已經是一個非常普遍的需求。過去一般只能將一個應用拆分為多個 workload(比如 Deployment)來部署,由 SRE 團隊手工管理或者對 PaaS 層深度定制,來支持對一個應用多個 workload 的精細化管理。

進一步來說,在應用部署的場景下有着多種多樣的拓撲打散以及彈性的訴求。其中最常見就是按某種或多種拓撲維度打散,比如:

  • 應用部署需要按 node 維度打散,避免堆疊(提高容災能力)。
  • 應用部署需要按 AZ(available zone)維度打散(提高容災能力)。
  • 按 zone 打散時,需要指定在不同 zone 中部署的比例數。

隨着雲原生在國內外的迅速普及落地,應用對於彈性的需求也越來越多。各公有雲廠商陸續推出了 Serverless 容器服務來支撐彈性部署場景,如阿里雲的彈性容器服務 ECI,AWS 的 Fragate 容器服務等。以 ECI 為例,ECI 可以通過Virtual Kubelet對接 Kubernetes 系統,給予 Pod 一定的配置就可以調度到 virtual-node 背后的 ECI 集群。總結一些常見的彈性訴求,比如:

  • 應用優先部署到自有集群,資源不足時再部署到彈性集群。縮容時,優先從彈性節點縮容以節省成本。
  • 用戶自己規划基礎節點池和彈性節點池。應用部署時需要固定數量或比例的 Pod 部署在基礎節點池,其余的都擴到彈性節點池。

針對這些需求,OpenKruise 在 v0.10.0 版本中新增了 WorkloadSpread 特性。目前它支持配合 Deployment、ReplicaSet、CloneSet 這些 workload,來管理它們下屬 Pod 的分區部署與彈性伸縮。下文會深入介紹 WorkloadSpread 的應用場景和實現原理,幫助用戶更好的了解該特性。

WorkloadSpread介紹

官方文檔(見文末相關鏈接一)

簡而言之,WorkloadSpread 能夠將 workload 所屬的 Pod 按一定規則分布到不同類型的 Node 節點上,能夠同時滿足上述的打散與彈性場景。

現有方案對比

簡單對比一些社區已有的方案。

Pod Topology Spread Constrains(見文末相關鏈接二)

Pod Topology Spread Constrains 是 Kubernetes 社區提供的方案,可以定義按 topology key 的水平打散。用戶在定義完后,調度器會依據配置選擇符合分布條件的 node。

由於 PodTopologySpread 更多的是均勻打散,無法支持自定義的分區數量以及比例配置,且縮容時會破壞分布。WorkloadSpread 可以自定義各個分區的數量,並且管理着縮容的順序。因此在一些場景下可以避免 PodTopologySpread 的不足。

UnitedDeployment(見文末相關鏈接三)

UnitedDeployment 是 Kruise 社區提供的方案,通過創建和管理多個 workload 管理多個區域下的 Pod。

UnitedDeployment非常好的支持了打散與彈性的需求,不過它是一個全新的 workload,用戶的使用和遷移成本會比較高。而 WorkloadSpread 是一種輕量化的方案,只需要簡單的配置並關聯到 workload 即可。

應用場景

下面我會列舉一些 WorkloadSpread 的應用場景,給出對應的配置,幫助大家快速了解 WorkloadSpread 的能力。

1. 基礎節點池至多部署 100 個副本,剩余的部署到彈性節點池

1.png

subsets:
- name: subset-normal
  maxReplicas: 100
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - normal
- name: subset-elastic #副本數量不限
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - elastic

當 workload 少於 100 副本時,全部部署到 normal 節點池,超過 100 個部署到 elastic 節點池。縮容時會優先刪除 elastic 節點上的 Pod。

由於 WorkloadSpread 不侵入 workload,只是限制住了 workload 的分布,我們還可以通過結合 HPA 根據資源負載動態調整副本數,這樣當業務高峰時會自動調度到 elastic 節點上去,業務低峰時會優先釋放 elastic 節點池上的資源。

2. 優先部署到基礎節點池,資源不足再部署到彈性資源池

2.png

scheduleStrategy:
  type: Adaptive
  adaptive:
    rescheduleCriticalSeconds: 30
    disableSimulationSchedule: false
subsets:
- name: subset-normal #副本數量不限
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - normal
- name: subset-elastic #副本數量不限
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: app.deploy/zone
      operator: In
      values:
      - elastic

兩個 subset 都沒有副本數量限制,且啟用 Adptive 調度策略的模擬調度和 Reschedule 能力。部署效果是優先部署到 normal 節點池,normal 資源不足時,webhook 會通過模擬調度選擇 elastic 節點。當 normal 節點池中的 Pod 處於 pending 狀態超過 30s 閾值, WorkloadSpread controller 會刪除該 Pod 以觸發重建,新的 Pod 會被調度到 elastic 節點池。縮容時還是優先縮容 elastic 節點上的 Pod,為用戶節省成本。

3. 打散到3個zone,比例分別為1:1:3

3.png

subsets:
- name: subset-a
  maxReplicas: 20%
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: topology.kubernetes.io/zone
      operator: In
      values:
      - zone-a
- name: subset-b
  maxReplicas: 20%
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: topology.kubernetes.io/zone
      operator: In
      values:
      - zone-b
- name: subset-c
  maxReplicas: 60%
  requiredNodeSelectorTerm:
    matchExpressions:
    - key: topology.kubernetes.io/zone
      operator: In
      values:
      - zone-c

按照不同 zone 的實際情況,將 workload 按照 1:1:3 的比例打散。WorkloadSpread 會確保 workload 擴縮容時按照定義的比例分布。

4. workload在不同CPU Arch上配置不同的資源配額

workload 分布的 Node 可能有不同的硬件配置,CPU 架構等,這就可能需要為不同的 subset 分別制定 Pod 配置。這些配置可以是 label 和 annotation 等元數據也可以是 Pod 內部容器的資源配額,環境變量等。

4.png

subsets:
- name: subset-x86-arch
  # maxReplicas...
  # requiredNodeSelectorTerm...
  patch:
    metadata:
      labels:
        resource.cpu/arch: x86
    spec: 
      containers:
      - name: main
        resources:
          limits:
            cpu: "500m"
            memory: "800Mi"
- name: subset-arm-arch
  # maxReplicas...
  # requiredNodeSelectorTerm...
  patch:
    metadata:
      labels:
        resource.cpu/arch: arm
    spec: 
      containers:
      - name: main
        resources:
          limits:
            cpu: "300m"
            memory: "600Mi"

從上面的樣例中我們為兩個 subset 的 Pod 分別 patch 了不同的 label, container resources,方便我們對 Pod 做更精細化的管理。當 workload 的 Pod 分布在不同的 CPU 架構的節點上,配置不同的資源配額以更好的利用硬件資源。

實現原理

WorkloadSpread 是一個純旁路的彈性/拓撲管控方案。用戶只需要針對自己的 Deployment/CloneSet/Job 對象創建對應的 WorkloadSpread 即可,無需對 workload 做改動,也不會對用戶使用 workload 造成額外成本。

5.png

1. subset優先級與副本數量控制

WorkloadSpread 中定義了多個 subset,每個 subset 代表一個邏輯域。用戶可以自由的根據節點配置,硬件類型,zone 等來划分 subset。特別的,我們規定了 subset 的優先級:

  1. 按定義從前往后的順序,優先級從高到低。
  2. 優先級越高,越先擴容;優先級越低,越先縮容。

2. 如何控制縮容優先級

理論上,WorkloadSpread 這種旁路方案是無法干涉到 workload 控制器里的縮容順序邏輯的。

不過,這個問題在近期得以解決—— 經過一代代用戶的不懈努力(反饋),K8s 從 1.21 版本開始為 ReplicaSet(Deployment)支持了通過設置 controller.kubernetes.io/pod-deletion-cost 這個 annotation 來指定 Pod 的 “刪除代價”:deletion-cost 越高的 Pod,刪除的優先級越低。

而 Kruise 從 v0.9.0 版本開始,就在 CloneSet 中支持了 deletion-cost 特性。

因此,WorkloadSpread controller通過調整各個 subset 下屬 Pod 的 deletion-cost,來控制workload的縮容順序。

舉個例子:對於以下 WorkloadSpread,以及它關聯的 CloneSet 有 10 個副本:

subsets:
- name: subset-a
  maxReplicas: 8
- name: subset-b # 副本數量不限

則 deletion-cost 數值以及刪除順序為:

  • 2 個在 subset-b上的 Pod,deletion-cost 為 100(優先縮容)
  • 8 個在 subset-a上的 Pod,deletion-cost 為 200(最后縮容)

然后,如果用戶修改了 WorkloadSpread 為:

subsets:
 - name: subset-a
   maxReplicas: 5 # 8-3, 
 - name: subset-b

則 workloadspread controller 會將其中 3 個在 susbet-a 上 Pod 的 deletion-cost 值由 200 改為 -100

  • 3 個在 subset-a 上的 Pod,deletion-cost 為 -100(優先縮容)
  • 2 個在 subset-b 上的 Pod,deletion-cost 為 100(其次縮容)
  • 5 個在 subset-a 上的 Pod,deletion-cost 為 200(最后縮容)

這樣就能夠優先縮容那些超過 subset 副本限制的 Pod 了,當然總體還是按照 subset 定義的順序從后向前縮容。

3. 數量控制

如何確保 webhook 嚴格按照 subset 優先級順序、maxReplicas 數量來注入 Pod 規則是 WorkloadSpread 實現層面的重點難題。

3.1 解決並發一致性問題

在 workloadspread 的 status 中有對應每個 subset 的 status,其中 missingReplicas 字段表示了這個 subset 需要的 Pod 數量,-1 表示沒有數量限制(subset 沒有配置 maxReplicas)。

spec:
  subsets:
  - name: subset-a
    maxReplicas: 1
  - name: subset-b
  # ...
status:
  subsetStatuses:
  - name: subset-a
    missingReplicas: 1
  - name: subset-b
    missingReplicas: -1
  # ...

當 webhook 收到 Pod create請求時:

  1. 根據 subsetStatuses 順序依次找 missingReplicas 大於 0 或為 -1 的 suitable subset。
  2. 找到suitable subset后,如果 missingReplicas 大於 0,則先減 1 並嘗試更新 workloadspread status。
  3. 如果更新成功,則將該 subset定義的規則注入到 pod 中。
  4. 如果更新失敗,則重新 get 這個 workloadspread以獲取最新的 status,並回到步驟 1(有一定重試次數限制)。

同樣,當 webhook 收到 Pod delete/eviction 請求時,則將 missingReplicas 加 1 並更新。

毫無疑問,我們在使用樂觀鎖來解決更新沖突。但是僅使用樂觀鎖是不合適的,因為 workload 在創建 Pod 時會並行創建大量的 Pod,apiserver 會在一瞬間發送很多 Pod create 請求到 webhook,並行處理會產生非常多的沖突。大家都知道,沖突太多就不適合使用樂觀鎖了,因為它解決沖突的重試成本非常高。為此我們還加入了 workloadspread 級別的互斥鎖,將並行處理限制為串行處理。加入互斥鎖還有新的問題,即當前 groutine 獲取鎖后,極有可能從 infromer 中拿的 workloadspread 不是最新的,還是會沖突。所以 groutine 在更新完 workloadspread 之后,先將最新的 workloadspread 對象緩存起來再釋放鎖,這樣新的 groutine 獲取鎖后就可以直接從緩存中拿到最新的 workloadspread。當然,多個 webhook 的情況下還是需要結合樂觀鎖機制來解決沖突。

3.2 解決數據一致性問題

那么,missingReplicas 數值是否交由 webhook 控制即可呢?答案是不行,因為:

  1. webhook 收到的 Pod create 請求,最終不一定真的能成功(比如 Pod 不合法,或在后續 quota 等校驗環節失敗了)。
  2. webhook 收到的 Pod delete/eviction 請求,最終也不一定真的能成功(比如后續被 PDB、PUB 等攔截了)。
  3. K8s 里總有種種的可能性,導致 Pod 沒有經過 webhook 就結束或沒了(比如 phase 進入 Succeeded/Failed,或是 etcd 數據丟了等等)。
  4. 同時,這也不符合面向終態的設計理念。

因此,workloadspread status 是由 webhook 與 controller 協作來控制的:

  • webhook 在 Pod create/delete/eviction 請求鏈路攔截,修改 missingReplicas 數值。
  • 同時 controller 的 reconcile 中也會拿到當前 workload 下的所有 Pod,根據 subset 分類,並將 missingReplicas 更新為當前實際缺少的數量。
  • 從上面的分析中,controller 從 informer 中獲取 Pod 很可能存在延時,所以我們還在status中增加了 creatingPods map, webook 注入的時候會記錄 key 為pod.name, value 為時間戳的一條 entry 到 map,controller 再結合 map 維護真實的 missingReplicas。同理還有一個 deletingPods map 來記錄 Pod 的delete/eviction 事件。

4. 自適應調度能力

在 WorkloadSpread 中支持配置 scheduleStrategy。默認情況下,type 為 Fixed,即固定按照各個 subset 的前后順序、maxReplicas 限制來將 Pod 調度到對應的 subset 中。

但真實的場景下,很多時候 subset 分區或拓撲的資源,不一定能完全滿足 maxReplicas 數量。用戶需要按照實際的資源情況,來為 Pod 選擇有資源的分區擴容。這就需要用 Adaptive 這種自適應的調度分配。

WorkloadSpread 提供的 Adaptive 能力,邏輯上分為兩種:

  • SimulationSchedule:在 Kruise webhook 中根據 informer 里已有的 nodes/pods 數據,組裝出調度賬本,對 Pod 進行模擬調度。即通過 nodeSelector/affinity、tolerations、以及基本的 resources 資源,做一次簡單的過濾。(對於 vk 這種節點不太適用)
  • Reschedule:在將 Pod 調度到一個 subset 后,如果調度失敗超過 rescheduleCriticalSeconds 時間,則將該 subset 暫時標記為 unschedulable,並刪除 Pod 觸發重建。默認情況下,unschedulable 會保留 5min,即在 5min 內的 Pod 創建會跳過這個 subset。

小結

WorkloadSpread 通過結合一些 kubernetes 現有的特性以一種旁路的形式賦予 workload 彈性部署與多域部署的能力。我們希望用戶通過使用 WorkloadSpread 降低 workload 部署復雜度,利用其彈性伸縮能力切實降低成本。

目前阿里雲內部正在積極的落地,落地過程中的調整會及時反饋社區。未來 WorkloadSpread 還有一些新能力計划,比如讓 WorkloadSpread 支持 workload 的存量 Pod 接管,支持批量的 workload 約束,甚至是跨過 workload 層級使用 label 來匹配 Pod。其中一些能力需要實際考量社區用戶的需求場景。希望大家多多參與到 Kruise 社區,多提 issue 和 pr,幫助用戶解決更多雲原生部署方面的難題,構建一個更好的社區。

6.png

相關鏈接:
鏈接一:WorkloadSpread 官方文檔:
https://openkruise.io/zh-cn/docs/workloadspread.html
鏈接二:Pod Topology Spread Constrains :
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/
鏈接三:UnitedDeployment :
https://openkruise.io/zh-cn/docs/uniteddeployment.html

點擊下方鏈接,立即了解 OpenKruise 項目!
https://github.com/openkruise/kruise


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM