使用 OpenKruise 增強 Kubernetes 工作負(fù)載之 CloneSet
OpenKruise 是一個基于 Kubernetes 的擴(kuò)展套件,主要聚焦于云原生應(yīng)用的自動化,比如部署、發(fā)布、運維以及可用性防護(hù)。OpenKruise 提供的絕大部分能力都是基于 CRD 擴(kuò)展來定義的,它們不存在于任何外部依賴,可以運行在任意純凈的 Kubernetes 集群中。Kubernetes 自身提供的一些應(yīng)用部署管理功能,對于大規(guī)模應(yīng)用與集群的場景這些功能是遠(yuǎn)遠(yuǎn)不夠的,OpenKruise 彌補了 Kubernetes 在應(yīng)用部署、升級、防護(hù)、運維等領(lǐng)域的不足。
OpenKruise 提供了以下的一些核心能力:
增強版本的 Workloads:OpenKruise 包含了一系列增強版本的工作負(fù)載,比如 CloneSet、Advanced StatefulSet、Advanced DaemonSet、BroadcastJob 等。它們不僅支持類似于 Kubernetes 原生 Workloads 的基礎(chǔ)功能,還提供了如原地升級、可配置的擴(kuò)縮容/發(fā)布策略、并發(fā)操作等。其中,原地升級是一種升級應(yīng)用容器鏡像甚至環(huán)境變量的全新方式,它只會用新的鏡像重建 Pod 中的特定容器,整個 Pod 以及其中的其他容器都不會被影響。因此它帶來了更快的發(fā)布速度,以及避免了對其他 Scheduler、CNI、CSI 等組件的負(fù)面影響。 應(yīng)用的旁路管理:OpenKruise 提供了多種通過旁路管理應(yīng)用 sidecar 容器、多區(qū)域部署的方式,“旁路” 意味著你可以不需要修改應(yīng)用的 Workloads 來實現(xiàn)它們。比如,SidecarSet 能幫助你在所有匹配的 Pod 創(chuàng)建的時候都注入特定的 sidecar 容器,甚至可以原地升級已經(jīng)注入的 sidecar 容器鏡像、并且對 Pod 中其他容器不造成影響。而 WorkloadSpread 可以約束無狀態(tài) Workload 擴(kuò)容出來 Pod 的區(qū)域分布,賦予單一 workload 的多區(qū)域和彈性部署的能力。 高可用性防護(hù):OpenKruise 可以保護(hù)你的 Kubernetes 資源不受級聯(lián)刪除機制的干擾,包括 CRD、Namespace、以及幾乎全部的 Workloads 類型資源。相比于 Kubernetes 原生的 PDB 只提供針對 Pod Eviction 的防護(hù),PodUnavailableBudget 能夠防護(hù) Pod Deletion、Eviction、Update 等許多種 voluntary disruption 場景。 高級的應(yīng)用運維能力:OpenKruise 也提供了很多高級的運維能力來幫助你更好地管理應(yīng)用,比如可以通過 ImagePullJob 來在任意范圍的節(jié)點上預(yù)先拉取某些鏡像,或者指定某個 Pod 中的一個或多個容器被原地重啟。
架構(gòu)
下圖是 OpenKruise 的整體架構(gòu):

首先我們要清楚所有 OpenKruise 的功能都是通過 Kubernetes CRD 來提供的:
??kubectl?get?crd?|?grep?kruise.io
advancedcronjobs.apps.kruise.io????????????2021-09-16T06:02:36Z
broadcastjobs.apps.kruise.io???????????????2021-09-16T06:02:36Z
clonesets.apps.kruise.io???????????????????2021-09-16T06:02:36Z
containerrecreaterequests.apps.kruise.io???2021-09-16T06:02:36Z
daemonsets.apps.kruise.io??????????????????2021-09-16T06:02:36Z
imagepulljobs.apps.kruise.io???????????????2021-09-16T06:02:36Z
nodeimages.apps.kruise.io??????????????????2021-09-16T06:02:36Z
podunavailablebudgets.policy.kruise.io?????2021-09-16T06:02:36Z
resourcedistributions.apps.kruise.io???????2021-09-16T06:02:36Z
sidecarsets.apps.kruise.io?????????????????2021-09-16T06:02:36Z
statefulsets.apps.kruise.io????????????????2021-09-16T06:02:36Z
uniteddeployments.apps.kruise.io???????????2021-09-16T06:02:37Z
workloadspreads.apps.kruise.io?????????????2021-09-16T06:02:37Z
其中 Kruise-manager 是一個運行控制器和 webhook 的中心組件,它通過 Deployment 部署在 kruise-system 命名空間中。從邏輯上來看,如 cloneset-controller、sidecarset-controller 這些的控制器都是獨立運行的,不過為了減少復(fù)雜度,它們都被打包在一個獨立的二進(jìn)制文件、并運行在 kruise-controller-manager-xxx 這個 Pod 中。除了控制器之外,kruise-controller-manager-xxx 中還包含了針對 Kruise CRD 以及 Pod 資源的 admission webhook。Kruise-manager 會創(chuàng)建一些 webhook configurations 來配置哪些資源需要感知處理、以及提供一個 Service 來給 kube-apiserver 調(diào)用。
從 v0.8.0 版本開始提供了一個新的 Kruise-daemon 組件,它通過 DaemonSet 部署到每個節(jié)點上,提供鏡像預(yù)熱、容器重啟等功能。
安裝
這里我們同樣還是使用 Helm 方式來進(jìn)行安裝,需要注意從 v1.0.0 開始,OpenKruise 要求在 Kubernetes >= 1.16 以上版本的集群中安裝和使用。
首先添加 charts 倉庫:
??helm?repo?add?openkruise?https://openkruise.github.io/charts
??helm?repo?update
然后執(zhí)行下面的命令安裝最新版本的應(yīng)用:
??helm?upgrade?--install?kruise?openkruise/kruise?--version?1.0.1
該 charts 在模板中默認(rèn)定義了命名空間為 kruise-system,所以在安裝的時候可以不用指定,如果你的環(huán)境訪問 DockerHub 官方鏡像較慢,則可以使用下面的命令將鏡像替換成阿里云的鏡像:
??helm?upgrade?--install?kruise?openkruise/kruise?--set?manager.image.repository=openkruise-registry.cn-hangzhou.cr.aliyuncs.com/openkruise/kruise-manager?--version?1.0.1
應(yīng)用部署完成后會在 kruise-system 命名空間下面運行2個 kruise-manager 的 Pod,同樣它們之間采用 leader-election 的方式選主,同一時間只有一個提供服務(wù),達(dá)到高可用的目的,此外還會以 DaemonSet 的形式啟動 kruise-daemon 組件:
??kubectl?get?pods?-n?kruise-system
NAME????????????????????????????????????????READY???STATUS????RESTARTS???AGE
kruise-controller-manager-f5c9b55c5-7hgt9???1/1?????Running???0??????????4m3s
kruise-controller-manager-f5c9b55c5-v9ptf???1/1?????Running???0??????????4m3s
kruise-daemon-bqf5v?????????????????????????1/1?????Running???0??????????4m3s
kruise-daemon-hvgwv?????????????????????????1/1?????Running???0??????????4m3s
kruise-daemon-tnqsx?????????????????????????1/1?????Running???0??????????4m3s
如果不想使用默認(rèn)的參數(shù)進(jìn)行安裝,也可以自定義配置,可配置的 values 值可以參考 charts 文檔 https://github.com/openkruise/charts 進(jìn)行定制。
CloneSet
CloneSet 控制器是 OpenKruise 提供的對原生 Deployment 的增強控制器,在使用方式上和 Deployment 幾乎一致,如下所示是我們聲明的一個 CloneSet 資源對象:
#?cloneset-demo.yaml
apiVersion:?apps.kruise.io/v1alpha1
kind:?CloneSet
metadata:
??name:?cs-demo
spec:
??replicas:?3
??selector:
????matchLabels:
??????app:?cs
??template:
????metadata:
??????labels:
????????app:?cs
????spec:
??????containers:
??????-?name:?nginx
????????image:?nginx:alpine
????????imagePullPolicy:?IfNotPresent
????????ports:
????????-?containerPort:?80
直接創(chuàng)建上面的這個 CloneSet 對象:
??kubectl?apply?-f?cloneset-demo.yaml
??kubectl?get?cloneset?cs-demo
NAME??????DESIRED???UPDATED???UPDATED_READY???READY???TOTAL???AGE
cs-demo???3?????????3?????????3???????????????3???????3???????112s
??kubectl?describe?cloneset?cs-demo
Name:?????????cs-demo
Namespace:????default
Labels:???????
Annotations:??kubectl.kubernetes.io/last-applied-configuration:
????????????????{"apiVersion":"apps.kruise.io/v1alpha1","kind":"CloneSet","metadata":{"annotations":{},"name":"cs-demo","namespace":"default"},"spec":{"re...
API?Version:??apps.kruise.io/v1alpha1
Kind:?????????CloneSet
......
Events:
??Type????Reason????????????Age???From?????????????????Message
??----????------????????????----??----?????????????????-------
??Normal??SuccessfulCreate??53s???cloneset-controller??succeed?to?create?pod?cs-demo-b6r6t
??Normal??SuccessfulCreate??53s???cloneset-controller??succeed?to?create?pod?cs-demo-fsbx5
??Normal??SuccessfulCreate??53s???cloneset-controller??succeed?to?create?pod?cs-demo-fv5gb
該對象創(chuàng)建完成后我們可以通過 kubectl describe 命令查看對應(yīng)的 Events 信息,可以發(fā)現(xiàn) cloneset-controller 是直接創(chuàng)建的 Pod,這個和原生的 Deployment 就有一些區(qū)別了,Deployment 是通過 ReplicaSet 去創(chuàng)建的 Pod,所以從這里也可以看出來 CloneSet 是直接管理 Pod 的,3個副本的 Pod 此時也創(chuàng)建成功了:
??kubectl?get?pods?-l?app=cs
NAME????????????READY???STATUS????RESTARTS???AGE
cs-demo-b6r6t???1/1?????Running???0??????????5m19s
cs-demo-fsbx5???1/1?????Running???0??????????5m19s
cs-demo-fv5gb???1/1?????Running???0??????????5m19s
CloneSet 雖然在使用上和 Deployment 比較類似,但還是有非常多比 Deployment 更高級的功能,下面我們來詳細(xì)介紹下。
擴(kuò)縮容
CloneSet 在擴(kuò)容的時候可以通過 ScaleStrategy.MaxUnavailable 來限制擴(kuò)容的步長,這樣可以對服務(wù)應(yīng)用的影響最小,可以設(shè)置一個絕對值或百分比,如果不設(shè)置該值,則表示不限制。
比如我們在上面的資源清單中添加如下所示數(shù)據(jù):
apiVersion:?apps.kruise.io/v1alpha1
kind:?CloneSet
metadata:
??name:?cs-demo
spec:
??minReadySeconds:?60
??scaleStrategy:
????maxUnavailable:?1
??replicas:?5
??......
上面我們配置 scaleStrategy.maxUnavailable 為1,結(jié)合 minReadySeconds 參數(shù),表示在擴(kuò)容時,只有當(dāng)上一個擴(kuò)容出的 Pod 已經(jīng) Ready 超過一分鐘后,CloneSet 才會執(zhí)行創(chuàng)建下一個 Pod,比如這里我們擴(kuò)容成5個副本,更新上面對象后查看 CloneSet 的事件:
??kubectl?describe?cloneset?cs-demo
......
Events:
??Type?????Reason????????????Age???????????????????From?????????????????Message
??----?????------????????????----??????????????????----?????????????????-------
??Normal???SuccessfulCreate??35m???????????????????cloneset-controller??succeed?to?create?pod?cs-demo-b6r6t
??Normal???SuccessfulCreate??35m???????????????????cloneset-controller??succeed?to?create?pod?cs-demo-fsbx5
??Normal???SuccessfulCreate??35m???????????????????cloneset-controller??succeed?to?create?pod?cs-demo-fv5gb
??Warning??ScaleUpLimited????2m39s?????????????????cloneset-controller??scaleUp?is?limited?because?of?scaleStrategy.maxUnavailable,?limit:?1
??Normal???SuccessfulCreate??2m39s?????????????????cloneset-controller??succeed?to?create?pod?cs-demo-xlsdg
??Normal???SuccessfulCreate??98s???????????????????cloneset-controller??succeed?to?create?pod?cs-demo-8w7h4
??Warning??ScaleUpLimited????68s?(x12?over?2m39s)??cloneset-controller??scaleUp?is?limited?because?of?scaleStrategy.maxUnavailable,?limit:?0
??Normal???SuccessfulCreate??37s???????????????????cloneset-controller??succeed?to?create?pod?cs-demo-79rcx
可以看到第一時間擴(kuò)容了一個 Pod,由于我們配置了 minReadySeconds: 60,也就是新擴(kuò)容的 Pod 創(chuàng)建成功超過1分鐘后才會擴(kuò)容另外一個 Pod,上面的 Events 信息也能表現(xiàn)出來,查看 Pod 的 AGE 也能看出來擴(kuò)容的2個 Pod 之間間隔了1分鐘左右:
??kubectl?get?pods?-l?app=cs
NAME????????????READY???STATUS????RESTARTS???AGE
cs-demo-79rcx???1/1?????Running???0??????????2m3s
cs-demo-8w7h4???1/1?????Running???0??????????3m4s
cs-demo-b6r6t???1/1?????Running???0??????????36m
cs-demo-fv5gb???1/1?????Running???0??????????36m
cs-demo-p4kmw???1/1?????Running???0??????????36s
當(dāng) CloneSet 被縮容時,我們還可以指定一些 Pod 來刪除,這對于 StatefulSet 或者 Deployment 來說是無法實現(xiàn)的, StatefulSet 是根據(jù)序號來刪除 Pod,而 Deployment/ReplicaSet 目前只能根據(jù)控制器里定義的排序來刪除。而 CloneSet 允許用戶在縮小 replicas 數(shù)量的同時,指定想要刪除的 Pod 名字,如下所示:
apiVersion:?apps.kruise.io/v1alpha1
kind:?CloneSet
metadata:
??name:?cs-demo
spec:
??minReadySeconds:?60
??scaleStrategy:
????maxUnavailable:?1
????podsToDelete:
????-?cs-demo-79rcx
??replicas:?4
??......
更新上面的資源對象后,會將應(yīng)用縮到4個 Pod,如果在 podsToDelete 列表中指定了 Pod 名字,則控制器會優(yōu)先刪除這些 Pod,對于已經(jīng)被刪除的 Pod,控制器會自動從 podsToDelete 列表中清理掉。比如我們更新上面的資源對象后 cs-demo-79rcx 這個 Pod 會被移除,其余會保留下來:
??kubectl?get?pods?-l?app=cs
NAME????????????READY???STATUS????RESTARTS??????AGE
cs-demo-8w7h4???1/1?????Running???4?(51m?ago)???3d6h
cs-demo-b6r6t???1/1?????Running???4?(51m?ago)???3d6h
cs-demo-fv5gb???1/1?????Running???4?(51m?ago)???3d6h
cs-demo-p4kmw???1/1?????Running???4?(51m?ago)???3d6h
如果你只把 Pod 名字加到 podsToDelete,但沒有修改 replicas 數(shù)量,那么控制器會先把指定的 Pod 刪掉,然后再擴(kuò)一個新的 Pod,另一種直接刪除 Pod 的方式是在要刪除的 Pod 上打 apps.kruise.io/specified-delete: true 標(biāo)簽。
相比于手動直接刪除 Pod,使用 podsToDelete 或 apps.kruise.io/specified-delete: true 方式會有 CloneSet 的 maxUnavailable/maxSurge 來保護(hù)刪除, 并且會觸發(fā) PreparingDelete 生命周期的鉤子。
升級
CloneSet 一共提供了 3 種升級方式:
ReCreate: 刪除舊 Pod 和它的 PVC,然后用新版本重新創(chuàng)建出來,這是默認(rèn)的方式InPlaceIfPossible: 會優(yōu)先嘗試原地升級 Pod,如果不行再采用重建升級InPlaceOnly: 只允許采用原地升級,因此,用戶只能修改上一條中的限制字段,如果嘗試修改其他字段會被拒絕
這里有一個重要概念:原地升級,這也是 OpenKruise 提供的核心功能之一,當(dāng)我們要升級一個 Pod 中鏡像的時候,下圖展示了重建升級和原地升級的區(qū)別:

重建升級時我們需要刪除舊 Pod、創(chuàng)建新 Pod:
Pod 名字和 uid 發(fā)生變化,因為它們是完全不同的兩個 Pod 對象(比如 Deployment 升級) Pod 名字可能不變、但 uid 變化,因為它們是不同的 Pod 對象,只是復(fù)用了同一個名字(比如 StatefulSet 升級) Pod 所在 Node 名字可能發(fā)生變化,因為新 Pod 很可能不會調(diào)度到之前所在的 Node 節(jié)點 Pod IP 發(fā)生變化,因為新 Pod 很大可能性是不會被分配到之前的 IP 地址
但是對于原地升級,我們?nèi)匀粡?fù)用同一個 Pod 對象,只是修改它里面的字段:
可以避免如調(diào)度、分配 IP、掛載盤等額外的操作和代價 更快的鏡像拉取,因為會復(fù)用已有舊鏡像的大部分 layer 層,只需要拉取新鏡像變化的一些 layer 當(dāng)一個容器在原地升級時,Pod 中的其他容器不會受到影響,仍然維持運行
所以顯然如果能用原地升級方式來升級我們的工作負(fù)載,對在線應(yīng)用的影響是最小的。上面我們提到 CloneSet 升級類型支持 InPlaceIfPossible,這意味著 Kruise 會盡量對 Pod 采取原地升級,如果不能則退化到重建升級,以下的改動會被允許執(zhí)行原地升級:
更新 workload 中的 spec.template.metadata.*,比如 labels/annotations,Kruise 只會將 metadata 中的改動更新到存量 Pod 上。更新 workload 中的 spec.template.spec.containers[x].image,Kruise 會原地升級 Pod 中這些容器的鏡像,而不會重建整個 Pod。從 Kruise v1.0 版本開始,更新 spec.template.metadata.labels/annotations并且 container 中有配置 env from 這些改動的labels/anntations,Kruise 會原地升級這些容器來生效新的 env 值。
否則,其他字段的改動,比如 spec.template.spec.containers[x].env 或 spec.template.spec.containers[x].resources,都是會回退為重建升級。
比如我們將上面的應(yīng)用升級方式設(shè)置為 InPlaceIfPossible,只需要在資源清單中添加 spec.updateStrategy.type: InPlaceIfPossible 即可:
apiVersion:?apps.kruise.io/v1alpha1
kind:?CloneSet
metadata:
??name:?cs-demo
spec:
??updateStrategy:
????type:?InPlaceIfPossible
??......
更新后可以發(fā)現(xiàn) Pod 的狀態(tài)并沒有發(fā)生什么大的變化,名稱、IP 都一樣,唯一變化的是鏡像 tag:
??kubectl?get?pods?-l?app=cs
NAME????????????READY???STATUS????RESTARTS??????AGE
cs-demo-8w7h4???1/1?????Running???4?(55m?ago)???3d6h
cs-demo-b6r6t???1/1?????Running???4?(55m?ago)???3d6h
cs-demo-fv5gb???1/1?????Running???5?(20s?ago)???3d6h
cs-demo-p4kmw???1/1?????Running???5?(83s?ago)???3d6h
??kubectl?describe?cloneset?cs-demo
Name:?????????cs-demo
Namespace:????default
Labels:???????
Annotations:??
API?Version:??apps.kruise.io/v1alpha1
Kind:?????????CloneSet
......
Events:
??Type????Reason??????????????????????Age????From?????????????????Message
??----????------??????????????????????----???----?????????????????-------
??Normal??SuccessfulDelete????????????4m44s??cloneset-controller??succeed?to?delete?pod?cs-demo-79rcx
??Normal??SuccessfulUpdatePodInPlace??97s????cloneset-controller??successfully?update?pod?cs-demo-p4kmw?in-place(revision?cs-demo-7cb9c88699)
??Normal??SuccessfulUpdatePodInPlace??34s????cloneset-controller??successfully?update?pod?cs-demo-fv5gb?in-place(revision?cs-demo-7cb9c88699)
??kubectl?describe?pod?cs-demo-p4kmw
......
Events:
??Type?????Reason??????????????????Age??????????????????From?????Message
??----?????------??????????????????----?????????????????----?????-------
??Normal???Pulled??????????????????56m??????????????????kubelet??Container?image?"nginx:alpine"?already?present?on?machine
??Normal???Created?????????????????2m28s?(x2?over?56m)??kubelet??Created?container?nginx
??Normal???Killing?????????????????2m28s????????????????kubelet??Container?nginx?definition?changed,?will?be?restarted
??Normal???Pulled??????????????????2m28s????????????????kubelet??Container?image?"nginx:1.7.9"?already?present?on?machine
??Normal???Started?????????????????2m27s?(x2?over?56m)??kubelet??Started?container?nginx
這就是原地升級的效果,原地升級整體工作流程如下圖所示:

如果你在安裝或升級 Kruise 的時候啟用了 PreDownloadImageForInPlaceUpdate 這個 feature-gate,CloneSet 控制器會自動在所有舊版本 pod 所在節(jié)點上預(yù)熱你正在灰度發(fā)布的新版本鏡像,這對于應(yīng)用發(fā)布加速很有幫助。
默認(rèn)情況下 CloneSet 每個新鏡像預(yù)熱時的并發(fā)度都是 1,也就是一個個節(jié)點拉鏡像,如果需要調(diào)整,你可以在 CloneSet annotation 上設(shè)置并發(fā)度:
apiVersion:?apps.kruise.io/v1alpha1
kind:?CloneSet
metadata:
??annotations:
????apps.kruise.io/image-predownload-parallelism:?"5"
注意,為了避免大部分不必要的鏡像拉取,目前只針對
replicas > 3的 CloneSet 做自動預(yù)熱。
此外 CloneSet 還支持分批進(jìn)行灰度,在 updateStrategy 屬性中可以配置 partition 參數(shù),該參數(shù)可以用來保留舊版本 Pod 的數(shù)量或百分比,默認(rèn)為0:
如果是數(shù)字,控制器會將 (replicas - partition)數(shù)量的 Pod 更新到最新版本如果是百分比,控制器會將 (replicas * (100% - partition))數(shù)量的 Pod 更新到最新版本
比如,我們將上面示例中的的 image 更新為 nginx:latest 并且設(shè)置 partition=2,更新后,過一會查看可以發(fā)現(xiàn)只升級了2個 Pod:
??kubectl?get?pods?-l?app=cs?-L?controller-revision-hash
NAME????????????READY???STATUS????RESTARTS??????AGE????CONTROLLER-REVISION-HASH
cs-demo-dx4lb???1/1?????Running???0?????????????69s????cs-demo-6599fc6cdd
cs-demo-fv5gb???1/1?????Running???0?????????????3d7h???cs-demo-7cb9c88699
cs-demo-nngtm???1/1?????Running???0?????????????8s?????cs-demo-6599fc6cdd
cs-demo-p4kmw???1/1?????Running???0?????????????3d6h???cs-demo-7cb9c88699
此外 CloneSet 還支持一些更高級的用法,比如可以定義優(yōu)先級策略來控制 Pod 發(fā)布的優(yōu)先級規(guī)則,還可以定義策略來將一類 Pod 打散到整個發(fā)布過程中,也可以暫停 Pod 發(fā)布等操作。
