新聞中心
一、背景介紹
目前很多企業(yè)應(yīng)用都已經(jīng)容器化,版本發(fā)布比較多,構(gòu)建的次數(shù)也比較多,相對(duì)于之前單臺(tái) jenkins 有了很大的挑戰(zhàn)
,傳統(tǒng)的 Jenkins Slave 一主多從方式會(huì)存在一些痛點(diǎn):
創(chuàng)新互聯(lián)建站是一家以網(wǎng)絡(luò)技術(shù)公司,為中小企業(yè)提供網(wǎng)站維護(hù)、成都網(wǎng)站建設(shè)、成都網(wǎng)站制作、網(wǎng)站備案、服務(wù)器租用、域名注冊(cè)、軟件開發(fā)、成都小程序開發(fā)等企業(yè)互聯(lián)網(wǎng)相關(guān)業(yè)務(wù),是一家有著豐富的互聯(lián)網(wǎng)運(yùn)營(yíng)推廣經(jīng)驗(yàn)的科技公司,有著多年的網(wǎng)站建站經(jīng)驗(yàn),致力于幫助中小企業(yè)在互聯(lián)網(wǎng)讓打出自已的品牌和口碑,讓企業(yè)在互聯(lián)網(wǎng)上打開一個(gè)面向全國(guó)乃至全球的業(yè)務(wù)窗口:建站咨詢電話:13518219792
- 主 Master 發(fā)生單點(diǎn)故障時(shí),整個(gè)流程都不可用了;
- 每個(gè) Slave 的配置環(huán)境不一樣,來(lái)完成不同語(yǔ)言的編譯打包等操作,但是這些差異化的配置導(dǎo)致管理起來(lái)非常不方便,維護(hù)起來(lái)也是比較費(fèi)勁;
- 資源分配不均衡,有的 Slave 要運(yùn)行的 job 出現(xiàn)排隊(duì)等待,而有的 Slave 處于空閑狀態(tài);
- 最后資源有浪費(fèi),每臺(tái) Slave 可能是實(shí)體機(jī)或者 VM,當(dāng) Slave 處于空閑狀態(tài)時(shí),也不會(huì)完全釋放掉資源了。
kubernetes 集群之中,我們正是利用這一容器平臺(tái)來(lái)實(shí)現(xiàn) jenkins 的自動(dòng)擴(kuò)容。
jenkins集群架構(gòu)圖
從圖上可以看到 Jenkins Master 和 Jenkins Slave 以 Docker Container 形式運(yùn)行在 Kubernetes 集群的 Node 上,Master 運(yùn)行在其中一個(gè)節(jié)點(diǎn),并且將其配置數(shù)據(jù)存儲(chǔ)到一個(gè) Volume 上去,Slave 運(yùn)行在各個(gè)節(jié)點(diǎn)上,并且它不是一直處于運(yùn)行狀態(tài),它會(huì)按照需求動(dòng)態(tài)的創(chuàng)建并自動(dòng)刪除。
這種方式的工作流程大致為:當(dāng) Jenkins Master 接受到 Build 請(qǐng)求時(shí),會(huì)根據(jù)配置的 Label 動(dòng)態(tài)創(chuàng)建一個(gè)運(yùn)行在 Docker Container 中的 Jenkins Slave 并注冊(cè)到 Master 上,當(dāng)運(yùn)行完 Job 后,這個(gè) Slave 會(huì)被注銷并且 Docker Container 也會(huì)自動(dòng)刪除,恢復(fù)到最初狀態(tài)。
這種方式帶來(lái)的好處有很多:
- 服務(wù)高可用,當(dāng) Jenkins Master 出現(xiàn)故障時(shí),Kubernetes 會(huì)自動(dòng)創(chuàng)建一個(gè)新的 Jenkins Master 容器,并且將 Volume 分配給新創(chuàng)建的容器,保證數(shù)據(jù)不丟失,從而達(dá)到集群服務(wù)高可用。
- 動(dòng)態(tài)伸縮,合理使用資源,每次運(yùn)行 Job 時(shí),會(huì)自動(dòng)創(chuàng)建一個(gè) Jenkins Slave,Job 完成后,Slave 自動(dòng)注銷并刪除容器,資源自動(dòng)釋放,而且 Kubernetes 會(huì)根據(jù)每個(gè)資源的使用情況,動(dòng)態(tài)分配 Slave 到空閑的節(jié)點(diǎn)上創(chuàng)建,降低出現(xiàn)因某節(jié)點(diǎn)資源利用率高,還排隊(duì)等待在該節(jié)點(diǎn)的情況。
- 擴(kuò)展性好,當(dāng) Kubernetes 集群的資源嚴(yán)重不足而導(dǎo)致 Job 排隊(duì)等待時(shí),可以很容易的添加一個(gè) Kubernetes Node 到集群中,從而實(shí)現(xiàn)擴(kuò)展。
二、部署 jenkins
我們把 master 節(jié)點(diǎn)部署到 k8s 集群中,大家可以參照 官方 github 文檔進(jìn)行配置,我這里進(jìn)行了一點(diǎn)簡(jiǎn)化,我這里使用的是 nfs 來(lái)存儲(chǔ) jenkins 的數(shù)據(jù),用于進(jìn)行持久存儲(chǔ)。
kubectl apply -f https://raw.githubusercontent.com/wangzan18/jenkins-agent-k8s-cicd/master/master/jenkins.yaml
說(shuō)明一下:這里 Service 我們暴漏了端口 8080 和 50000,8080 為訪問 Jenkins Server 頁(yè)面端口,50000 為創(chuàng)建的 Jenkins Slave 與 Master 建立連接進(jìn)行通信的默認(rèn)端口,如果不暴露的話,Slave 無(wú)法跟 Master 建立連接。這里使用 NodePort 方式暴漏端口,并未指定其端口號(hào),由 Kubernetes 系統(tǒng)默認(rèn)分配,當(dāng)然也可以指定不重復(fù)的端口號(hào)(范圍在 30000~32767)。
2.1、配置 kubernetes plugin
Jenkins 的配置過程我這里不再掩飾,我們直接配置 kubernetes plugin。
管理員賬戶登錄 Jenkins Master 頁(yè)面,點(diǎn)擊 “系統(tǒng)管理” —> “管理插件” —> “可選插件” —> “Kubernetes plugin” 勾選安裝即可。
安裝完畢后,點(diǎn)擊 “系統(tǒng)管理” —> “系統(tǒng)設(shè)置” —> “新增一個(gè)云” —> 選擇 “Kubernetes”,然后填寫 Kubernetes 和 Jenkins 配置信息。
說(shuō)明一下:
- Name處默認(rèn)為 kubernetes,也可以修改為其他名稱,如果這里修改了,下邊在執(zhí)行 Job 時(shí)指定 podTemplate() 參數(shù) cloud 為其對(duì)應(yīng)名稱,否則會(huì)找不到,cloud 默認(rèn)值取:kubernetes。
- Kubernetes URL處我填寫了 https://kubernetes 這里我填寫了 Kubernetes Service 對(duì)應(yīng)的 DNS 記錄,通過該 DNS 記錄可以解析成該 Service 的 Cluster IP,注意:也可以填寫 https://kubernetes.default.svc.cluster.local 完整 DNS 記錄,因?yàn)樗?
<svc_name>.<namespace_name>.svc.cluster.local
的命名方式,或者直接填寫外部 Kubernetes 的地址https://<ClusterIP>:<Ports>
。 - Jenkins URL處我填寫了 http://jenkins.default:8080 ,跟上邊類似,也是使用 Jenkins Service 對(duì)應(yīng)的 DNS 記錄,不過要指定為 8080 端口,因?yàn)槲覀冊(cè)O(shè)置暴漏 8080 端口。同時(shí)也可以用
http://<ClusterIP>:<Node_Port>
方式。
配置完畢,可以點(diǎn)擊 “Test Connection” 按鈕測(cè)試是否能夠連接的到 Kubernetes,如果顯示 Connection test successful 則表示連接成功,配置沒有問題。
因?yàn)槲覀兊?jenkins 是集群內(nèi)部的 pod,所以它是可以直接和 kubernetes api 進(jìn)行通信,并且我們也賦予了相應(yīng)的權(quán)限,如果說(shuō) master 是創(chuàng)建在集群外部的,我們需要提前為 jenkins agent 創(chuàng)建一個(gè) service account,然后把相應(yīng)的 token 賦予到憑據(jù)的 sercet text。
三、pipeline job 驗(yàn)證測(cè)試
3.1、pipeline 支持
創(chuàng)建一個(gè) Pipeline 類型 Job 并命名為 jenkins-pipeline
,然后在 Pipeline 腳本處填寫一個(gè)簡(jiǎn)單的測(cè)試腳本如下:
podTemplate {
node(POD_LABEL) {
stage('Run shell') {
sh 'echo hello world'
sh 'sleep 60'
}
}
}
創(chuàng)建還 job 之后,點(diǎn)擊構(gòu)建,我們會(huì)在構(gòu)建隊(duì)列中發(fā)現(xiàn)一個(gè)待執(zhí)行的 job,因?yàn)槲覀冊(cè)?pipeline 中要求 jenkins agent 節(jié)點(diǎn)的名稱為 POD_LABEL,沒有發(fā)現(xiàn)這個(gè) agent,所以會(huì)去請(qǐng)求 kubernetes 去創(chuàng)建 agent 節(jié)點(diǎn)。
jenkins agent 節(jié)點(diǎn)創(chuàng)建好了之后,會(huì)去 jenkins master 注冊(cè),并去執(zhí)行隊(duì)列中的 job,完成之后取消注冊(cè),并自行銷毀。
我們還可以去 console 查看構(gòu)建日志。
也可以在 k8s 上面看到啟動(dòng)的 agent 容器。
wangzan:~/k8s $ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
jenkins-5df4dff655-f4gk8 1/1 Running 0 25m app=jenkins,pod-template-hash=5df4dff655
jenkins-pipeline-5-lbs5j-b2jl6-0mk2g 1/1 Running 0 7s jenkins/label=jenkins-pipeline_5-lbs5j,jenkins=slave
myapp1 1/1 Running 0 21h app=myapp1
podTemplate
The podTemplate
is a template of a pod that will be used to create agents. It can be either configured via the user interface, or via pipeline.
Either way it provides access to the following fields:
- cloudThe name of the cloud as defined in Jenkins settings. Defaults to
kubernetes
- nameThe name of the pod.
- namespaceThe namespace of the pod.
- labelThe label of the pod. Can be set to a unique value to avoid conflicts across builds, or omitted and
POD_LABEL
will be defined inside the step. - yamlyaml representation of the Pod, to allow setting any values not supported as fields
- yamlMergeStrategy
merge()
oroverride()
. Controls whether the yaml definition overrides or is merged with the yaml definition inherited from pod templates declared withinheritFrom
. Defaults tooverride()
. - containersThe container templates that are use to create the containers of the pod (see below).
- serviceAccountThe service account of the pod.
- nodeSelectorThe node selector of the pod.
- nodeUsageModeEither 'NORMAL' or 'EXCLUSIVE', this controls whether Jenkins only schedules jobs with label expressions matching or use the node as much as possible.
- volumesVolumes that are defined for the pod and are mounted by ALLcontainers.
- envVarsEnvironment variables that are applied to ALLcontainers.
- envVarAn environment variable whose value is defined inline.
- secretEnvVarAn environment variable whose value is derived from a Kubernetes secret.
- imagePullSecretsList of pull secret names, to pull images from a private Docker registry.
- annotationsAnnotations to apply to the pod.
- inheritFromList of one or more pod templates to inherit from (more details below).
- slaveConnectTimeoutTimeout in seconds for an agent to be online (more details below).
- podRetentionControls the behavior of keeping slave pods. Can be 'never()', 'onFailure()', 'always()', or 'default()' - if empty will default to deleting the pod after
activeDeadlineSeconds
has passed. - activeDeadlineSecondsIf
podRetention
is set to 'never()' or 'onFailure()', pod is deleted after this deadline is passed. - idleMinutesAllows the Pod to remain active for reuse until the configured number of minutes has passed since the last step was executed on it.
- showRawYamlEnable or disable the output of the raw Yaml file. Defaults to
true
- runAsUserThe user ID to run all containers in the pod as.
- runAsGroupThe group ID to run all containers in the pod as.
- hostNetworkUse the hosts network.
3.2、Container Group
前面的 pipeline 中的 agent 是使用的默認(rèn)的鏡像 jenkins/jnlp-slave:3.35-5-alpine,我們也可以添加其他的一些鏡像到 pod 里面。
創(chuàng)建一個(gè) Pipeline 類型 Job 并命名為 jenkins-pipeline-container
,然后在 Pipeline 腳本處填寫一個(gè)簡(jiǎn)單的測(cè)試腳本如下:
podTemplate(containers: [
containerTemplate(name: 'maven', image: 'maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
containerTemplate(name: 'golang', image: 'golang:1.8.0', ttyEnabled: true, command: 'cat')
]) {
node(POD_LABEL) {
stage('Get a Maven project') {
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn -B clean install'
}
}
}
stage('Get a Golang project') {
git url: 'https://github.com/hashicorp/terraform.git'
container('golang') {
stage('Build a Go project') {
sh """
mkdir -p /go/src/github.com/hashicorp
ln -s `pwd` /go/src/github.com/hashicorp/terraform
cd /go/src/github.com/hashicorp/terraform && make core-dev
"""
}
}
}
}
}
從 k8s 中也可以看到 pod 中存在三個(gè)容器。
wangzan:~/k8s $ kubectl get pod --show-labels
NAME READY STATUS RESTARTS AGE LABELS
jenkins-5df4dff655-f4gk8 1/1 Running 0 42m app=jenkins,pod-template-hash=5df4dff655
jenkins-pipeline-container-1-6zf73-chltq-b0rjt 3/3 Running 0 70s jenkins/label=jenkins-pipeline-container_1-6zf73,jenkins=slave
myapp1
containerTemplate
The containerTemplate
is a template of container that will be added to the pod. Again, its configurable via the user interface or via pipeline and allows you to set the following fields:
- nameThe name of the container.
- imageThe image of the container.
- envVarsEnvironment variables that are applied to the container (supplementing and overriding env vars that are set on pod level).
- envVarAn environment variable whose value is defined inline.
- secretEnvVarAn environment variable whose value is derived from a Kubernetes secret.
- commandThe command the container will execute.
- argsThe arguments passed to the command.
- ttyEnabledFlag to mark that tty should be enabled.
- livenessProbeParameters to be added to a exec liveness probe in the container (does not support httpGet liveness probes)
- portsExpose ports on the container.
- alwaysPullImageThe container will pull the image upon starting.
- runAsUserThe user ID to run the container as.
- runAsGroupThe group ID to run the container as.
3.3、使用 SCM
使用 SCM 可以有很多好處:
- 每次修改 pipeline 我們不用到 console 中去修改;
- 開發(fā)人員可以方便的自定義 pipeline,選擇自己需要的 container;
- 當(dāng) jenkins 數(shù)據(jù)丟失,也不會(huì)丟掉 pipeline。
使用 SCM ,就需要我們把上面所寫的 pipeline 代碼放到 Jenkinsfile,一般是這個(gè)名字,當(dāng)然也可以自定義名稱,我們把上面第一個(gè)案例使用 SCM 運(yùn)行一下,首先就是修改我們的 job。
我的 jenkinsfile 地址為 https://github.com/wangzan18/jenkins-agent-k8s-cicd/blob/master/jenkinsfile/jenkins-pipeline-podtemplate.jenkinsfile 。
然后在控制臺(tái)查看運(yùn)行日志。
其他參數(shù)大家可以根據(jù)自己的情況進(jìn)行設(shè)定。
四、普通 job 驗(yàn)證
Jenkins 中除了使用 Pipeline 方式運(yùn)行 Job 外,通常我們也會(huì)使用普通類型 Job,如果也要想使用 kubernetes plugin 來(lái)構(gòu)建任務(wù)
那么就需要點(diǎn)擊 “系統(tǒng)管理” —> “系統(tǒng)設(shè)置” —> “云” —> “Kubernetes” —> “Add Pod Template” 進(jìn)行配置 “Kubernetes Pod Template” 信息。
Labels 名:在配置非 pipeline 類型 Job 時(shí),用來(lái)指定任務(wù)運(yùn)行的節(jié)點(diǎn)。
Containers Name: 這里要注意的是,如果 Name 配置為 jnlp,那么 Kubernetes 會(huì)用下邊指定的 Docker Image 代替默認(rèn)的 jenkinsci/jnlp-slave 鏡像,否則,Kubernetes plugin 還是會(huì)用默認(rèn)的 jenkinsci/jnlp-slave 鏡像與 Jenkins Server 建立連接,即使我們指定其他 Docker Image。這里我配置為 jenkins-slave,意思就是使用 plugin 默認(rèn)的鏡像與 jenkins server 建立連接,當(dāng)我選擇 jnlp 的時(shí)候,發(fā)現(xiàn)鏡像無(wú)法與 jenkins server 建立連接,具體情況我也不太清楚,也有可能是鏡像的問題。
新建一個(gè)自由風(fēng)格的 Job 名稱為 jenkins-simple
,配置 “Restrict where this project can be run” 勾選,在 “Label Expression” 后邊輸出我們上邊創(chuàng)建模板是指定的 Labels 名稱 jnlp-agent,意思是指定該 Job 匹配 jenkins-slave
標(biāo)簽的 Slave 上運(yùn)行。
效果如我們預(yù)期所示:
五、自定義 jenkins-slave 鏡像
前面我隨便在 https://hub.docker.com/r/jenkins/jnlp-slave 中選擇了一個(gè)鏡像,發(fā)現(xiàn)無(wú)法與 jenkins server 建立連接,那我們就自己制作一個(gè)鏡像。
通過 kubernetest plugin 默認(rèn)提供的鏡像 jenkinsci/jnlp-slave 可以完成一些基本的操作,它是基于 openjdk:8-jdk 鏡像來(lái)擴(kuò)展的,但是對(duì)于我們來(lái)說(shuō)這個(gè)鏡像功能過于簡(jiǎn)單,比如我們想執(zhí)行 Maven 編譯或者其他命令時(shí),就有問題了,那么可以通過制作自己的鏡像來(lái)預(yù)安裝一些軟件,既能實(shí)現(xiàn) jenkins-slave 功能,又可以完成自己個(gè)性化需求,那就比較不錯(cuò)了。如果我們從頭開始制作鏡像的話,會(huì)稍微麻煩些,不過可以參考 jenkinsci/jnlp-slave 和 jenkinsci/docker-slave 這兩個(gè)官方鏡像來(lái)做,注意:jenkinsci/jnlp-slave 鏡像是基于 jenkinsci/docker-slave 來(lái)做的。這里我簡(jiǎn)單演示下,基于 jenkinsci/jnlp-slave:latest 鏡像,在其基礎(chǔ)上做擴(kuò)展,安裝 Maven 到鏡像內(nèi),然后運(yùn)行驗(yàn)證是否可行吧,大家可以查看我的鏡像:https://hub.docker.com/r/wangzan18/jenkins-slave-maven 。
podTemplate(containers: [
containerTemplate(
name: 'jnlp',
image: 'wangzan18/jenkins-agent:maven-3.6.3',
alwaysPullImage: false,
args: '${computer.jnlpmac} ${computer.name}'),
]) {
node(POD_LABEL) {
stage('git pull') {
echo "hello git"
}
stage('build') {
sh 'mvn -version'
}
stage('test') {
echo "hello test"
}
stage('deploy') {
echo "hello deploy"
sleep 10
}
}
}
這里 containerTemplate 的 name 屬性必須叫 jnlp
,Kubernetes 才能用自定義 images 指定的鏡像替換默認(rèn)的 jenkinsci/jnlp-slave 鏡像。此外,args 參數(shù)傳遞兩個(gè) jenkins-slave 運(yùn)行需要的參數(shù)。還有一點(diǎn)就是這里并不需要指定 container('jnlp'){...} 了,因?yàn)樗?Kubernetes 指定了要被執(zhí)行的容器,所以直接執(zhí)行 Stage 就可以了。
可以看到已經(jīng)達(dá)到我們想要的效果,確實(shí)也是使用我們自定義的 jenkins-slave 鏡像。
問題:非pipeline job
我測(cè)試的過程中,使用自由風(fēng)格的 job,不管使用什么鏡像,鏡像就是無(wú)法自主連接 jenkins server,目前我也不清楚是哪里的原因,如果有知道的小伙伴可以留言回復(fù)。
參考文檔:https://github.com/jenkinsci/kubernetes-plugin
新聞標(biāo)題:jenkins如何在k8s集群中實(shí)現(xiàn)動(dòng)態(tài)agent
鏈接URL:http://fisionsoft.com.cn/article/ghjipc.html