Kubernetes 動(dòng)態(tài)創(chuàng)建 Jenkins Agent 壓力測(cè)試

前面我們利用 Kubernetes 提供的彈性,在 Kubernetes 上動(dòng)態(tài)創(chuàng)建 Jenkins Slave,本文主要是對(duì) Jenkins 進(jìn)行大規(guī)模構(gòu)建的壓力測(cè)試。
1. 集群配置
1.1 Kubernetes 版本
這里使用的是 v1.16.7
kubectl?version
Client?Version:?version.Info{Major:"1",?Minor:"16",?GitVersion:"v1.16.7",?GitCommit:"be3d344ed06bff7a4fc60656200a93c74f31f9a4",?GitTreeState:"clean",?BuildDate:"2020-02-11T19:34:02Z",?GoVersion:"go1.13.6",?Compiler:"gc",?Platform:"linux/amd64"}
Server?Version:?version.Info{Major:"1",?Minor:"16",?GitVersion:"v1.16.7",?GitCommit:"be3d344ed06bff7a4fc60656200a93c74f31f9a4",?GitTreeState:"clean",?BuildDate:"2020-02-11T19:24:46Z",?GoVersion:"go1.13.6",?Compiler:"gc",?Platform:"linux/amd64"}
1.2 節(jié)點(diǎn)數(shù)量
集群節(jié)點(diǎn)總數(shù), 16 個(gè)
kubectl?get?node?|grep?"Ready"?|?wc?-l
16
其中 master 節(jié)點(diǎn) 3 個(gè),worker 節(jié)點(diǎn) 13 個(gè)。
kubectl?get?node?|grep?"master"?|?wc?-l
3
kubectl?get?node?|grep?"worker"?|?wc?-l
13
1.3 CI 節(jié)點(diǎn)
選取其中的 10 個(gè)節(jié)點(diǎn)用于 CI 構(gòu)建,5 個(gè) 8 核 32 G ,5 個(gè) 16 核 32 G 。給這些節(jié)點(diǎn)打上 Label node-role.kubernetes.io/worker=ci ,用于構(gòu)建 Pod 選取 Node 使用,避免影響集群上的其他負(fù)載。
kubectl?top?node?-l?node-role.kubernetes.io/worker=ci
NAME???CPU(cores)???CPU%???MEMORY(bytes)???MEMORY%
ci1????67m??????????0%?????1268Mi??????????8%
ci10???100m?????????1%?????1273Mi??????????4%
ci2????80m??????????1%?????1258Mi??????????8%
ci3????90m??????????1%?????1274Mi??????????8%
ci4????72m??????????0%?????1286Mi??????????8%
ci5????80m??????????1%?????1276Mi??????????8%
ci6????80m??????????1%?????1268Mi??????????4%
ci7????89m??????????1%?????1293Mi??????????4%
ci8????118m?????????1%?????1285Mi??????????4%
ci9????81m??????????1%?????1268Mi??????????4%
1.4 CI 資源配置
Pod 數(shù)量限制,足夠支持 1100 Pod
按照官網(wǎng)文檔描述,Kubernetes 最大支持 5000 個(gè)節(jié)點(diǎn),15 W 個(gè) Pod。
At?v1.18,?Kubernetes?supports?clusters?with?up?to?5000?nodes.?More?specifically,?we?support?configurations?that?meet?all?of?the?following?criteria:
No?more?than?5000?nodes
No?more?than?150000?total?pods
No?more?than?300000?total?containers
No?more?than?100?pods?per?node
除了集群 Pod 總數(shù)有上限,這里有意義的是 kubelet 對(duì) pod 最大數(shù)量的限制。
cat?/var/lib/kubelet/config.yaml|grep?max
maxOpenFiles:?1000000
maxPods:?110
10 個(gè) CI 節(jié)點(diǎn),總共能提供 1100 個(gè) Pod,除去一些系統(tǒng)組件占用,已經(jīng)足夠。
Memory 和 CPU,足夠支持 400 條流水線并發(fā)
每個(gè) Pod 大約占用 500 MB Memory,CPU 是構(gòu)建時(shí)瞬時(shí)值會(huì)比較高,但是維持時(shí)間較短,這里不用太多考慮。5 個(gè) 8 核 32 G ,5 個(gè) 16 核 32 G,總共有 120 核 320 G 內(nèi)存,足夠支撐 400 ( > 320 * 0.8 / 0.5 = 512) 條流水線同時(shí)構(gòu)建。另外,由于 Jenkins Agent Pod 配置的是軟親和,當(dāng) CI 節(jié)點(diǎn)資源不足時(shí),也可以調(diào)度到其他節(jié)點(diǎn)。
2. Jenkins 配置
2.1 Jenkins
即使流水線是在 Agent 上執(zhí)行,但是大量的流水線同時(shí)運(yùn)行,也會(huì)對(duì) Jenkins 產(chǎn)生壓力,這里給 Jenkins 的 limit 為 8 核 16 GB ,也就是最大允許消耗的資源量。
Jenkins 采用 Helm 部署,運(yùn)行在 Kubernetes 上。下面是截取的部分 Deployment 信息:
kind:?Deployment
apiVersion:?apps/v1
metadata:
??name:?ks-jenkins
??namespace:?ks-jenkins
??labels:
????app.kubernetes.io/managed-by:?Helm
????chart:?jenkins-0.19.0
spec:
??replicas:?1
??template:
????metadata:
??????labels:
????????chart:?jenkins-0.19.0
????spec:
??????containers:
????????-?name:?ks-jenkins
??????????image:?'jenkins/jenkins:2.176.2'
??????????env:
????????????-?name:?JAVA_TOOL_OPTIONS
??????????????value:?>-
????????????????-Xms3g?-Xmx6g?-XX:MaxRAM=16g
????????????????-Dhudson.slaves.NodeProvisioner.initialDelay=20
????????????????-Dhudson.slaves.NodeProvisioner.MARGIN=50
????????????????-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
????????????????-Dhudson.model.LoadStatistics.clock=5000
????????????????-Dhudson.model.LoadStatistics.decay=0.2
????????????????-Dhudson.slaves.NodeProvisioner.recurrencePeriod=5000
????????????????-Dio.jenkins.plugins.casc.ConfigurationAsCode.initialDelay=10000
????????????????-verbose:gc?-Xloggc:/var/jenkins_home/gc-%t.log
????????????????-XX:NumberOfGCLogFiles=2?-XX:+UseGCLogFileRotation
????????????????-XX:GCLogFileSize=100m?-XX:+PrintGC?-XX:+PrintGCDateStamps
????????????????-XX:+PrintGCDetails?-XX:+PrintHeapAtGC?-XX:+PrintGCCause
????????????????-XX:+PrintTenuringDistribution?-XX:+PrintReferenceGC
????????????????-XX:+PrintAdaptiveSizePolicy?-XX:+UseG1GC
????????????????-XX:+UseStringDeduplication?-XX:+ParallelRefProcEnabled
????????????????-XX:+DisableExplicitGC?-XX:+UnlockDiagnosticVMOptions
????????????????-XX:+UnlockExperimentalVMOptions?
????????????-?name:?kubernetes.connection.timeout
??????????????value:?'60000'
????????????-?name:?kubernetes.request.timeout
??????????????value:?'60000'
??????schedulerName:?default-scheduler
??????...
2.2 Jenkins Agent
使用 Kubernetes 提供的動(dòng)態(tài) Pod 作為 Jenkins Agent 用于構(gòu)建流水線,具體配置可以參考頂部的文檔鏈接。
Pod 中的 Maven 容器鏡像 Dockerfile 主要內(nèi)容如下:
centos:7
#?java
RUN?yum?install?-y?java-1.8.0-openjdk?\
????java-1.8.0-openjdk-devel?\
????java-1.8.0-openjdk-devel.i686
????...
為了減少對(duì)其他節(jié)點(diǎn)的影響,在 Jenkins 中配置了軟親和,將創(chuàng)建的動(dòng)態(tài) Pod 盡量調(diào)度到指定的 CI 節(jié)點(diǎn)。
spec:
??affinity:
????nodeAffinity:
??????preferredDuringSchedulingIgnoredDuringExecution:
??????-?weight:?1
????????preference:
??????????matchExpressions:
??????????-?key:?node-role.kubernetes.io/worker
????????????operator:?In
????????????values:
????????????-?ci
2.4 Jenkins 中 Kubernetes 插件配置
將容器數(shù)量和等待時(shí)間設(shè)置為一個(gè)較大值。
2.5 測(cè)試用的 Pipeline Demo
Demo 采用的是一個(gè) Java 項(xiàng)目,克隆代碼、執(zhí)行單元測(cè)試、鏡像構(gòu)建。由于鏡像內(nèi)容都一樣,這里就沒有推送鏡像,同時(shí)也減少了外部依賴。gitee.com 對(duì)拉取頻率也有限制,建議使用自己搭建的代碼倉庫。
pipeline?{
??agent?{
????node?{
??????label?'maven'
????}
??}
??environment?{
????????REGISTRY?=?'docker.io'
????????DOCKERHUB_NAMESPACE?=?'shaowenchen'
????????APP_NAME?=?'devops-java-sample'
????????TAG_NAME?=?"SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER"
????}
??stages?{
????stage('checkout')?{
??????steps?{
????????container('maven')?{
??????????git?branch:?'master',?url:?'https://gitee.com/shaowenchen/devops-java-sample.git'
????????}
??????}
????}
????stage('unit?test')?{
??????steps?{
????????container('maven')?{
??????????sh?'mvn?clean?-o?-gs?`pwd`/configuration/settings.xml?test'
????????}
??????}
????}
????stage('build')?{
??????steps?{
????????container('maven')?{
??????????sh?'mvn?-o?-Dmaven.test.skip=true?-gs?`pwd`/configuration/settings.xml?clean?package'
??????????sh?'docker?build?-f?Dockerfile-online?-t?$REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER?.'
????????}
??????}
????}
????stage('sleep?0.5h')?{
??????steps?{
????????sh?'sleep?1800'
??????}
????}
??}
}
2.6 遠(yuǎn)程觸發(fā)流水線腳本
#?-*-?coding:?utf-8?-*-
#?import?time
import?requests
jenkins_job_name?=?"new"
Jenkins_url?=?"http://jenkins.chenshaowen.com:8080"
jenkins_user?=?"admin"
jenkins_pwd?=?"password"
#?buildWithParameters?=?True??#?if?there?are?parameters
buildWithParameters?=?False
jenkins_params?=?{'token':?'mytoken',
??????????????????'param1':?'valu1'}
def?trigger():
????try:
????????auth?=?(jenkins_user,?jenkins_pwd)
????????crumb_data?=?requests.get(
????????????"{0}/crumbIssuer/api/json".format(Jenkins_url),
????????????auth=auth,
????????????headers={
????????????????'content-type':?'application/json'})
????????if?str(crumb_data.status_code)?==?"200":
????????????if?buildWithParameters:
????????????????data?=?requests.get(
????????????????????"{0}/job/{1}/buildWithParameters".format(
????????????????????????Jenkins_url,
????????????????????????jenkins_job_name),
????????????????????auth=auth,
????????????????????params=jenkins_params,
????????????????????headers={
????????????????????????'content-type':?'application/json',
????????????????????????'Jenkins-Crumb':?crumb_data.json()['crumb']})
????????????else:
????????????????data?=?requests.get(
????????????????????"{0}/job/{1}/build".format(
????????????????????????Jenkins_url,
????????????????????????jenkins_job_name),
????????????????????auth=auth,
????????????????????params=jenkins_params,
????????????????????headers={
????????????????????????'content-type':?'application/json',
????????????????????????'Jenkins-Crumb':?crumb_data.json()['crumb']})
????????????print(data.status_code)
????????????if?str(data.status_code)?==?"201":
????????????????print("Jenkins?job?is?triggered")
????????????else:
????????????????print("Failed?to?trigger?the?Jenkins?job")
????????else:
????????????print("Couldn't?fetch?Jenkins-Crumb")
????????????raise
????except?Exception?as?e:
????????print("Failed?triggering?the?Jenkins?job")
????????print("Error:?"?+?str(e))
if?__name__?==?"__main__":
????for?i?in?range(400):
????????#?time.sleep(1)
????????print("Trigger-"?+?str(i))
????????trigger()
3. 測(cè)試策略
為了更好的測(cè)試 Jenkins 在 Kubernetes 上執(zhí)行流水線的性能,在上面的配置中,我提供了足夠 400 條流水線并發(fā)執(zhí)行的資源。
由于首次運(yùn)行流水線時(shí),需要拉取鏡像、對(duì)依賴包進(jìn)行緩存。在執(zhí)行測(cè)試之前,執(zhí)行 20 次流水線對(duì)節(jié)點(diǎn)進(jìn)行預(yù)熱。
主要進(jìn)行五組測(cè)試,分別為 50、100、200、400、800 條流水線并發(fā)。
觀察的指標(biāo)
觸發(fā)流水線成功率 Jenkins UI 能否正常打開 Jenkins 創(chuàng)建 Pod 的速度 流水線執(zhí)行成功率 失敗的原因
4. 測(cè)試結(jié)果
| 流水線并發(fā)數(shù)量 | 觸發(fā)成功率 | UI 能否正常打開 | 全部 Pod 創(chuàng)建成功耗時(shí) | 流水線執(zhí)行成功率 | 失敗的原因 |
|---|---|---|---|---|---|
| 50 | 50/50 | 可以 | 12分鐘 | 50/50 | - |
| 100 | 100/100 | 可以 | 7分鐘 | 100/100 | - |
| 200 | 200/200 | 4 秒加載 | 7分鐘 | 178/200 | Gitee 限制了拉取頻率 |
| 400 | 400/400 | 11 秒加載 | 21分鐘 | 348/400 | Gitee 限制了拉取頻率 |
| 800 | 778/800 | 17 秒加載 | 18分鐘 | 446/800 | 觸發(fā)失敗、流水線堆積無法調(diào)度 |
下面是具體的監(jiān)控?cái)?shù)據(jù)和分析
50 并發(fā):正常執(zhí)行,應(yīng)該是預(yù)熱不夠充分,后半段速度變慢,創(chuàng)建時(shí)間較長(zhǎng)。
100 并發(fā):正常執(zhí)行,創(chuàng)建 Pod 速度很快,3~4 秒一個(gè)
200 并發(fā):觸發(fā)正常,執(zhí)行時(shí)部分流水線報(bào)錯(cuò)。這里的錯(cuò)誤,主要是拉取 git 服務(wù)器代碼受到了限制。錯(cuò)誤提示如下:
400 并發(fā):有極少量調(diào)度到非 CI 節(jié)點(diǎn),同樣有大量拉取 git 服務(wù)器代碼提示錯(cuò)誤。
800 并發(fā):460、461、551、552、759-776 觸發(fā)失敗。有少量調(diào)度到非 CI 節(jié)點(diǎn),大量流水線堆積在 Build Queue ,這些流水線長(zhǎng)時(shí)間不被調(diào)度,嘗試重啟 Jenkins 依然無法執(zhí)行。
800 條流水線并發(fā),超過了集群的負(fù)載極限。Jenkins 使用的內(nèi)存達(dá)到了極限,能連接管理的 jnlp 數(shù)量也達(dá)到了極限。下面是相關(guān)的提示報(bào)錯(cuò):
INFO:?Server?reports?protocol?JNLP-connect?not?supported,?skipping
Aug?02,?2020?7:20:33?AM?hudson.remoting.jnlp.Main$CuiListener?error
SEVERE:?The?server?rejected?the?connection:?None?of?the?protocols?were?accepted
java.lang.Exception:?The?server?rejected?the?connection:?None?of?the?protocols?were?accepted
at?hudson.remoting.Engine.onConnectionRejected(Engine.java:675)
at?hudson.remoting.Engine.innerRun(Engine.java:639)
at?hudson.remoting.Engine.run(Engine.java:474)
-XX:MaxRAM=16g 的配置在 400 并發(fā)時(shí),明顯吃力,到了 800 并發(fā)時(shí),已經(jīng)不夠。之后,我又將最大內(nèi)存使用設(shè)置為 32 g 進(jìn)行測(cè)試,觸發(fā)成功率有所改善,依然達(dá)不到 100% ;Pod 創(chuàng)建速度變快,集群資源充足的情況下,依然有部分堵在 Build Queue 中無法調(diào)度。
后來,我找了一個(gè) 202 個(gè)節(jié)點(diǎn)的集群進(jìn)行測(cè)試,Jenkins 內(nèi)存限制設(shè)置很大。通過接口不停地發(fā)送觸發(fā)請(qǐng)求,Pod 數(shù)量最高峰在 517(=520-3),Pod 中的 jnlp 與 Jenkins 連接出現(xiàn)問題。同時(shí),也伴隨著大量觸發(fā)和構(gòu)建錯(cuò)誤。下圖是,關(guān)于 Pod 數(shù)量監(jiān)控:
5. 測(cè)試總結(jié)和建議
從原理上講 Jenkins 的 Kubernetes 插件實(shí)現(xiàn)的功能是調(diào)用 Kubernetes 的接口,創(chuàng)建 Pod 用于構(gòu)建。創(chuàng)建的 Pod 中包含 jnlp 和真正構(gòu)建環(huán)境的容器。
在高并發(fā)、高負(fù)載的場(chǎng)景下,瓶頸會(huì)出現(xiàn)在如下方面:
Jenkins 提供的 API Jenkins 的調(diào)度算法 Jenkins 調(diào)用的 Kubernetes API Kubernetes 調(diào)度創(chuàng)建 Pod 的速度 Pod 運(yùn)行時(shí)的資源消耗,CPU、Mem、IO 等 Jenkins 的 Mem 和 CPU 限制
這次測(cè)試不算特別完善,有如下問題:
預(yù)熱不夠充分。測(cè)試 50 并發(fā)的數(shù)據(jù)有明顯問題,創(chuàng)建速度比 100 并發(fā)還慢,說明有些節(jié)點(diǎn)沒有相關(guān)的鏡像或緩存。 Jenkins 內(nèi)存不夠充足。在 400 并發(fā)時(shí),Jenkins 的內(nèi)存使用已經(jīng)接近 limit 限制,頁面打開緩慢。
配置建議:
限制 Jenkins 同時(shí)連接 Pod 的數(shù)量,配置足夠的情況下,200 并發(fā)是沒有問題的,400 并發(fā)是可以爭(zhēng)取的。Jenkins 需要與每一個(gè) Pod 中的 jnlp 通信,控制并發(fā)數(shù)量能有效減輕 Jenkins 的負(fù)擔(dān),避免觸發(fā)失敗的發(fā)生。 使用專用的 CI 節(jié)點(diǎn)。讓流水線的 Pod 在節(jié)點(diǎn)之間隨意漂移,充分享受 Kubernetes 提供的彈性固然很好,但是大量并發(fā)的流水線會(huì)擠走節(jié)點(diǎn)上的負(fù)載,導(dǎo)致其他應(yīng)用不穩(wěn)定。 構(gòu)建的 Pod 需要設(shè)置合適的 request 。與創(chuàng)建應(yīng)用負(fù)載類似,過小的 request 會(huì)導(dǎo)致調(diào)度成功,但是 Pod 起不來的問題。大量流水線并發(fā)時(shí),過小的 request 可能會(huì)直接壓垮節(jié)點(diǎn)。 充足的 Jenkins 內(nèi)存,16 G 基本能保證系統(tǒng)穩(wěn)定,CPU 4C 及以上即可。Java 應(yīng)用占用內(nèi)存比較多。分配充足的內(nèi)存給 Jenkins,可以提高觸發(fā)成功率,提高 Pod 的創(chuàng)建效率,同時(shí) Jenkins 也更穩(wěn)定,不容易出現(xiàn) Jenkins 頁面打不開的情況。 綁定一個(gè)專門的節(jié)點(diǎn)用來運(yùn)行 Jenkins。當(dāng)給 Jenkins 設(shè)置了較大的內(nèi)存限制時(shí),隨著并發(fā)數(shù)量上升,內(nèi)存使用逐漸增加,雖然 limit 很大,但是節(jié)點(diǎn)內(nèi)存可能不夠,這樣可能會(huì)導(dǎo)致 Jenkins 被調(diào)度到其他節(jié)點(diǎn)。 使用單實(shí)例 Jenkins 。Jenkins 使用磁盤文件存儲(chǔ)數(shù)據(jù),多實(shí)例會(huì)讓 Jenkins 紊亂。提示錯(cuò)誤如下:
Error
Jenkins?detected?that?you?appear?to?be?running?more?than?one?instance?of?Jenkins?that?share?the?same?home?directory?'/var/jenkins_home'.?This?greatly?confuses?Jenkins?and?you?will?likely?experience?strange?behaviors,?so?please?correct?the?situation.
This?Jenkins:????449134911?contextPath=""?at?6@ks-jenkins-68b8949bb-mgmjc
Other?Jenkins:????1869668338?contextPath=""?at?6@ks-jenkins-68b8949bb-kg49k
“原文鏈接:https://www.chenshaowen.com/blog/the-stress-test-about-kubernetes-dynamically-creates-jenkins-agent.html
”
?點(diǎn)擊屏末?|?閱讀原文?|?即刻學(xué)習(xí)











