一篇學(xué)會(huì)用 KEDA 根據(jù)工作負(fù)載進(jìn)行快速擴(kuò)容
歷史問題
眾所周知,Kubernetes 有個(gè)親生的 HPA 組件,在云原生早期,這個(gè)名義上的自動(dòng)擴(kuò)縮容的能力給 Kubernetes 贏得了不少掌聲。當(dāng)然現(xiàn)在回頭看看,僅僅根據(jù) CPU 和內(nèi)存這樣“貧瘠”的指標(biāo),不論是用于判斷負(fù)載水平,還是用于計(jì)算擴(kuò)容目標(biāo),都不是很夠用的。這個(gè)階段里,HPA 的擴(kuò)縮容效率也是廣受詬病的一個(gè)問題,在一個(gè)多級微服務(wù)調(diào)用的業(yè)務(wù)場景里,壓力是逐級傳遞的,下圖展示了一個(gè)常見情況:
圖片
如上圖,用戶流量進(jìn)入集群之后:
- 首先在 Deploy A 造成負(fù)載,指標(biāo)變化迫使 Deploy A 擴(kuò)容
- A 擴(kuò)容之后,吞吐量變大,B 受到壓力,再次采集到指標(biāo)變化,擴(kuò)容 Deploy B
- B 吞吐變大,C ..
這個(gè)逐級傳遞的過程不僅緩慢,而且可以說是步步驚心——每一級的擴(kuò)容都是直接被 CPU 或內(nèi)存的飆高觸發(fā)的,被“沖垮”的可能性是普遍存在的。這種被動(dòng)、滯后的方式,很明顯是有問題的。
推陳出新
造成 HPA 窘境的原因之一,就是“自掃門前雪”,每個(gè) Pod 都只能根據(jù)自身負(fù)載情況來進(jìn)行擴(kuò)縮容決策。如果能夠直接根據(jù)業(yè)務(wù)流量的變化進(jìn)行決策,并且將流量流經(jīng)的所有微服務(wù)進(jìn)行擴(kuò)縮容,看起來情況就會(huì)好很多了。HPA 的自定義指標(biāo)支持,給這個(gè)問題了一個(gè)可行的方案。該能力讓 HPA 可以用其它的指標(biāo)來作為擴(kuò)縮容的觸發(fā)器,例如我們可以用 Promethues 采集消息中間件的深度或者負(fù)載均衡器的隊(duì)列長度,作為一個(gè)更能如實(shí)反映業(yè)務(wù)流量的指標(biāo),直接用來觸發(fā)相關(guān)的多個(gè)微服務(wù)的擴(kuò)縮容,如下圖所示:
圖片
在上圖中:
- Prometheus 采集消息隊(duì)列和負(fù)載均衡等更能反映業(yè)務(wù)流量的指標(biāo)
- 使用 Prometheus Adapter 將 Promethues Metrics 轉(zhuǎn)換為 Kubernetes 的 Aggregated API
- HPA 使用自定義指標(biāo),同時(shí)對多個(gè)應(yīng)用進(jìn)行擴(kuò)縮容。
這中間涉及到的 Prometheus Adapter,通過配置文件完成步驟 2 的轉(zhuǎn)換:
- seriesQuery: '{__name__=~"^container_.*_total",container!="POD",namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
seriesFilters:
# since this is a superset of the query above, we introduce an additional filter here
- isNot: "^container_.*_seconds_total$"
name: {matches: "^container_(.*)_total$"}
metricsQuery: "sum(rate(<<.Series>>{<<.LabelMatchers>>,container!="POD"}[2m])) by (<<.GroupBy>>)"
當(dāng)然,完全可以自行實(shí)現(xiàn) Aggregated API 來支持這種指標(biāo)的采集和呈現(xiàn)工作。Prometheus 所提供的大量 Exporter 是吸引我們寫這種古怪語法的最大動(dòng)力。
那么如果是 KEDA 的話,這個(gè)問題又如何呢?KEDA 提供了幾十個(gè)被稱為 Scaler 的東西,其中除了 Promethues 之外,還包括 Kafka、Redis、PostgreSQL 等多種選擇。所以在很多場景中,無需 Promethues,也能使用 Scaler 完成對輸入指標(biāo)的讀取和判斷。下面用 KEDA 為例,看看這種伸縮方法的具體實(shí)現(xiàn)。
KEDA
假設(shè)一個(gè)容器化應(yīng)用由多個(gè)工作負(fù)載組成:
- Ingress:負(fù)責(zé)接收業(yè)務(wù)流量
- Backend 1、Backend 2:負(fù)責(zé)處理 Ingress 發(fā)來的任務(wù)
- Database:數(shù)據(jù)庫
我們希望達(dá)成的效果是 —— Ingress、Backend 1、Backend 2、Database,實(shí)例數(shù)量保持在 1:2:1.5:2 的關(guān)系,Keda 的大致流程如下圖所示:
圖片
首先使用 Helm 安裝 KEDA:
$ helm repo add kedacore https://kedacore.github.io/charts
$ helm install keda kedacore/keda --namespace default
NAME: keda
LAST DEPLOYED: Wed Nov 29 18:56:36 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
...
隨便創(chuàng)建幾個(gè)工作負(fù)載,冒充微服務(wù):
$ kubectl create deploy ingress --image=nginx
deployment.apps/ingress created
$ kubectl create deploy backend1 --image=nginx
deployment.apps/backend1 created
$ kubectl create deploy backend2 --image=nginx
deployment.apps/backend2 created
$ kubectl create deploy database --image=nginx
deployment.apps/database created
$ kubectl get pods | cut -d - -f 1 | grep -v keda | sort
...
backend1
backend2
database
ingress
運(yùn)行成功后,我們可以看到,四個(gè)微服務(wù),每個(gè)微服務(wù)都有一個(gè)實(shí)例。
按照剛才瞎掰的比例,編寫一個(gè) ScaleObject:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: bk1
spec:
scaleTargetRef:
name: backend1
triggers:
- type: kubernetes-workload
metadata:
podSelector: 'app=ingress'
value: '0.5'
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: bk2
spec:
scaleTargetRef:
name: backend2
triggers:
- type: kubernetes-workload
metadata:
podSelector: 'app=ingress'
value: '0.67'
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: db
spec:
scaleTargetRef:
name: database
triggers:
- type: kubernetes-workload
metadata:
podSelector: 'app=ingress'
value: '0.5'
上述代碼引入了 kubernetes-workload 類型的觸發(fā)器,他會(huì)監(jiān)控 app=ingress 的容器,并對 scaleTargetRef 中提到的工作負(fù)載數(shù)量比例進(jìn)行擴(kuò)縮容。
提交到集群之后,會(huì)看到實(shí)例數(shù)量數(shù)量發(fā)生了變化:
$ kubectl get pods | cut -d - -f 1 | sort | uniq --count
...
2 backend1
2 backend2
2 database
1 ingress
3 keda
我們把 Ingress 擴(kuò)容到 2 實(shí)例,再次統(tǒng)計(jì):
$ kubectl scale deployment ingress --replicas=2
deployment.apps/ingress scaled
$ kubectl get pods | cut -d - -f 1 | sort | uniq --count
...
4 backend1
3 backend2
4 database
2 ingress
3 keda
可以看到,的確是按照我們設(shè)定的比例,同步產(chǎn)生了縮放。如果縮減 Ingress 服務(wù)實(shí)例數(shù),幾分鐘之后,其它工作負(fù)載也會(huì)隨之縮容。
$ kubectl scale deployment ingress --replicas=1
deployment.apps/ingress scaled
$ kubectl get pods | cut -d - -f 1 | sort | uniq --count \
...
2 backend1
2 backend2
2 database
1 ingress
結(jié)論
雖說云原生架構(gòu)的復(fù)雜性問題越來越被強(qiáng)調(diào),但是這一生態(tài)的宗旨應(yīng)該還是沒有變化——用簡單的透明的手段解決復(fù)雜問題。