使用 Loki、Kubernetes 和 Golang 在生產(chǎn)環(huán)境中進(jìn)行負(fù)載測試

12月26日至2月初這段時(shí)間是英國節(jié)假日交易活動(dòng)增加的時(shí)期之一,在 loveholidays 這屬于高峰期。在高峰期,loveholidays.com 的吞吐量超過平均水平的10倍以上。為了確保我們的服務(wù)能夠承受負(fù)載,我們通過將生產(chǎn)環(huán)境訪問日志的流量以原吞吐量的倍數(shù)重放到我們的 staging 和生產(chǎn)環(huán)境來不斷測試它們。負(fù)載測試會(huì)在晚上針對生產(chǎn)環(huán)境運(yùn)行,因?yàn)榇藭r(shí)英國和愛爾蘭的流量較少,我們在晚間針對生產(chǎn)環(huán)境執(zhí)行測試的系統(tǒng)是圍繞 Grafana Loki、Kubernetes CronJob 和我們開源的一個(gè)名為 ripley 的 HTTP 流量重放工具構(gòu)建的,我們稱這個(gè)系統(tǒng)為 Owlbot。

可重放的訪問日志
通過使用訪問日志重放流量,可以真實(shí)地了解請求的分布情況,例如有多少用戶點(diǎn)擊了主頁與搜索結(jié)果頁面,或者搜索所有目的地的用戶與只搜索馬略卡島度假的用戶的比例。由于不同類型請求之間的這種分布會(huì)影響性能,使用腳本合成負(fù)載測試來復(fù)制真實(shí)流量可能會(huì)更加困難。
我們將訪問日志與我們所有的服務(wù)日志一起存儲(chǔ)在 Grafana Loki 中以提高效率,也因?yàn)樗?Prometheus/Grafana 生態(tài)系統(tǒng)的原生項(xiàng)目,這是我們監(jiān)控堆棧的一個(gè)組成部分,所以我們使用 Loki,而不是其他定制解決方案,這符合我們專注于差異化工程的原則,以及專注于簡單性的原則,只需要最小的配置,沒有中間系統(tǒng),例如 GCS,我們在該系統(tǒng)的早期版本上嘗試過。
將我們所有的訪問日志存儲(chǔ)在 Loki 中,還可以捕獲到性能下降的時(shí)期或?qū)е轮袛嗟氖录@樣我們就可以重放它們來證明我們的后續(xù)改進(jìn)工作。
我們使用來自 NGINX 的訪問日志,這是我們生產(chǎn)集群的入口點(diǎn),在收集這些日志時(shí),我們會(huì)排除一些敏感數(shù)據(jù),比如個(gè)人身份信息等。
使用 Ripley 重放訪問日志
Ripley 是我們編寫的一個(gè) Go 工具,靈感來自 Vegeta HTTP 負(fù)載測試工具,其他負(fù)載測試工具通常以配置的速率生成負(fù)載,例如每秒100個(gè)請求,這種恒定的負(fù)載并不能準(zhǔn)確地代表用戶行為。默認(rèn)情況下,ripley 以與生產(chǎn)中發(fā)生的請求完全相同的速率進(jìn)行復(fù)制,它還允許以錄制速率的倍數(shù)進(jìn)行快速(或慢速)重放,這更接近于自然流量的行為,在 loveholidays.com 的案例中,自然流量通常不是突然爆發(fā)的,這種真實(shí)的流量模擬對于調(diào)整 Kubernetes 的 Horizontal Pod Autoscaler (HPA) 非常有用,我們用它來在吞吐量上升和下降時(shí)彈性地?cái)U(kuò)展我們的服務(wù)。
比如一個(gè)與 HPA 調(diào)整相關(guān)的發(fā)現(xiàn)示例,在運(yùn)行期間,我們注意到我們的一項(xiàng)服務(wù)難以處理增加的負(fù)載,該服務(wù)的 HPA 基于 CPU 利用率。在測試期間,隨著負(fù)載的增加,CPU 的利用率也隨之增加,幾個(gè)新的 Pod 會(huì)出現(xiàn),CPU 利用率會(huì)下降,Kubernetes 會(huì)關(guān)閉 Pod,同樣的過程會(huì)重復(fù),這樣會(huì)導(dǎo)致 Pod 會(huì)抖動(dòng),性能最終會(huì)下降,這樣我們就需要去調(diào)整服務(wù)的 scaleUp 和 scaleDown 策略,比如設(shè)置 stabilizationWindowSeconds 穩(wěn)定窗口參數(shù)來確保順利處理流量的波動(dòng)。
使用 Kubernetes CronJob 編排負(fù)載測試
負(fù)載測試周期性地針對生產(chǎn)運(yùn)行,沒有人為干預(yù),除非發(fā)現(xiàn)了有性能上的問題,在這種情況下,我們的監(jiān)控系統(tǒng)會(huì)通知我們。我們使用 Kubernetes CronJob 來進(jìn)行編排:
使用 LogCLI 從 Loki 獲取訪問日志 將訪問日志通過管道傳輸?shù)揭粋€(gè)工具中,該工具將它們轉(zhuǎn)換為 Ripley 的 JSON Lines 輸入格式 Ripley 針對我們的生產(chǎn)集群重放訪問日志
對應(yīng)的資源清單示例文件如下所示:
---
apiVersion:?batch/v1beta1
kind:?CronJob
metadata:
??name:?owlbot
??namespace:?perf-test
??labels:
????team:?platform-infrastructure
spec:
??suspend:?false
??schedule:?"48?02?*?*?*"
??startingDeadlineSeconds:?1800
??jobTemplate:
????spec:
??????#?確保我們每次都只嘗試一次
??????backoffLimit:?0
??????template:
????????metadata:
??????????labels:
????????????team:?platform-infrastructure
????????spec:
??????????serviceAccountName:?owlbot
??????????volumes:
????????????-?name:?requests
??????????????emptyDir:?{}
??????????initContainers:
????????????-?name:?loki-fetch
??????????????image:?#?`logcli`?鏡像
??????????????command:
????????????????-?/bin/sh
????????????????-?-c
????????????????-?|
??????????????????/logcli-linux-amd64?query?\
??????????????????--quiet?\
??????????????????--forward?\
??????????????????--limit=24000000?\
??????????????????--batch=1000?\
??????????????????--timezone=UTC?\
??????????????????--from="2021-11-14T11:00:00Z"?\
??????????????????--to="2021-11-14T16:30:00Z"?\
??????????????????--output=raw?\
??????????????????#?在這里過濾掉任何不需要被重放的請求
??????????????????'{job="frontend/nginx"}?|=?"\"request\":\""'?>?/load/nginx.jsonl?&&?\
??????????????????du?-sh?/load/*
??????????????resources:?{?}
??????????????volumeMounts:
????????????????-?mountPath:?/load
??????????????????name:?requests
??????????tolerations:
????????????-?key:?workload
??????????????operator:?Equal
??????????????value:?perf-test
??????????????effect:?NoSchedule
??????????containers:
????????????-?name:?owlbot
??????????????image:?...?#?image?with?`ripley`?and?tools?to?convert?logs?to?`ripley`'s?input?model
??????????????resources:?{}
??????????????command:
????????????????-?/bin/sh
????????????????-?-c
????????????????-?|
??????????????????#?/opt/your/ripley/convert/script?/load/requests.jsonl?>?/load/requests.ripley.jsonl
??????????????????seq?9999?|?xargs?-I?{}?cat?/load/requests.ripley.jsonl?|?/opt/ripley/bin/ripley?-pace?"1m@1?5m@2?5m@3?5m@4?5m@5?5m@6?5m@7?5m@8?5m@9?5m@10"
??????????????volumeMounts:
????????????????-?mountPath:?/load
??????????????????name:?requests
??????????restartPolicy:?Never
這些結(jié)果由 Prometheus 記錄,可以在它或在 Grafana 中直接訪問,還有我們正常的應(yīng)用指標(biāo),包括 Tempo 中的 OpenTelemetry 跟蹤。
結(jié)束語
負(fù)載測試對于了解我們的系統(tǒng)處理不同水平的流量的能力方面是非常寶貴的,在一個(gè)隔離的 stagng 環(huán)境中進(jìn)行重復(fù)測試,可以使測試結(jié)果更容易理解,并且不會(huì)帶來中斷實(shí)時(shí)應(yīng)用程序的風(fēng)險(xiǎn)。針對生產(chǎn)系統(tǒng)測試是最直接的選擇,因?yàn)樗丝绛h(huán)境的調(diào)整需要。在未來,我們還會(huì)探索如何能讓我們有足夠的信心隨時(shí)針對生產(chǎn)運(yùn)行負(fù)載測試,并將混沌工程引入到我們的流程中來。
原文鏈接:https://medium.com/loveholidays-tech/load-testing-in-production-with-grafana-loki-kubernetes-and-golang-1699554d2aa3
