天天看點

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景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 個副本,剩餘的部署到彈性節點池

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景WorkloadSpread介紹現有方案對比應用場景實作原理小結
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. 優先部署到基礎節點池,資源不足再部署到彈性資源池

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景WorkloadSpread介紹現有方案對比應用場景實作原理小結
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

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景WorkloadSpread介紹現有方案對比應用場景實作原理小結
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 内部容器的資源配額,環境變量等。

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景WorkloadSpread介紹現有方案對比應用場景實作原理小結
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 造成額外成本。

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景WorkloadSpread介紹現有方案對比應用場景實作原理小結

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 能力,邏輯上分為兩種:

  1. SimulationSchedule:在 Kruise webhook 中根據 informer 裡已有的 nodes/pods 資料,組裝出排程賬本,對 Pod 進行模拟排程。即通過 nodeSelector/affinity、tolerations、以及基本的 resources 資源,做一次簡單的過濾。(對于 vk 這種節點不太适用)
  2. 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,幫助使用者解決更多雲原生部署方面的難題,建構一個更好的社群。

OpenKruise v0.10.0 新特性 WorkloadSpread 解讀背景WorkloadSpread介紹現有方案對比應用場景實作原理小結

相關連結:

連結一: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 項目!