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

OpenKruise(https://openkruise.io) 是一個(gè)基于 Kubernetes 的擴(kuò)展套件,主要聚焦于云原生應(yīng)用的自動(dòng)化,比如部署、發(fā)布、運(yùn)維以及可用性防護(hù)。OpenKruise 提供的絕大部分能力都是基于 CRD 擴(kuò)展來定義的,它們不存在于任何外部依賴,可以運(yùn)行在任意純凈的 Kubernetes 集群中。Kubernetes 自身提供的一些應(yīng)用部署管理功能,對(duì)于大規(guī)模應(yīng)用與集群的場(chǎng)景這些功能是遠(yuǎn)遠(yuǎn)不夠的,OpenKruise 彌補(bǔ)了 Kubernetes 在應(yīng)用部署、升級(jí)、防護(hù)、運(yùn)維等領(lǐng)域的不足。
OpenKruise 提供了以下的一些核心能力:
- 增強(qiáng)版本的 Workloads:OpenKruise 包含了一系列增強(qiáng)版本的工作負(fù)載,比如 CloneSet、Advanced StatefulSet、Advanced DaemonSet、BroadcastJob 等。它們不僅支持類似于 Kubernetes 原生 Workloads 的基礎(chǔ)功能,還提供了如原地升級(jí)、可配置的擴(kuò)縮容/發(fā)布策略、并發(fā)操作等。其中,原地升級(jí)是一種升級(jí)應(yīng)用容器鏡像甚至環(huán)境變量的全新方式,它只會(huì)用新的鏡像重建 Pod 中的特定容器,整個(gè) Pod 以及其中的其他容器都不會(huì)被影響。因此它帶來了更快的發(fā)布速度,以及避免了對(duì)其他 Scheduler、CNI、CSI 等組件的負(fù)面影響。
- 應(yīng)用的旁路管理:OpenKruise 提供了多種通過旁路管理應(yīng)用 sidecar 容器、多區(qū)域部署的方式,旁路意味著你可以不需要修改應(yīng)用的 Workloads 來實(shí)現(xiàn)它們。比如,SidecarSet 能幫助你在所有匹配的 Pod 創(chuàng)建的時(shí)候都注入特定的 sidecar 容器,甚至可以原地升級(jí)已經(jīng)注入的 sidecar 容器鏡像、并且對(duì) Pod 中其他容器不造成影響。而 WorkloadSpread 可以約束無狀態(tài) Workload 擴(kuò)容出來 Pod 的區(qū)域分布,賦予單一 workload 的多區(qū)域和彈性部署的能力。
- 高可用性防護(hù):OpenKruise 可以保護(hù)你的 Kubernetes 資源不受級(jí)聯(lián)刪除機(jī)制的干擾,包括 CRD、Namespace、以及幾乎全部的 Workloads 類型資源。相比于 Kubernetes 原生的 PDB 只提供針對(duì) Pod Eviction 的防護(hù),PodUnavailableBudget 能夠防護(hù) Pod Deletion、Eviction、Update 等許多種 voluntary disruption 場(chǎng)景。
- 高級(jí)的應(yīng)用運(yùn)維能力:OpenKruise 也提供了很多高級(jí)的運(yùn)維能力來幫助你更好地管理應(yīng)用,比如可以通過 ImagePullJob 來在任意范圍的節(jié)點(diǎn)上預(yù)先拉取某些鏡像,或者指定某個(gè) Pod 中的一個(gè)或多個(gè)容器被原地重啟。
架構(gòu)
下圖是 OpenKruise 的整體架構(gòu):
架構(gòu)
所有 OpenKruise 的功能都是通過 Kubernetes CRD 來提供的。其中 Kruise-manager 是一個(gè)運(yùn)行控制器和 webhook 的中心組件,它通過 Deployment 部署在 kruise-system 命名空間中。 從邏輯上來看,如 cloneset-controller、sidecarset-controller 這些的控制器都是獨(dú)立運(yùn)行的,不過為了減少?gòu)?fù)雜度,它們都被打包在一個(gè)獨(dú)立的二進(jìn)制文件、并運(yùn)行在 kruise-controller-manager-xxx 這個(gè) Pod 中。除了控制器之外,kruise-controller-manager-xxx 中還包含了針對(duì) Kruise CRD 以及 Pod 資源的 admission webhook。Kruise-manager 會(huì)創(chuàng)建一些 webhook configurations 來配置哪些資源需要感知處理、以及提供一個(gè) Service 來給 kube-apiserver 調(diào)用。
從 v0.8.0 版本開始提供了一個(gè)新的 Kruise-daemon 組件,它通過 DaemonSet 部署到每個(gè)節(jié)點(diǎn)上,提供鏡像預(yù)熱、容器重啟等功能。
安裝
這里我們同樣還是使用 Helm 方式來進(jìn)行安裝,需要注意從 v1.0.0 開始,OpenKruise 要求在 Kubernetes >= 1.16 以上版本的集群中安裝和使用。
首先添加 charts 倉(cāng)庫:
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,所以在安裝的時(shí)候可以不用指定,如果你的環(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)用部署完成后會(huì)在 kruise-system 命名空間下面運(yùn)行 2 個(gè) kruise-manager 的 Pod,同樣它們之間采用 leader-election 的方式選主,同一時(shí)間只有一個(gè)提供服務(wù),達(dá)到高可用的目的,此外還會(huì)以 DaemonSet 的形式啟動(dòng) 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 提供的對(duì)原生 Deployment 的增強(qiáng)控制器,在使用方式上和 Deployment 幾乎一致,如下所示是我們聲明的一個(gè) CloneSet 資源對(duì)象:
# 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)建上面的這個(gè) CloneSet 對(duì)象:
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
該對(duì)象創(chuàng)建完成后我們可以通過 kubectl describe 命令查看對(duì)應(yīng)的 Events 信息,可以發(fā)現(xiàn) cloneset-controller 是直接創(chuàng)建的 Pod,這個(gè)和原生的 Deployment 就有一些區(qū)別了,Deployment 是通過 ReplicaSet 去創(chuàng)建的 Pod,所以從這里也可以看出來 CloneSet 是直接管理 Pod 的,3 個(gè)副本的 Pod 此時(shí)也創(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 更高級(jí)的功能,下面我們來詳細(xì)介紹下。
擴(kuò)縮容
流式擴(kuò)容
CloneSet 在擴(kuò)容的時(shí)候可以通過 ScaleStrategy.MaxUnavailable 來限制擴(kuò)容的步長(zhǎng),這樣可以對(duì)服務(wù)應(yīng)用的影響最小,可以設(shè)置一個(gè)絕對(duì)值或百分比,如果不設(shè)置該值,則表示不限制。
比如我們?cè)谏厦娴馁Y源清單中添加如下所示數(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ò)容時(shí),只有當(dāng)上一個(gè)擴(kuò)容出的 Pod 已經(jīng) Ready 超過一分鐘后,CloneSet 才會(huì)執(zhí)行創(chuàng)建下一個(gè) Pod,比如這里我們擴(kuò)容成 5 個(gè)副本,更新上面對(duì)象后查看 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
可以看到第一時(shí)間擴(kuò)容了一個(gè) Pod,由于我們配置了 minReadySeconds: 60,也就是新擴(kuò)容的 Pod 創(chuàng)建成功超過 1 分鐘后才會(huì)擴(kuò)容另外一個(gè) Pod,上面的 Events 信息也能表現(xiàn)出來,查看 Pod 的 AGE 也能看出來擴(kuò)容的 2 個(gè) 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 被縮容時(shí),我們還可以指定一些 Pod 來刪除,這對(duì)于 StatefulSet 或者 Deployment 來說是無法實(shí)現(xiàn)的, StatefulSet 是根據(jù)序號(hào)來刪除 Pod,而 Deployment/ReplicaSet 目前只能根據(jù)控制器里定義的排序來刪除。而 CloneSet 允許用戶在縮小 replicas 數(shù)量的同時(shí),指定想要?jiǎng)h除的 Pod 名字,如下所示:
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
name: cs-demo
spec:
minReadySeconds: 60
scaleStrategy:
maxUnavailable: 1
podsToDelete:
- cs-demo-n72fr
replicas: 4
......
更新上面的資源對(duì)象后,會(huì)將應(yīng)用縮到 4 個(gè) Pod,如果在 podsToDelete 列表中指定了 Pod 名字,則控制器會(huì)優(yōu)先刪除這些 Pod,對(duì)于已經(jīng)被刪除的 Pod,控制器會(huì)自動(dòng)從 podsToDelete 列表中清理掉。比如我們更新上面的資源對(duì)象后 cs-demo-n72fr 這個(gè) Pod 會(huì)被移除,其余會(huì)保留下來:
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ù)量,那么控制器會(huì)先把指定的 Pod 刪掉,然后再擴(kuò)一個(gè)新的 Pod,另一種直接刪除 Pod 的方式是在要?jiǎng)h除的 Pod 上打 apps.kruise.io/specified-delete: true 標(biāo)簽。
相比于手動(dòng)直接刪除 Pod,使用 podsToDelete 或 apps.kruise.io/specified-delete: true 方式會(huì)有 CloneSet 的 maxUnavailable/maxSurge 來保護(hù)刪除, 并且會(huì)觸發(fā) PreparingDelete 生命周期的鉤子。
PVC 模板
一個(gè)比較奇特的特性,CloneSet 允許用戶配置 PVC 模板 volumeClaimTemplates,用來給每個(gè) Pod 生成獨(dú)享的 PVC,這是 Deployment 所不支持的,因?yàn)橥袪顟B(tài)的應(yīng)用才需要單獨(dú)設(shè)置 PVC,在使用 CloneSet 的 PVC 模板的時(shí)候需要注意下面的這些事項(xiàng):
- 每個(gè)被自動(dòng)創(chuàng)建的 PVC 會(huì)有一個(gè) ownerReference 指向 CloneSet,因此 CloneSet 被刪除時(shí),它創(chuàng)建的所有 Pod 和 PVC 都會(huì)被刪除。
- 每個(gè)被 CloneSet 創(chuàng)建的 Pod 和 PVC,都會(huì)帶一個(gè) apps.kruise.io/cloneset-instance-id: xxx 的 label。關(guān)聯(lián)的 Pod 和 PVC 會(huì)有相同的 instance-id,且它們的名字后綴都是這個(gè) instance-id。
- 如果一個(gè) Pod 被 CloneSet controller 縮容刪除時(shí),這個(gè) Pod 關(guān)聯(lián)的 PVC 都會(huì)被一起刪掉。
- 如果一個(gè) Pod 被外部直接調(diào)用刪除或驅(qū)逐時(shí),這個(gè) Pod 關(guān)聯(lián)的 PVC 還都存在;并且 CloneSet controller 發(fā)現(xiàn)數(shù)量不足重新擴(kuò)容時(shí),新擴(kuò)出來的 Pod 會(huì)復(fù)用原 Pod 的 instance-id 并關(guān)聯(lián)原來的 PVC。
- 當(dāng) Pod 被重建升級(jí)時(shí),關(guān)聯(lián)的 PVC 會(huì)跟隨 Pod 一起被刪除、新建。
- 當(dāng) Pod 被原地升級(jí)時(shí),關(guān)聯(lián)的 PVC 會(huì)持續(xù)使用。
以下是一個(gè)帶有 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)用上面的資源對(duì)象后會(huì)自動(dòng)創(chuàng)建 3 個(gè) Pod 和 3 個(gè) PVC,每個(gè) Pod 都會(huì)掛載一個(gè) 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
升級(jí)
CloneSet 一共提供了 3 種升級(jí)方式:
- ReCreate: 刪除舊 Pod 和它的 PVC,然后用新版本重新創(chuàng)建出來,這是默認(rèn)的方式。
- InPlaceIfPossible: 會(huì)優(yōu)先嘗試原地升級(jí) Pod,如果不行再采用重建升級(jí)。
- InPlaceOnly: 只允許采用原地升級(jí),因此,用戶只能修改上一條中的限制字段,如果嘗試修改其他字段會(huì)被拒絕。
這里有一個(gè)重要概念:原地升級(jí),這也是 OpenKruise 提供的核心功能之一,當(dāng)我們要升級(jí)一個(gè) Pod 中鏡像的時(shí)候,下圖展示了重建升級(jí)和原地升級(jí)的區(qū)別:
原地升級(jí)
重建升級(jí)時(shí)我們需要?jiǎng)h除舊 Pod、創(chuàng)建新 Pod:
- Pod 名字和 uid 發(fā)生變化,因?yàn)樗鼈兪峭耆煌膬蓚€(gè) Pod 對(duì)象(比如 Deployment 升級(jí))
- Pod 名字可能不變、但 uid 變化,因?yàn)樗鼈兪遣煌?Pod 對(duì)象,只是復(fù)用了同一個(gè)名字(比如 StatefulSet 升級(jí))
- Pod 所在 Node 名字可能發(fā)生變化,因?yàn)樾?Pod 很可能不會(huì)調(diào)度到之前所在的 Node 節(jié)點(diǎn)
- Pod IP 發(fā)生變化,因?yàn)樾?Pod 很大可能性是不會(huì)被分配到之前的 IP 地址
但是對(duì)于原地升級(jí),我們?nèi)匀粡?fù)用同一個(gè) Pod 對(duì)象,只是修改它里面的字段:
- 可以避免如調(diào)度、分配 IP、掛載 volume 等額外的操作和代價(jià)
- 更快的鏡像拉取,因?yàn)闀?huì)復(fù)用已有舊鏡像的大部分 layer 層,只需要拉取新鏡像變化的一些 layer
- 當(dāng)一個(gè)容器在原地升級(jí)時(shí),Pod 中的其他容器不會(huì)受到影響,仍然維持運(yùn)行
所以顯然如果能用原地升級(jí)方式來升級(jí)我們的工作負(fù)載,對(duì)在線應(yīng)用的影響是最小的。上面我們提到 CloneSet 升級(jí)類型支持 InPlaceIfPossible,這意味著 Kruise 會(huì)盡量對(duì) Pod 采取原地升級(jí),如果不能則退化到重建升級(jí),以下的改動(dòng)會(huì)被允許執(zhí)行原地升級(jí):
- 更新 workload 中的 spec.template.metadata.*,比如 labels/annotations,Kruise 只會(huì)將 metadata 中的改動(dòng)更新到存量 Pod 上。
- 更新 workload 中的 spec.template.spec.containers[x].image,Kruise 會(huì)原地升級(jí) Pod 中這些容器的鏡像,而不會(huì)重建整個(gè) Pod。
- 從 Kruise v1.0 版本開始,更新 spec.template.metadata.labels/annotations 并且 container 中有配置 env from 這些改動(dòng)的 labels/anntations,Kruise 會(huì)原地升級(jí)這些容器來生效新的 env 值。
否則,其他字段的改動(dòng),比如 spec.template.spec.containers[x].env? 或 spec.template.spec.containers[x].resources,都是會(huì)回退為重建升級(jí)。
比如我們將上面的應(yīng)用升級(jí)方式設(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)
這就是原地升級(jí)的效果,原地升級(jí)整體工作流程如下圖所示:
原地升級(jí)流程
如果你在安裝或升級(jí) Kruise 的時(shí)候啟用了 PreDownloadImageForInPlaceUpdate 這個(gè) feature-gate,CloneSet 控制器會(huì)自動(dòng)在所有舊版本 pod 所在節(jié)點(diǎn)上預(yù)熱你正在灰度發(fā)布的新版本鏡像,這對(duì)于應(yīng)用發(fā)布加速很有幫助。
默認(rèn)情況下 CloneSet 每個(gè)新鏡像預(yù)熱時(shí)的并發(fā)度都是 1,也就是一個(gè)個(gè)節(jié)點(diǎn)拉鏡像,如果需要調(diào)整,你可以在 CloneSet 通過 apps.kruise.io/image-predownload-parallelism 這個(gè) annotation 來設(shè)置并發(fā)度。
另外從 Kruise v1.1.0 開始,還可以使用 apps.kruise.io/image-predownload-min-updated-ready-pods 來控制在少量新版本 Pod 已經(jīng)升級(jí)成功之后再執(zhí)行鏡像預(yù)熱。它的值可能是絕對(duì)值數(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"
注意,為了避免大部分不必要的鏡像拉取,目前只針對(duì) replicas > 3 的 CloneSet 做自動(dòng)預(yù)熱。
此外 CloneSet 還支持分批進(jìn)行灰度,在 updateStrategy? 屬性中可以配置 partition 參數(shù),該參數(shù)可以用來保留舊版本 Pod 的數(shù)量或百分比,默認(rèn)為 0:
- 如果是數(shù)字,控制器會(huì)將 (replicas - partition) 數(shù)量的 Pod 更新到最新版本
- 如果是百分比,控制器會(huì)將 (replicas * (100% - partition)) 數(shù)量的 Pod 更新到最新版本
比如,我們將上面示例中的的 image 更新為 nginx:latest? 并且設(shè)置 partitinotallow=2,更新后,過一會(huì)查看可以發(fā)現(xiàn)只升級(jí)了 2 個(gè) 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 還支持一些更高級(jí)的用法,比如可以定義優(yōu)先級(jí)策略來控制 Pod 發(fā)布的優(yōu)先級(jí)規(guī)則,還可以定義策略來將一類 Pod 打散到整個(gè)發(fā)布過程中,也可以暫停 Pod 發(fā)布等操作。
生命周期鉤子
每個(gè) CloneSet 管理的 Pod 會(huì)有明確所處的狀態(tài),在 Pod label 中的 lifecycle.apps.kruise.io/state 標(biāo)記:
- Normal:正常狀態(tài)。
- PreparingUpdate:準(zhǔn)備原地升級(jí)。
- Updating:原地升級(jí)中。
- Updated:原地升級(jí)完成。
- PreparingDelete:準(zhǔn)備刪除。
而生命周期鉤子,則是通過在上述狀態(tài)流轉(zhuǎn)中卡點(diǎn),來實(shí)現(xiàn)原地升級(jí)前后、刪除前的自定義操作(比如開關(guān)流量、告警等)。CloneSet 的 lifecycle 下面主要支持 preDelete 和 inPlaceUpdate 兩個(gè)屬性。
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"
升級(jí)/刪除 Pod 前將其置為 NotReady
lifecycle:
preDelete:
markPodNotReady: true
finalizersHandler:
- example.io/unready-blocker
inPlaceUpdate:
markPodNotReady: true
finalizersHandler:
- example.io/unready-blocker
- 如果設(shè)置 preDelete.markPodNotReady=true:
- Kruise 將會(huì)在 Pod 進(jìn)入 PreparingDelete 狀態(tài)時(shí),將 KruisePodReady 這個(gè) Pod Condition 設(shè)置為 False, Pod 將變?yōu)?NotReady。
- 如果設(shè)置 inPlaceUpdate.markPodNotReady=true:
-
Kruise 將會(huì)在 Pod 進(jìn)入 PreparingUpdate 狀態(tài)時(shí),將 KruisePodReady 這個(gè) Pod Condition 設(shè)置為 False, Pod 將變?yōu)?NotReady。
-
Kruise 將會(huì)嘗試將 KruisePodReady 這個(gè) Pod Condition 設(shè)置回 True。
我們可以利用這一特性,在容器真正被停止之前將 Pod 上的流量先行排除,防止流量損失。
流轉(zhuǎn)示意圖
生命周期示意圖
- 當(dāng) CloneSet 刪除一個(gè) Pod(包括正??s容和重建升級(jí))時(shí):
- 如果沒有定義 lifecycle hook 或者 Pod 不符合 preDelete 條件,則直接刪除
- 否則,先只將 Pod 狀態(tài)改為 PreparingDelete。等用戶 controller 完成任務(wù)去掉 label/finalizer、Pod 不符合 preDelete 條件后,kruise 才執(zhí)行 Pod 刪除
- 需要注意的是 PreparingDelete 狀態(tài)的 Pod 處于刪除階段,不會(huì)被升級(jí)
- 當(dāng) CloneSet 原地升級(jí)一個(gè) Pod 時(shí):
-
升級(jí)之前,如果定義了 lifecycle hook 且 Pod 符合 inPlaceUpdate 條件,則將 Pod 狀態(tài)改為 PreparingUpdate
-
等用戶 controller 完成任務(wù)去掉 label/finalizer、Pod 不符合 inPlaceUpdate 條件后,kruise 將 Pod 狀態(tài)改為 Updating 并開始升級(jí)
-
升級(jí)完成后,如果定義了 lifecycle hook 且 Pod 不符合 inPlaceUpdate 條件,將 Pod 狀態(tài)改為 Updated
-
等用戶 controller 完成任務(wù)加上 label/finalizer、Pod 符合 inPlaceUpdate 條件后,kruise 將 Pod 狀態(tài)改為 Normal 并判斷為升級(jí)成功
關(guān)于從 PreparingDelete 回到 Normal 狀態(tài),從設(shè)計(jì)上是支持的(通過撤銷指定刪除),但我們一般不建議這種用法。由于 PreparingDelete 狀態(tài)的 Pod 不會(huì)被升級(jí),當(dāng)回到 Normal 狀態(tài)后可能立即再進(jìn)入發(fā)布階段,對(duì)于用戶處理 hook 是一個(gè)難題。
用戶 controller 邏輯示例
按上述例子,可以定義:
- example.io/unready-blocker finalizer 作為 hook。
- example.io/initialing annotation 作為初始化標(biāo)記。
在 CloneSet template 模板里帶上這個(gè)字段:
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 的邏輯如下:
- 對(duì)于 Normal 狀態(tài)的 Pod,如果 annotation 中有 example.io/initialing: true 并且 Pod status 中的 ready condition 為 True,則接入流量、去除這個(gè) annotation。
- 對(duì)于 PreparingDelete 和 PreparingUpdate 狀態(tài)的 Pod,切走流量,并去除 example.io/unready-blocker finalizer。
- 對(duì)于 Updated 狀態(tài)的 Pod,接入流量,并打上 example.io/unready-blocker finalizer。
使用場(chǎng)景
因?yàn)楦鞣N各樣的歷史原因和客觀因素,有些用戶可能無法將自己公司的整套體系架構(gòu) Kubernetes 化,比如有些用戶暫時(shí)無法使用 Kubernetes 本身提供的 Service 服務(wù)發(fā)現(xiàn)機(jī)制,而是使用了獨(dú)立于 Kubernetes 之外的另外一套服務(wù)注冊(cè)和發(fā)現(xiàn)體系。在這種架構(gòu)下,如果用戶對(duì)服務(wù)進(jìn)行 Kubernetes 化改造,可能會(huì)遇到諸多問題。例如,每當(dāng) Kubernetes 成功創(chuàng)建出一個(gè) Pod,都需要自行將該 Pod 注冊(cè)到服務(wù)發(fā)現(xiàn)中心,以便能夠?qū)?nèi)對(duì)外提供服務(wù);相應(yīng)的,想要下線一個(gè) Pod,也通常先要將其在服務(wù)發(fā)現(xiàn)中心刪除,才能將 Pod 優(yōu)雅下線,否則就可能導(dǎo)致流量損失。但是在原生的 Kubernetes 體系中, Pod 的生命周期由 Workload 管理(例如 Deployment),當(dāng)這些 Workload 的 Replicas 字段發(fā)生變化后,相應(yīng)的 Controller 會(huì)立即添加或刪除掉 Pod,用戶很難定制化地去管理 Pod 的生命周期。
面對(duì)這類問題,一般來說有兩種解決思路:一是約束 Kubernetes 的彈性能力,例如規(guī)定只能由特定的鏈路對(duì) Workload 進(jìn)行擴(kuò)縮容,以保證在刪除 Pod 前先把 Pod IP 在服務(wù)注冊(cè)中心摘除,但這樣一來會(huì)制約 Kubernetes 本身的彈性能力, 并且也增加了鏈路管控的難度和風(fēng)險(xiǎn)。 二是在根本上改造現(xiàn)有的服務(wù)發(fā)現(xiàn)體系,顯然這是一個(gè)更加漫長(zhǎng)和高風(fēng)險(xiǎn)的事情。
CloneSet生命周期改造
那么有沒有一種既能夠充分利用 Kubernetes 彈性能力,又避免對(duì)既有服務(wù)發(fā)現(xiàn)體系進(jìn)行改造,快速?gòu)浹a(bǔ)兩個(gè)系統(tǒng)之間的間隙的方法呢?
OpenKruise CloneSet 就提供了這樣一組高度可定制化的擴(kuò)展能力來專門應(yīng)對(duì)此類場(chǎng)景,讓用戶能夠?qū)?Pod 生命周期做更精細(xì)化、定制化的管理。CloneSet 在 Pod 生命周期中幾個(gè)重要的時(shí)間節(jié)點(diǎn)預(yù)留了 Hook,使得用戶可以在這些時(shí)間節(jié)點(diǎn)插入一些定制化的擴(kuò)展動(dòng)作。比如,在 Pod 升級(jí)前,將 Pod IP 在服務(wù)發(fā)現(xiàn)中心刪除,升級(jí)完成后再將 Pod IP 注冊(cè)到服務(wù)發(fā)現(xiàn)中心,或者做一些特殊的嗅探和監(jiān)控動(dòng)作。
我們假設(shè)現(xiàn)在有這樣一個(gè)場(chǎng)景:
- 用戶不使用 Kubernetes Service 作為服務(wù)發(fā)現(xiàn)機(jī)制,服務(wù)發(fā)現(xiàn)體系完全獨(dú)立于 Kubernetes。
- 使用 CloneSet 作為 Kubernetes 工作負(fù)載。
并且對(duì)具體的需求做如下合理假設(shè):
- 當(dāng) Kubernetes Pod 被創(chuàng)建時(shí):
- 在創(chuàng)建成功,且 Pod Ready 之后,將 Pod IP 注冊(cè)到服務(wù)發(fā)現(xiàn)中心。
- 當(dāng) Kubernetes Pod 原地升級(jí)時(shí):
-
在升級(jí)之前,需要將 Pod IP 從服務(wù)發(fā)現(xiàn)中心刪除(或主動(dòng) FailOver)。
-
在升級(jí)完成,且 Pod Ready 之后,將 Pod IP 再次注冊(cè)到服務(wù)發(fā)現(xiàn)中心。
-
當(dāng) Kubernetes Pod 被刪除時(shí):
-
在刪除之前,需要先將 Pod IP 從服務(wù)發(fā)現(xiàn)中心刪除。
基于以上假設(shè),其實(shí)我們就可以利用 CloneSet LifeCycle 來編寫一個(gè)簡(jiǎn)單的 Operator 實(shí)現(xiàn)用戶定義的 Pod 生命周期管理機(jī)制。
前面我們提到了 CloneSet LifeCycle 將 Pod 的生命周期定義為了 5 種狀態(tài),5 種狀態(tài)之間的轉(zhuǎn)換邏輯由一個(gè)狀態(tài)機(jī)所控制。我們可以只選擇自己所關(guān)心的一種或多種,編寫一個(gè)獨(dú)立的 Operator 來實(shí)現(xiàn)這些狀態(tài)的轉(zhuǎn)換,控制 Pod 的生命周期,并在所關(guān)心的時(shí)間節(jié)點(diǎn)插入自己的定制化邏輯。
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
namespace: demo
n
本文名稱:K8s增強(qiáng)版工作負(fù)載OpenKruise之CloneSet
文章鏈接:http://www.5511xx.com/article/cdcsddj.html


咨詢
建站咨詢
