新聞中心
K8s 增強(qiáng)版工作負(fù)載 OpenKruise 之 CloneSet
作者:k8s技術(shù)圈 2023-04-04 07:25:46
云計算
云原生 Kubernetes 集群中。Kubernetes 自身提供的一些應(yīng)用部署管理功能,對于大規(guī)模應(yīng)用與集群的場景這些功能是遠(yuǎn)遠(yuǎn)不夠的,OpenKruise 彌補(bǔ)了 Kubernetes 在應(yīng)用部署、升級、防護(hù)、運(yùn)維等領(lǐng)域的不足。

OpenKruise(https://openkruise.io) 是一個基于 Kubernetes 的擴(kuò)展套件,主要聚焦于云原生應(yīng)用的自動化,比如部署、發(fā)布、運(yùn)維以及可用性防護(hù)。OpenKruise 提供的絕大部分能力都是基于 CRD 擴(kuò)展來定義的,它們不存在于任何外部依賴,可以運(yùn)行在任意純凈的 Kubernetes 集群中。Kubernetes 自身提供的一些應(yīng)用部署管理功能,對于大規(guī)模應(yīng)用與集群的場景這些功能是遠(yuǎn)遠(yuǎn)不夠的,OpenKruise 彌補(bǔ)了 Kubernetes 在應(yīng)用部署、升級、防護(hù)、運(yùn)維等領(lǐng)域的不足。
OpenKruise 提供了以下的一些核心能力:
- 增強(qiáng)版本的 Workloads:OpenKruise 包含了一系列增強(qiáng)版本的工作負(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)刪除機(jī)制的干擾,包括 CRD、Namespace、以及幾乎全部的 Workloads 類型資源。相比于 Kubernetes 原生的 PDB 只提供針對 Pod Eviction 的防護(hù),PodUnavailableBudget 能夠防護(hù) Pod Deletion、Eviction、Update 等許多種 voluntary disruption 場景。
- 高級的應(yīng)用運(yùn)維能力:OpenKruise 也提供了很多高級的運(yùn)維能力來幫助你更好地管理應(yīng)用,比如可以通過 ImagePullJob 來在任意范圍的節(jié)點上預(yù)先拉取某些鏡像,或者指定某個 Pod 中的一個或多個容器被原地重啟。
架構(gòu)
下圖是 OpenKruise 的整體架構(gòu):
架構(gòu)
所有 OpenKruise 的功能都是通過 Kubernetes CRD 來提供的。其中 Kruise-manager 是一個運(yùn)行控制器和 webhook 的中心組件,它通過 Deployment 部署在 kruise-system 命名空間中。 從邏輯上來看,如 cloneset-controller、sidecarset-controller 這些的控制器都是獨立運(yùn)行的,不過為了減少復(fù)雜度,它們都被打包在一個獨立的二進(jìn)制文件、并運(yù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.3.0
該 charts 在模板中默認(rèn)定義了命名空間為 kruise-system,所以在安裝的時候可以不用指定,如果你的環(huán)境訪問 DockerHub 官方鏡像較慢,則可以使用下面的命令將鏡像替換成阿里云的鏡像:
helm upgrade --install kruise openkruise/kruise --set manager.image.repository=openkruise-registry.cn-shanghai.cr.aliyuncs.com/openkruise/kruise-manager --version 1.3.0
應(yīng)用部署完成后會在 kruise-system 命名空間下面運(yùn)行 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-7d78fc5c97-d6mbb 1/1 Running 0 52s
kruise-controller-manager-7d78fc5c97-wccbn 1/1 Running 0 52s
kruise-daemon-9f94k 1/1 Running 0 52s
kruise-daemon-bqj69 1/1 Running 0 52s
kruise-daemon-h95pf 1/1 Running 0 52s
如果不想使用默認(rèn)的參數(shù)進(jìn)行安裝,也可以自定義配置,可配置的 values 值可以參考 charts 文檔 https://github.com/openkruise/charts 進(jìn)行定制。
CloneSet
CloneSet 控制器是 OpenKruise 提供的對原生 Deployment 的增強(qiáng)控制器,在使用方式上和 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 0 0 3 8s
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 SuccessfulCreate 21s cloneset-controller succeed to create pod cs-demo-jfx5s
Normal SuccessfulCreate 21s cloneset-controller succeed to create pod cs-demo-kg9p2
Normal SuccessfulCreate 21s cloneset-controller succeed to create pod cs-demo-n72fr
該對象創(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-jfx5s 1/1 Running 0 58s
cs-demo-kg9p2 1/1 Running 0 58s
cs-demo-n72fr 1/1 Running 0 58s
CloneSet 雖然在使用上和 Deployment 比較類似,但還是有非常多比 Deployment 更高級的功能,下面我們來詳細(xì)介紹下。
擴(kuò)縮容
流式擴(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 4m25s cloneset-controller succeed to create pod cs-demo-jfx5s
Normal SuccessfulCreate 4m25s cloneset-controller succeed to create pod cs-demo-kg9p2
Normal SuccessfulCreate 4m25s cloneset-controller succeed to create pod cs-demo-n72fr
Warning ScaleUpLimited 66s cloneset-controller scaleUp is limited because of scaleStrategy.maxUnavailable, limit: 1
Normal SuccessfulCreate 66s cloneset-controller succeed to create pod cs-demo-x8ndf
Warning ScaleUpLimited 64s (x6 over 66s) cloneset-controller scaleUp is limited because of scaleStrategy.maxUnavailable, limit: 0
Normal SuccessfulCreate 5s cloneset-controller succeed to create pod cs-demo-2sfzz
可以看到第一時間擴(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-2sfzz 1/1 Running 0 22s
cs-demo-jfx5s 1/1 Running 0 4m42s
cs-demo-kg9p2 1/1 Running 0 4m42s
cs-demo-n72fr 1/1 Running 0 4m42s
cs-demo-x8ndf 1/1 Running 0 83s
當(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-n72fr
replicas: 4
......
更新上面的資源對象后,會將應(yīng)用縮到 4 個 Pod,如果在 podsToDelete 列表中指定了 Pod 名字,則控制器會優(yōu)先刪除這些 Pod,對于已經(jīng)被刪除的 Pod,控制器會自動從 podsToDelete 列表中清理掉。比如我們更新上面的資源對象后 cs-demo-n72fr 這個 Pod 會被移除,其余會保留下來:
kubectl get pods -l app=cs
NAME READY STATUS RESTARTS AGE
cs-demo-2sfzz 1/1 Running 0 61s
cs-demo-jfx5s 1/1 Running 0 5m21s
cs-demo-kg9p2 1/1 Running 0 5m21s
cs-demo-x8ndf 1/1 Running 0 2m2s
如果你只把 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 生命周期的鉤子。
PVC 模板
一個比較奇特的特性,CloneSet 允許用戶配置 PVC 模板 volumeClaimTemplates,用來給每個 Pod 生成獨享的 PVC,這是 Deployment 所不支持的,因為往往有狀態(tài)的應(yīng)用才需要單獨設(shè)置 PVC,在使用 CloneSet 的 PVC 模板的時候需要注意下面的這些事項:
- 每個被自動創(chuàng)建的 PVC 會有一個 ownerReference 指向 CloneSet,因此 CloneSet 被刪除時,它創(chuàng)建的所有 Pod 和 PVC 都會被刪除。
- 每個被 CloneSet 創(chuàng)建的 Pod 和 PVC,都會帶一個 apps.kruise.io/cloneset-instance-id: xxx 的 label。關(guān)聯(lián)的 Pod 和 PVC 會有相同的 instance-id,且它們的名字后綴都是這個 instance-id。
- 如果一個 Pod 被 CloneSet controller 縮容刪除時,這個 Pod 關(guān)聯(lián)的 PVC 都會被一起刪掉。
- 如果一個 Pod 被外部直接調(diào)用刪除或驅(qū)逐時,這個 Pod 關(guān)聯(lián)的 PVC 還都存在;并且 CloneSet controller 發(fā)現(xiàn)數(shù)量不足重新擴(kuò)容時,新擴(kuò)出來的 Pod 會復(fù)用原 Pod 的 instance-id 并關(guān)聯(lián)原來的 PVC。
- 當(dāng) Pod 被重建升級時,關(guān)聯(lián)的 PVC 會跟隨 Pod 一起被刪除、新建。
- 當(dāng) Pod 被原地升級時,關(guān)聯(lián)的 PVC 會持續(xù)使用。
以下是一個帶有 PVC 模板的例子:
# cloneset-pvc.yaml
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
labels:
app: sample
name: sample-data
spec:
replicas: 3
selector:
matchLabels:
app: sample
template:
metadata:
labels:
app: sample
spec:
containers:
- name: nginx
image: nginx:alpine
volumeMounts:
- name: data-vol
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data-vol
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
比如應(yīng)用上面的資源對象后會自動創(chuàng)建 3 個 Pod 和 3 個 PVC,每個 Pod 都會掛載一個 PVC:
kubectl get pods -l app=sample
NAME READY STATUS RESTARTS AGE
sample-data-t4vq6 0/1 Pending 0 2m13s
sample-data-vcjnl 0/1 Pending 0 2m13s
sample-data-znwjd 0/1 Pending 0 2m13s
kubectl get pvc -l app=sample
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-vol-sample-data-t4vq6 Pending 2m46s
data-vol-sample-data-vcjnl Pending 2m46s
data-vol-sample-data-znwjd Pending 2m46s
升級
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、掛載 volume 等額外的操作和代價
- 更快的鏡像拉取,因為會復(fù)用已有舊鏡像的大部分 layer 層,只需要拉取新鏡像變化的一些 layer
- 當(dāng)一個容器在原地升級時,Pod 中的其他容器不會受到影響,仍然維持運(yùn)行
所以顯然如果能用原地升級方式來升級我們的工作負(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
......
# image: nginx:1.7.9
更新后可以發(fā)現(xiàn) Pod 的狀態(tài)并沒有發(fā)生什么大的變化,名稱、IP 都一樣,唯一變化的是鏡像 tag:
kubectl get pods -l app=cs
NAME READY STATUS RESTARTS AGE
cs-demo-2sfzz 1/1 Running 1 (18s ago) 36m
cs-demo-jfx5s 1/1 Running 0 40m
cs-demo-kg9p2 1/1 Running 0 40m
cs-demo-x8ndf 1/1 Running 0 37m
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 SuccessfulUpdatePodInPlace 6m58s cloneset-controller successfully update pod cs-demo-2sfzz in-place(revision cs-demo-7cb9c88699)
Normal SuccessfulUpdatePodInPlace 5m46s cloneset-controller successfully update pod cs-demo-x8ndf in-place(revision cs-demo-7cb9c88699)
Normal SuccessfulUpdatePodInPlace 4m43s cloneset-controller successfully update pod cs-demo-kg9p2 in-place(revision cs-demo-7cb9c88699)
Normal SuccessfulUpdatePodInPlace 3m40s cloneset-controller successfully update pod cs-demo-jfx5s in-place(revision cs-demo-7cb9c88699)
kubectl describe pod cs-demo-2sfzz
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 44m default-scheduler Successfully assigned default/cs-demo-2sfzz to node2
Normal Pulled 44m kubelet Container image "nginx:alpine" already present on machine
Normal Killing 8m8s kubelet Container nginx definition changed, will be restarted
Normal Pulling 8m8s kubelet Pulling image "nginx:1.7.9"
Normal Created 7m58s (x2 over 44m) kubelet Created container nginx
Normal Started 7m58s (x2 over 44m) kubelet Started container nginx
Normal Pulled 7m58s kubelet Successfully pulled image "nginx:1.7.9" in 9.720841233s (9.720847295s including waiting)
這就是原地升級的效果,原地升級整體工作流程如下圖所示:
原地升級流程
如果你在安裝或升級 Kruise 的時候啟用了 PreDownloadImageForInPlaceUpdate 這個 feature-gate,CloneSet 控制器會自動在所有舊版本 pod 所在節(jié)點上預(yù)熱你正在灰度發(fā)布的新版本鏡像,這對于應(yīng)用發(fā)布加速很有幫助。
默認(rèn)情況下 CloneSet 每個新鏡像預(yù)熱時的并發(fā)度都是 1,也就是一個個節(jié)點拉鏡像,如果需要調(diào)整,你可以在 CloneSet 通過 apps.kruise.io/image-predownload-parallelism 這個 annotation 來設(shè)置并發(fā)度。
另外從 Kruise v1.1.0 開始,還可以使用 apps.kruise.io/image-predownload-min-updated-ready-pods 來控制在少量新版本 Pod 已經(jīng)升級成功之后再執(zhí)行鏡像預(yù)熱。它的值可能是絕對值數(shù)字或是百分比。
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
annotations:
apps.kruise.io/image-predownload-parallelism: "5"
apps.kruise.io/image-predownload-min-updated-ready-pods: "2"
注意,為了避免大部分不必要的鏡像拉取,目前只針對 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è)置 partitinotallow=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-2sfzz 1/1 Running 1 (11m ago) 47m cs-demo-7cb9c88699
cs-demo-jfx5s 1/1 Running 2 (99s ago) 52m cs-demo-7c4d79f5bc
cs-demo-kg9p2 1/1 Running 2 (27s ago) 52m cs-demo-7c4d79f5bc
cs-demo-x8ndf 1/1 Running 1 (10m ago) 48m cs-demo-7cb9c88699
kubectl get pods -o custom-columns='DATA:metadata.name,CONTAINERS:spec.containers[*].name,IMAGES:spec.containers[*].image' -l app=cs
DATA CONTAINERS IMAGES
cs-demo-2sfzz nginx nginx:1.7.9
cs-demo-jfx5s nginx nginx:latest
cs-demo-kg9p2 nginx nginx:latest
cs-demo-x8ndf nginx nginx:1.7.9
此外 CloneSet 還支持一些更高級的用法,比如可以定義優(yōu)先級策略來控制 Pod 發(fā)布的優(yōu)先級規(guī)則,還可以定義策略來將一類 Pod 打散到整個發(fā)布過程中,也可以暫停 Pod 發(fā)布等操作。
生命周期鉤子
每個 CloneSet 管理的 Pod 會有明確所處的狀態(tài),在 Pod label 中的 lifecycle.apps.kruise.io/state 標(biāo)記:
- Normal:正常狀態(tài)。
- PreparingUpdate:準(zhǔn)備原地升級。
- Updating:原地升級中。
- Updated:原地升級完成。
- PreparingDelete:準(zhǔn)備刪除。
而生命周期鉤子,則是通過在上述狀態(tài)流轉(zhuǎn)中卡點,來實現(xiàn)原地升級前后、刪除前的自定義操作(比如開關(guān)流量、告警等)。CloneSet 的 lifecycle 下面主要支持 preDelete 和 inPlaceUpdate 兩個屬性。
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
# 通過 finalizer 定義 hook
lifecycle:
preDelete: # PreDelete 是 Pod 被刪除之前的 hook
finalizersHandler:
- example.io/unready-blocker
inPlaceUpdate: # InPlaceUpdate 是 Pod 更新之前和更新后的 hook
finalizersHandler:
- example.io/unready-blocker
# 或者也可以通過 label 定義
lifecycle:
inPlaceUpdate:
labelsHandler:
example.io/block-unready: "true"
升級/刪除 Pod 前將其置為 NotReady
lifecycle:
preDelete:
markPodNotReady: true
finalizersHandler:
- example.io/unready-blocker
inPlaceUpdate:
markPodNotReady: true
finalizersHandler:
- example.io/unready-blocker
- 如果設(shè)置 preDelete.markPodNotReady=true:
- Kruise 將會在 Pod 進(jìn)入 PreparingDelete 狀態(tài)時,將 KruisePodReady 這個 Pod Condition 設(shè)置為 False, Pod 將變?yōu)?NotReady。
- 如果設(shè)置 inPlaceUpdate.markPodNotReady=true:
-
Kruise 將會在 Pod 進(jìn)入 PreparingUpdate 狀態(tài)時,將 KruisePodReady 這個 Pod Condition 設(shè)置為 False, Pod 將變?yōu)?NotReady。
-
Kruise 將會嘗試將 KruisePodReady 這個 Pod Condition 設(shè)置回 True。
我們可以利用這一特性,在容器真正被停止之前將 Pod 上的流量先行排除,防止流量損失。
流轉(zhuǎn)示意圖
生命周期示意圖
- 當(dāng) CloneSet 刪除一個 Pod(包括正??s容和重建升級)時:
- 如果沒有定義 lifecycle hook 或者 Pod 不符合 preDelete 條件,則直接刪除
- 否則,先只將 Pod 狀態(tài)改為 PreparingDelete。等用戶 controller 完成任務(wù)去掉 label/finalizer、Pod 不符合 preDelete 條件后,kruise 才執(zhí)行 Pod 刪除
- 需要注意的是 PreparingDelete 狀態(tài)的 Pod 處于刪除階段,不會被升級
- 當(dāng) CloneSet 原地升級一個 Pod 時:
-
升級之前,如果定義了 lifecycle hook 且 Pod 符合 inPlaceUpdate 條件,則將 Pod 狀態(tài)改為 PreparingUpdate
-
等用戶 controller 完成任務(wù)去掉 label/finalizer、Pod 不符合 inPlaceUpdate 條件后,kruise 將 Pod 狀態(tài)改為 Updating 并開始升級
-
升級完成后,如果定義了 lifecycle hook 且 Pod 不符合 inPlaceUpdate 條件,將 Pod 狀態(tài)改為 Updated
-
等用戶 controller 完成任務(wù)加上 label/finalizer、Pod 符合 inPlaceUpdate 條件后,kruise 將 Pod 狀態(tài)改為 Normal 并判斷為升級成功
關(guān)于從 PreparingDelete 回到 Normal 狀態(tài),從設(shè)計上是支持的(通過撤銷指定刪除),但我們一般不建議這種用法。由于 PreparingDelete 狀態(tài)的 Pod 不會被升級,當(dāng)回到 Normal 狀態(tài)后可能立即再進(jìn)入發(fā)布階段,對于用戶處理 hook 是一個難題。
用戶 controller 邏輯示例
按上述例子,可以定義:
- example.io/unready-blocker finalizer 作為 hook。
- example.io/initialing annotation 作為初始化標(biāo)記。
在 CloneSet template 模板里帶上這個字段:
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
template:
metadata:
annotations:
example.io/initialing: "true"
finalizers:
- example.io/unready-blocker
# ...
lifecycle:
preDelete:
finalizersHandler:
- example.io/unready-blocker
inPlaceUpdate:
finalizersHandler:
- example.io/unready-blocker
而后用戶 controller 的邏輯如下:
- 對于 Normal 狀態(tài)的 Pod,如果 annotation 中有 example.io/initialing: true 并且 Pod status 中的 ready condition 為 True,則接入流量、去除這個 annotation。
- 對于 PreparingDelete 和 PreparingUpdate 狀態(tài)的 Pod,切走流量,并去除 example.io/unready-blocker finalizer。
- 對于 Updated 狀態(tài)的 Pod,接入流量,并打上 example.io/unready-blocker finalizer。
使用場景
因為各種各樣的歷史原因和客觀因素,有些用戶可能無法將自己公司的整套體系架構(gòu) Kubernetes 化,比如有些用戶暫時無法使用 Kubernetes 本身提供的 Service 服務(wù)發(fā)現(xiàn)機(jī)制,而是使用了獨立于 Kubernetes 之外的另外一套服務(wù)注冊和發(fā)現(xiàn)體系。在這種架構(gòu)下,如果用戶對服務(wù)進(jìn)行 Kubernetes 化改造,可能會遇到諸多問題。例如,每當(dāng) Kubernetes 成功創(chuàng)建出一個 Pod,都需要自行將該 Pod 注冊到服務(wù)發(fā)現(xiàn)中心,以便能夠?qū)?nèi)對外提供服務(wù);相應(yīng)的,想要下線一個 Pod,也通常先要將其在服務(wù)發(fā)現(xiàn)中心刪除,才能將 Pod 優(yōu)雅下線,否則就可能導(dǎo)致流量損失。但是在原生的 Kubernetes 體系中, Pod 的生命周期由 Workload 管理(例如 Deployment),當(dāng)這些 Workload 的 Replicas 字段發(fā)生變化后,相應(yīng)的 Controller 會立即添加或刪除掉 Pod,用戶很難定制化地去管理 Pod 的生命周期。
面對這類問題,一般來說有兩種解決思路:一是約束 Kubernetes 的彈性能力,例如規(guī)定只能由特定的鏈路對 Workload 進(jìn)行擴(kuò)縮容,以保證在刪除 Pod 前先把 Pod IP 在服務(wù)注冊中心摘除,但這樣一來會制約 Kubernetes 本身的彈性能力, 并且也增加了鏈路管控的難度和風(fēng)險。 二是在根本上改造現(xiàn)有的服務(wù)發(fā)現(xiàn)體系,顯然這是一個更加漫長和高風(fēng)險的事情。
CloneSet生命周期改造
那么有沒有一種既能夠充分利用 Kubernetes 彈性能力,又避免對既有服務(wù)發(fā)現(xiàn)體系進(jìn)行改造,快速彌補(bǔ)兩個系統(tǒng)之間的間隙的方法呢?
OpenKruise CloneSet 就提供了這樣一組高度可定制化的擴(kuò)展能力來專門應(yīng)對此類場景,讓用戶能夠?qū)?Pod 生命周期做更精細(xì)化、定制化的管理。CloneSet 在 Pod 生命周期中幾個重要的時間節(jié)點預(yù)留了 Hook,使得用戶可以在這些時間節(jié)點插入一些定制化的擴(kuò)展動作。比如,在 Pod 升級前,將 Pod IP 在服務(wù)發(fā)現(xiàn)中心刪除,升級完成后再將 Pod IP 注冊到服務(wù)發(fā)現(xiàn)中心,或者做一些特殊的嗅探和監(jiān)控動作。
我們假設(shè)現(xiàn)在有這樣一個場景:
- 用戶不使用 Kubernetes Service 作為服務(wù)發(fā)現(xiàn)機(jī)制,服務(wù)發(fā)現(xiàn)體系完全獨立于 Kubernetes。
- 使用 CloneSet 作為 Kubernetes 工作負(fù)載。
并且對具體的需求做如下合理假設(shè):
- 當(dāng) Kubernetes Pod 被創(chuàng)建時:
- 在創(chuàng)建成功,且 Pod Ready 之后,將 Pod IP 注冊到服務(wù)發(fā)現(xiàn)中心。
- 當(dāng) Kubernetes Pod 原地升級時:
-
在升級之前,需要將 Pod IP 從服務(wù)發(fā)現(xiàn)中心刪除(或主動 FailOver)。
-
在升級完成,且 Pod Ready 之后,將 Pod IP 再次注冊到服務(wù)發(fā)現(xiàn)中心。
-
當(dāng) Kubernetes Pod 被刪除時:
-
在刪除之前,需要先將 Pod IP 從服務(wù)發(fā)現(xiàn)中心刪除。
基于以上假設(shè),其實我們就可以利用 CloneSet LifeCycle 來編寫一個簡單的 Operator 實現(xiàn)用戶定義的 Pod 生命周期管理機(jī)制。
前面我們提到了 CloneSet LifeCycle 將 Pod 的生命周期定義為了 5 種狀態(tài),5 種狀態(tài)之間的轉(zhuǎn)換邏輯由一個狀態(tài)機(jī)所控制。我們可以只選擇自己所關(guān)心的一種或多種,編寫一個獨立的 Operator 來實現(xiàn)這些狀態(tài)的轉(zhuǎn)換,控制 Pod 的生命周期,并在所關(guān)心的時間節(jié)點插入自己的定制化邏輯。
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
namespace: demo
n
文章名稱:K8s增強(qiáng)版工作負(fù)載OpenKruise之CloneSet
分享地址:http://m.fisionsoft.com.cn/article/cdcsddj.html


咨詢
建站咨詢
