本文章案例可用于參考Jenkins for Kubernetes部署。因每個公司的架構(gòu)和環(huán)境不一樣,需要改變一些部署的方式。
Jenkins for Kubernetes的好處:
- Jenkins-Master的高可用。Kubernetes的RC或Deployment可以監(jiān)控副本的存活狀態(tài)(通過探針)和副本數(shù)量,如果Master出現(xiàn)無法提供服務(wù)的情況,就會重啟或者遷移到其他節(jié)點。
- Jenkins-Slave的動態(tài)伸縮。每次構(gòu)建都會啟動一個Pod用于部署Slave,構(gòu)建完成后就會釋放掉。那么Pod在創(chuàng)建的時候,Kubernetes就會選擇集群內(nèi)資源剩余較多的節(jié)點創(chuàng)建Slave的Pod,構(gòu)建完成后Pod會自動刪除。
- 擴展性好。 因為可以同時擁有很多個Slave,可以配置Jenkins同時執(zhí)行很多構(gòu)建操作,減少排隊等待構(gòu)建的時間。
部署思路
首先在Kubernetes中部署Jenkins-Master然后使用Kubernetes Plugin插件進行Slave的動態(tài)伸縮。并且使用NFS作為后端存儲的PersistentVolume來掛載Jenkins-Master的jenkins_home目錄、構(gòu)建時Slave的Maven緩存m2目錄(可以利用緩存加快每次構(gòu)建的速度)、保留Slave每次構(gòu)建產(chǎn)生的數(shù)據(jù)(workspace目錄中的每個Job)。
使用PersistentVolume的原因是Kubernetes任何節(jié)點都可以訪問到掛載的目錄,不會因為Master遷移節(jié)點導(dǎo)致數(shù)據(jù)丟失。NFS方便部署而且性能也滿足Jenkins的使用需求所以選擇了NFS,也可以使用其他的后端存儲。
部署
部署方式可以自定義也可以使用Kubernetes Pugin官網(wǎng)提供的部署yml。自定義使用Deployment也是可以的,但是官網(wǎng)的部署方式使用了StatefulSet。Jenkins是一個有狀態(tài)的應(yīng)用,我感覺使用StatefulSet部署更加嚴(yán)謹(jǐn)一點。我這里使用了官網(wǎng)提供的文檔進行部署的,但是也根據(jù)實際情況修改了一些東西。
首先需要在Kubernetes所有節(jié)點部署NFS客戶端:
yum -y install nfs-utils
systemctl start nfs-utils
systemctl enable nfs-utils
rpcinfo -p
NFS服務(wù)端配置文件增加配置:
/data/dev_jenkins 10.0.0.0/24(rw,sync,no_root_squash,no_subtree_check)
dev環(huán)境Jenkins Slave節(jié)點掛載workspace
/data/dev_jenkins/workspace 0.0.0.0/0(rw,sync,no_root_squash,no_subtree_check)
dev環(huán)境Jenkins Slave節(jié)點掛載m2 Maven緩存目錄
/data/dev_jenkins/m2 0.0.0.0/0(rw,sync,no_root_squash,no_subtree_check)
共享目錄一定要給777權(quán)限。不然容器內(nèi)部會報錯沒有寫入權(quán)限。
service-account.yml此文件用于創(chuàng)建Kubernetes的RBAC,授權(quán)給后面的Jenkins應(yīng)用可以創(chuàng)建和刪除Slave的Pod。
# In GKE need to get RBAC permissions first with
# kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin [--user=<user-name>|--group=<group-name>]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: jenkins
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get","list","watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: jenkins #與jenkins.yml中的serviceAccountName: jenkins相對應(yīng)
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
jenkins-pv.yml和jenkins-pvc.yml用于創(chuàng)建掛載jenkins_home目錄:
[root@dev-master1 kubernetes]# cat jenkins-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-home
spec:
capacity: #指定容量
storage: 20Gi
accessModes:
- ReadWriteOnce #訪問模式,還有ReadOnlyMany ##ReadOnlymany
# persistenVolumeReclaimPolicy: Recycle
# storageClassName: nfs ##指定存儲的類型
nfs:
path: /data/dev_jenkins #指明NFS的路徑
server: 10.0.0.250 #指明NFS的IP
[root@dev-master1 kubernetes]# cat jenkins-pvc.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
namespace: kubernetes-plugin
name: jenkins-home
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
創(chuàng)建Jenkins的Master,可以根據(jù)實際情況限制Jenkins的資源使用。
[root@dev-master1 kubernetes]# cat jenkins.yml
# jenkins
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jenkins
labels:
name: jenkins
spec:
selector:
matchLabels:
name: jenkins
serviceName: jenkins
replicas: 1
updateStrategy:
type: RollingUpdate
template:
metadata:
name: jenkins
labels:
name: jenkins
spec:
terminationGracePeriodSeconds: 10
serviceAccountName: jenkins
containers:
- name: jenkins
image: 10.0.0.59/jenkins/jenkins:lts-alpine #官方鏡像為jenkins/jenkins:lts-alpine,為了節(jié)省下載時間已經(jīng)push到自己到Harbor倉庫
imagePullPolicy: Always
ports:
- containerPort: 8080
- containerPort: 50000
resources:
limits:
cpu: 1
memory: 1Gi
requests:
cpu: 0.5
memory: 500Mi
env:
- name: LIMITS_MEMORY
valueFrom:
resourceFieldRef:
resource: limits.memory
divisor: 1Mi
- name: JAVA_OPTS
# value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
volumeMounts: #掛載PVC存儲到Jenkins容器的/var/jenkins_home
- name: jenkinshome
mountPath: /var/jenkins_home
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 600 #存活探針時間改為600s,如果服務(wù)器配置低,Jenkins還沒有啟動成功就被重啟了。
timeoutSeconds: 5
failureThreshold: 12 # ~2 minutes
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
timeoutSeconds: 5
failureThreshold: 12 # ~2 minutes
securityContext:
fsGroup: 1000
volumes: #此處聲明Jenkins的PVC存儲
- name: jenkinshome
persistentVolumeClaim:
claimName: jenkins-home
# imagePullSecrets: 如果使用私有倉庫,并且倉庫對鏡像設(shè)置了訪問權(quán)限,需要在Kubernetes Master創(chuàng)建一個secret
# - name: registry-secret
jenkins-sv.yml用于創(chuàng)建Jenkins的Service。
[root@dev-master1 kubernetes]# cat jenkins-sv.yml
apiVersion: v1
kind: Service
metadata:
name: jenkins
spec:
sessionAffinity: "ClientIP"
type: NodePort
selector:
name: jenkins
ports:
-
name: http
port: 80
nodePort: 31006
protocol: TCP
-
name: agent
port: 50000
nodePort: 31007
protocol: TCP
掛載Maven緩存目錄。
[root@dev-master1 kubernetes]# cat m2-pv.yml
m2是Maven的緩存,掛載以提高build速度
apiVersion: v1
kind: PersistentVolume
metadata:
name: maven-m2
spec:
capacity: #指定容量
storage: 200Gi
accessModes:
- ReadWriteOnce #訪問模式,還有ReadOnlyMany ##ReadOnlymany
# persistenVolumeReclaimPolicy: Recycle
# storageClassName: nfs ##指定存儲的類型
nfs:
path: /data/dev_jenkins/m2 #指明NFS的路徑
server: 10.0.0.250 #指明NFS的IP
[root@dev-master1 kubernetes]# cat m2-pvc.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
namespace: kubernetes-plugin
name: maven-m2
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Gi
掛載Slave節(jié)點保存構(gòu)建結(jié)果的目錄。
[root@dev-master1 kubernetes]# cat workspace-pv.yml
m2是maven的緩存,掛載以提高build速度
apiVersion: v1
kind: PersistentVolume
metadata:
name: workspace
spec:
capacity: #指定容量
storage: 200Gi
accessModes:
- ReadWriteOnce #訪問模式,還有ReadOnlyMany ##ReadOnlymany
# persistenVolumeReclaimPolicy: Recycle
# storageClassName: nfs ##指定存儲的類型
nfs:
path: /data/dev_jenkins/workspace #指明NFS的路徑
server: 10.0.0.250 #指明NFS的IP
[root@dev-master1 kubernetes]# cat workspace-pvc.yml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
namespace: kubernetes-plugin
name: workspace
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 200Gi
創(chuàng)建Jenkins的Ingress。因為我的Kubernetes集群里面使用的是Traefik,所以我把Traefik的配置文件和kubernetes-plugin官網(wǎng)給出的Ingress一起貼出來。
[root@dev-master1 kubernetes]# cat jenkins-traefik.yml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins
namespace: kubernetes-plugin
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: jenkins-dev.doudou.com
http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 80
[root@dev-master1 kubernetes]# cat jenkins-Ingress.yml
因為集群使用Traefik所以此Ingress配置文件不創(chuàng)建,此文件為官方原版
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/tls-acme: "true"
# "413 Request Entity Too Large" uploading plugins, increase client_max_body_size
nginx.ingress.kubernetes.io/proxy-body-size: 50m
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
# For nginx-ingress controller < 0.9.0.beta-18
ingress.kubernetes.io/ssl-redirect: "true"
# "413 Request Entity Too Large" uploading plugins, increase client_max_body_size
ingress.kubernetes.io/proxy-body-size: 50m
ingress.kubernetes.io/proxy-request-buffering: "off"
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: jenkins
servicePort: 80
host: jenkins.example.com
tls:
- hosts:
- jenkins.example.com
secretName: tls-jenkins
創(chuàng)建以上的配置文件:
kubectl create namespace kubernetes-plugin #創(chuàng)建kubernetes-plugin namespace,下面創(chuàng)建的所有東西都?xì)w屬到這個namespace
kubectl config set-context $(kubectl config current-context) --namespace=kubernetes-plugin #修改Kubernetes默認(rèn)的namespace為kubernetes-plugin,這樣下面創(chuàng)建的都默認(rèn)為kubernetes-plugin命名空間
kubectl create -f service-account.yml
kubectl create -f jenkins-Ingress.yml
kubectl create -f jenkins-pv.yml
kubectl create -f jenkins-pvc.yml
kubectl create -f jenkins-sv.yml
kubectl create -f jenkins.yml
kubectl create -f m2-pvc.yml
kubectl create -f m2-pv.yml
kubectl create -f workspace-pvc.yml
kubectl create -f workspace-pv.yml
查看創(chuàng)建狀態(tài):
[root@dev-master1 ~]# kubectl get service,pod,StatefulSet -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/jenkins NodePort 10.105.123.193 <none> 80:31006/TCP,50000:31007/TCP 9d name=jenkins
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/jenkins-0 1/1 Running 0 6d5h 100.78.0.141 dev-node4 <none> <none>
NAME READY AGE CONTAINERS IMAGES
statefulset.apps/jenkins 1/1 7d jenkins 10.0.0.59/jenkins/jenkins:lts-alpine
[root@dev-master1 ~]# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/jenkins-home 20Gi RWO Retain Bound kubernetes-plugin/jenkins-home 13d
persistentvolume/maven-m2 200Gi RWO Retain Bound kubernetes-plugin/maven-m2 7d5h
persistentvolume/workspace 200Gi RWO Retain Bound kubernetes-plugin/workspace 7d5h
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/jenkins-home Bound jenkins-home 20Gi RWO 13d
persistentvolumeclaim/maven-m2 Bound maven-m2 200Gi RWO 7d5h
persistentvolumeclaim/workspace Bound workspace 200Gi RWO 7d5h
PV的狀態(tài)為Bound狀態(tài)表示已經(jīng)綁定到對應(yīng)的PVC上。Jenkins的Pod狀態(tài)為1/1就說明啟動成功了,可以通過綁定Ingress的域名訪問了。或者使用Service配置中的nodePort端口訪問Kubernetes任意節(jié)點IP:nodePort。
查看Jenkins密碼:
kubectl exec -it jenkins-0 -n kubernetes-plugin -- cat /var/jenkins_home/secrets/initialAdminPassword
Jenkins配置
Jenkins安裝完成后進入UI界面,首先需要安裝需要的插件。
Jenkins可以根據(jù)實際情況選擇適合的源:
系統(tǒng)管理->插件管理->高級
https://updates.jenkins.io/update-center.json #官方源
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json #清華源
然后安裝需要的插件:
Git pPugin
Maven Integration Plugin
Docker Plugin
Kubernetes Continuous Deploy Plugin
Kubernetes Plugin
Publish Over SSH Plugin
SSH Agent Plugin
SSH Build Agents Plugin
promoted builds plugin
Promoted Builds (Simple)
配置
Kubernetes Plugin插件安裝完成后在Jenkins設(shè)置里面點擊【系統(tǒng)配置】拉到最下面就可以看到一個Cloud。
單擊之,添加一個云:
- 名稱:名字隨便取,后面連接云的時候需要這個名字。
- Kubernetes地址:訪問Kubernetes Master上kube-apiserver服務(wù)的地址。
- Kubernetes命名空間:Jenkins部署在哪個命名空間里面了。
- Jenkins地址:Jenkins訪問地址。
- Jenkins通道(這特么是一個大坑) :訪問Jenkins容器內(nèi)50000端口地址。因為Jenkins的Service配置文件中我把50000端口映射為nodePort,再加上我配置了DNS所以我這里寫了域名:端口號的格式,也可以使用IP地址+端口號。
因為Jenkins-Master和Jenkins-Slave都在Kubernetes集群內(nèi)部,所以寫ClusterIP:端口號應(yīng)該也是可以的,但是我沒試過,略略略:),地址只要能訪問到容器內(nèi)部的50000端口就可以,但是有一點需要注意,這里的格式不能加http不能加/感覺應(yīng)該是協(xié)議的問題,但是還沒搞懂。
點擊連接測試,是否能夠成功。
測試
連接成功后,創(chuàng)建一個流水線Job進行測試使用。
podTemplate(label: 'jnlp-slave', cloud: 'kubernetes', containers: [
containerTemplate(name: 'maven', image: '10.0.0.59/jenkins/maven:3.3.9-jdk-8-alpine', ttyEnabled: true, command: 'cat'),
],
volumes: [
persistentVolumeClaim(mountPath: '/root/.m2', claimName: 'maven-m2'),
persistentVolumeClaim(mountPath: '/home/jenkins/agent/workspace', claimName: 'workspace'),
]
)
{
node("jnlp-slave"){
stage('Build'){
git branch: 'master', url: 'http://root:qrGw1S_azFE3F77Rs7tA@gitlab.gemantic.com/java/$JOB_NAME.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn clean package -U deploy'
}
}
}
stage('deploy'){
sshPublisher(publishers: [sshPublisherDesc(configName: '76', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '/data/script/jenkins.sh $JOB_NAME', execTimeout: 120000000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/data/kubernetes/service/$JOB_NAME', remoteDirectorySDF: false, removePrefix: 'target', sourceFiles: 'target/$JOB_NAME*.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
}
Pipeline解讀:
podTemplate創(chuàng)建了一個Pod模版。Cloud字段指定了連接哪個Kubernetes云,Kubernetes就是剛才創(chuàng)建一個一個Kubernetes,云的名字就是kubernetes。
Maven鏡像為了加快下載速度,我傳到了私有倉庫,官方鏡像就是把IP地址去掉對應(yīng)的鏡像。
persistentVolumeClaim定義了目錄掛載,把Maven構(gòu)建的緩存目錄.m2和構(gòu)建產(chǎn)生的數(shù)據(jù)目錄workspace都掛載了一下
下面的Pipeline指定后面的操作在jnlp-slave中(也就是Pod模版同時也是Slave節(jié)點)
-
在build操作中,需要先拉取代碼,GitLab拉取代碼這里使用了GitLab的root token進行拉取的。GitLab用戶獲取Token方法:
image 下面就是開始編譯啦~,因為是一個Java服務(wù),編譯完成后會生成一個jar包。
-
deploy步驟就是開始發(fā)布了,下面的Pipeline是用流水線語法自動生成的。
image -
然后點擊構(gòu)建進行測試。
image 構(gòu)建過程中,可以看到Pod調(diào)度到master3上進行構(gòu)建了。
構(gòu)建過程中用到了兩個鏡像,一個Maven(已被上傳到了私有倉庫),一個inbound-agent鏡像。inbound-agent鏡像是官方的鏡像,和Maven的關(guān)系是都在同一個Pod中共享數(shù)據(jù),并和Jenkins-master進行交互。(inbound-agent鏡像怎么修改為私有倉庫鏡像還沒搞明白,總是去公網(wǎng)下載速度慢)
構(gòu)建過程中不斷的下載Java程序依賴的各種包,因為是第一次時間久了一點,但是我們已經(jīng)把.m2緩存目錄掛載出來了,下次再次構(gòu)建的時候就可以大大縮減構(gòu)建的時間。
workspace也被掛載了出來,每次構(gòu)建的數(shù)據(jù)也會保留,以備不時之需。
構(gòu)建成功后查看NFS共享目錄中的數(shù)據(jù):
root@sa-storage:/data/dev_jenkins# du -sh m2/
218M m2/
root@sa-storage:/data/dev_jenkins# du -sh workspace/
65M workspace/
至此所有的需求都實現(xiàn)了,Slave實現(xiàn)了動態(tài)伸縮,相關(guān)的目錄都被掛載出來了。
排錯
kubectl get pod -n kubernetes-plugin -o wide命令可以查看Slave的Pod狀態(tài),如果出現(xiàn)問題Slave一直無限重啟,需要查看Pod日志。
kubectl logs `kubectl get pod -n kubernetes-plugin -o wide|grep jnlp-slave|awk '{print $1}'` -n kubernetes-plugin
每次重啟Pod的名字都會重新生成,而且正在創(chuàng)建中的Pod是無法查看日志的,就算有問題Pod也是瞬間就重啟了,所以只能上面的這個命令無限的刷。手速快的可以手動哦~手速跟不上的也可以寫個循環(huán)噠。主要就是文中說的那個大坑,那個坑過去,小問題都可以通過看日志解決的。如果忘記大坑在哪里,可以ctrl+f搜索關(guān)鍵字 “大坑” 哦~