OpenKruise v1.2:新增 PersistentPodState 實現有狀態 Pod 拓撲固定與 IP 複用

語言: CN / TW / HK

雲原生應用自動化管理套件、CNCF Sandbox 專案 -- OpenKruise,近期釋出了 v1.2 版本。

OpenKruise [ 1] 是針對 Kubernetes 的增強能力套件,聚焦於雲原生應用的部署、升級、運維、穩定性防護等領域。所有的功能都通過 CRD 等標準方式擴充套件,可以適用於 1.16 以上版本的任意 Kubernetes 叢集。單條 helm 命令即可完成 Kruise 的一鍵部署,無需更多配置。

版本解析

Cloud Native

在 v1.2 版本中,OpenKruise 提供了一個名為 PersistentPodState 的新 CRD 和控制器,在 CloneSet status 和 lifecycle hook 中新增欄位, 並對 PodUnavailableBudget 做了多重優化。

1. 新增 CRD 和 Con troller- PersistentPodState

隨著雲原生的發展,越來越多的公司開始將有狀態服務(如:Etcd、MQ)進行 Kubernetes 部署。K8s StatefulSet 是管理有狀態服務的工作負載,它在很多方面考慮了有狀態服務的部署特徵。然而,StatefulSet 只能保持有限的 Pod 狀態,如:Pod Name 有序且不變,PVC 持久化,並不能滿足其它 Pod 狀態的保持需求,例如:固定 IP 排程,優先排程到之前部署的 Node 等。典型案例有:

  • 服務發現中介軟體服務對部署之後的 Pod IP 異常敏感,要求 IP 不能隨意改變

  • 資料庫服務將資料持久化到宿主機磁碟,所屬 Node 改變將導致資料丟失

針對上述描述,Kruise 通過自定義 PersistentPodState CRD,能夠保持 Pod 其它相關狀態,例如:“固定 IP 排程”。

一個 PersistentPodState 資源物件 YAML 如下:

apiVersion: apps.kruise.io/v1alpha1
kind: PersistentPodState
metadata:
name: echoserver
namespace: echoserver
spec:
targetRef:
# 原生k8s 或 kruise StatefulSet
# 只支援 StatefulSet 型別
apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
name: echoserver
# required node affinity,如下:Pod重建後將強制部署到同Zone
requiredPersistentTopology:
nodeTopologyKeys:
failure-domain.beta.kubernetes.io/zone[,other node labels]
# preferred node affinity,如下:Pod重建後將盡量部署到同Node
preferredPersistentTopology:
- preference:
nodeTopologyKeys:
kubernetes.io/hostname[,other node labels]
# int, [1 - 100]
weight: 100

“固定 IP 排程”應該是比較常見的有狀態服務的 K8s 部署要求,它的含義不是“指定 Pod IP 部署”,而是要求 Pod 在第一次部署之後,業務釋出或機器驅逐等常規性運維操作都不會導致 Pod IP 發生變化。達到上述效果,首先就需要 K8s 網路元件能夠支援 Pod IP 保留以及儘量保持 IP 不變的能力,本文將 flannel 網路元件中的 Host-local 外掛做了一些程式碼改造, 使之能夠達到同 Node 下保持 Pod IP 不變的效果,相關原理就不在此陳述,程式碼請參考: host-local [ 2]

“固定 IP 排程”好像有網路元件支援就好了,這跟 PersistentPodState 有什麼關係呢?因為,網路元件實現"Pod IP 保持不變"都有一定的限制, 例如:flannel 只能支援同 Node 保持 Pod IP 不變。但是,K8s 排程的最大特性就是“不確定性”,所以“如何保證 Pod 重建後排程到同 Node 上”就是 PersistentPodState 解決的問題。

另外,你可以通過在 StatefulSet 或 Advanced StatefulSet 上面新增如下的 annotations,來讓 Kruise 自動為你的 StatefulSet 建立 PersistentPodState 物件,從而避免了手動建立所有 PersistentPodState 的負擔。

apiVersion: apps.kruise.io/v1alpha1
kind: StatefulSet
metadata:
annotations:
# 自動生成PersistentPodState物件
kruise.io/auto-generate-persistent-pod-state: "true"
# preferred node affinity,如下:Pod重建後將盡量部署到同Node
kruise.io/preferred-persistent-topology: kubernetes.io/hostname[,other node labels]
# required node affinity,如下:Pod重建後將強制部署到同Zone
kruise.io/required-persistent-topology: failure-domain.beta.kubernetes.io/zone[,other node labels]

2. CloneSet 針對百分比形式 partition 計算邏輯變化,新增 status 欄位

過去,CloneSet 通過 “向上取整” 的方式來計算它的 partition 數值(當它是百分比形式的數值時),這意味著即使你將 partition 設定為一個小於 100% 的百分比,CloneSet 也有可能不會升級任何一個 Pod 到新版本。比如,對於一個 replicas=8 和 partition=90% 的 CloneSet 物件,它所計算出的實際 partition 數值是 8(來自 8 * 90% 向上取整), 因此它暫時不會執行升級動作。這有時候會為使用者來帶困惑,尤其是對於使用了一些 rollout 滾動升級元件的場景,比如 Kruise Rollout 或 Argo。

因此,從 v1.2 版本開始,CloneSet 會保證在 partition 是小於 100% 的百分比數值時,至少有 1 個 Pod 會被升級,除非 CloneSet 處於 replicas <= 1 的情況。

不過,這樣會導致使用者難以理解其中的計算邏輯,同時又需要在 partition 升級的時候知道期望升級的 Pod 數量,來判斷該批次升級是否完成。

所以我們另外又在 CloneSet status 中新增了 expectedUpdatedReplicas 欄位,它可以很直接地展示基於當前的 partition 數值,期望有多少 Pod 會被升級。對於使用者來說:

只需要比對 status.updatedReplicas >= status.expectedUpdatedReplicas 以及另外的 updatedReadyReplicas 來判斷當前釋出階段是否達到完成狀態。

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
replicas: 8
updateStrategy:
partition: 90%
status:
replicas: 8
expectedUpdatedReplicas: 1
updatedReplicas: 1
updatedReadyReplicas: 1

3. 在 lifecycle hook 階段設定 Pod not-ready

Kruise 在早先的版本中提供了 lifecycle hook 功能,其中 CloneSet 和 Advanced StatefulSet 都支援了 PreDelete、InPlaceUpdate 兩種 hook, Advanced DaemonSet 目前只支援 PreDelete hook。

過去,這些 hook 只會將當前的操作卡住,並允許使用者在 Pod 刪除之前或者原地升級的前後來做一些自定義的事情(比如將 Pod 從服務端點中摘除)。但是,Pod 在這些階段中很可能還處於 Ready 狀態,此時將它從一些自定義的 service 實現中摘除, 其實一定程度上有點違背 Kubernetes 的常理,一般來說它只會將處於 NotReady 狀態的 Pod 從服務端點中摘除。

因此,這個版本我們在 lifecycle hook 中新增了 markPodNotReady 欄位,它控制了 Pod 在處於 hook 階段的時候是否會被強制設為 NotReady 狀態。

type LifecycleStateType string
// Lifecycle contains the hooks for Pod lifecycle.
type Lifecycle struct
// PreDelete is the hook before Pod to be deleted.
PreDelete *LifecycleHook `json:"preDelete,omitempty"`
// InPlaceUpdate is the hook before Pod to update and after Pod has been updated.
InPlaceUpdate *LifecycleHook `json:"inPlaceUpdate,omitempty"`
}
type LifecycleHook struct {
LabelsHandler map[string]string `json:"labelsHandler,omitempty"`
FinalizersHandler []string `json:"finalizersHandler,omitempty"`


/********************** FEATURE STATE: 1.2.0 ************************/
// MarkPodNotReady = true means:
// - Pod will be set to 'NotReady' at preparingDelete/preparingUpdate state.
// - Pod will be restored to 'Ready' at Updated state if it was set to 'NotReady' at preparingUpdate state.
// Default to false.
MarkPodNotReady bool `json:"markPodNotReady,omitempty"`
/*********************************************************************/
}

對於配置了 markPodNotReady: true 的 PreDelete hook,它會在 PreparingDelete 階段的時候將 Pod 設定為 NotReady, 並且這種 Pod 在我們重新調大 replicas 數值的時候無法重新回到 normal 狀態。

對於配置了 markPodNotReady: true 的 InPlaceUpdate hook,它會在 PreparingUpdate 階段將 Pod 設定為 NotReady, 並在 Updated 階段將強制 NotReady 的狀態去掉。

4. PodUnavailableBudget 支援自定義 workload 與效能優化

Kubernetes 自身提供了 PodDisruptionBudget 來幫助使用者保護高可用的應用,但它只能防護 eviction 驅逐一種場景。對於多種多樣的不可用操作,PodUnavailableBudget 能夠更加全面地防護應用的高可用和 SLA,它不僅能夠防護 Pod 驅逐,還支援其他如刪除、原地升級等會導致 Pod 不可用的操作。

過去,PodUnavailableBudget 僅僅支援一些特定的 workload,比如 CloneSet、Deployment 等,但它不能夠識別使用者自己定義的一些未知工作負載。

從 v1.2 版本開始,PodUnavailableBudget 支援了保護任意自定義工作負載的 Pod,只要這些工作負載聲明瞭 scale subresource 子資源。

在 CRD 中,scale 子資源的宣告方式如下:

    subresources:
scale:
labelSelectorPath: .status.labelSelector
specReplicasPath: .spec.replicas
statusReplicasPath: .status.replicas

不過,如果你的專案是通過 kubebuilder 或 operator-sdk 生成的,那麼只需要在你的 workload 定義結構上加一行註解並重新 make manifests 即可:

// +kubebuilder:subresource:scale:specpath=.spec.replicas,statuspath=.status.replicas,selectorpath=.status.labelSelector

另外,PodUnavailableBudget 還通過關閉 client list 時候的預設 DeepCopy 操作,來提升了在大規模叢集中的執行時效能。

5. 其他改動

你可以通過  Github release [ 3] 頁面,來檢視更多的改動以及它們的作者與提交記錄。

社群參與

Cloud Native

非常歡迎你通過 Github/Slack/釘釘/微信 等方式加入我們來參與 OpenKruise 開源社群。你是否已經有一些希望與我們社群交流的內容呢?可以在我們的社群雙週會

https://shimo.im/docs/gXqmeQOYBehZ4vqo)上分享你的聲音,或通過以下渠道參與討論:

  • 加入社群 Slack channel (English)

    https://kubernetes.slack.com/?redir=%2Farchives%2Fopenkruise

  • 加入社群釘釘群:搜尋群號 23330762 (Chinese)

  • 加入社群微信群(新):新增使用者 openkruise 並讓機器人拉你入群 (Chinese)

參考連結:

[1] OpenKruise:

https: //openkruise.io/

[2] host-local:

https: //github.com/openkruise/samples

[3] Github release : 

https://github.com/openkruise/kruise/releases

戳原文,檢視 OpenKruise 專案 github 主頁!!