kubernetes(k8s) jenkins CI/CD(動(dòng)態(tài)創(chuàng)建slave)

jenkins CI/CD(動(dòng)態(tài)創(chuàng)建slave)簡述:

持續(xù)構(gòu)建與發(fā)布是我們?nèi)粘9ぷ髦斜夭豢缮俚囊粋€(gè)步驟,目前大多公司都采用 Jenkins 集群來搭建符合需求的 CI/CD 流程,然而傳統(tǒng)的 Jenkins Slave 一主多從方式會(huì)存在一些痛點(diǎn),比如:

  • 主 Master 發(fā)生單點(diǎn)故障時(shí),整個(gè)流程都不可用了
  • 每個(gè) Slave 的配置環(huán)境不一樣,來完成不同語言的編譯打包等操作,但是這些差異化的配置導(dǎo)致管理起來非常不方便,維護(hù)起來也是比較費(fèi)勁
  • 資源分配不均衡,有的 Slave 要運(yùn)行的 job 出現(xiàn)排隊(duì)等待,而有的 Slave 處于空閑狀態(tài)
  • 資源有浪費(fèi),每臺(tái) Slave 可能是物理機(jī)或者虛擬機(jī),當(dāng) Slave 處于空閑狀態(tài)時(shí),也不會(huì)完全釋放掉資源。

正因?yàn)樯厦娴倪@些種種痛點(diǎn),我們渴望一種更高效更可靠的方式來完成這個(gè) CI/CD 流程,而 Docker 虛擬化容器技術(shù)能很好的解決這個(gè)痛點(diǎn),又特別是在 Kubernetes 集群環(huán)境下面能夠更好來解決上面的問題,下圖是基于 Kubernetes 搭建 Jenkins 集群的簡單示意圖:
k8s-jenkins

從圖上可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式運(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)行在 Pod 中的 Jenkins Slave 并注冊(cè)到 Master 上,當(dāng)運(yùn)行完 Job 后,這個(gè) Slave 會(huì)被注銷并且這個(gè) Pod 也會(huì)自動(dòng)刪除,恢復(fù)到最初狀態(tài)。

使用jenkins動(dòng)態(tài)slave的優(yōu)勢:

  • 服務(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

1、創(chuàng)建一個(gè)kube-ops的 namespace(為了方便管理):

$ kubectl create namespace kube-ops

2、創(chuàng)建pv、pvc或使用storageclass都可以,本實(shí)驗(yàn)使用前者(pvc.yaml):
注:下面使用的是nfs的存儲(chǔ)方式,詳情連接:http://www.itdecent.cn/p/a1089f9bb36e

apiVersion: v1
kind: PersistentVolume
metadata:
  name: opspv
spec:
  capacity:
    storage: 20Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Delete
  nfs:
    server: 10.8.13.211
    path: /data/cmp

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: opspvc
  namespace: kube-ops
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 20Gi

3、創(chuàng)建需要用到的 PVC 對(duì)象:

$ kubectl create -f pvc.yaml

4、給jenkins綁定權(quán)限(rbac.yaml),如果對(duì)rbac不熟悉,可以先給定cluster-admin權(quán)限

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: kube-ops

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins
rules:
  - apiGroups: ["extensions", "apps"]
    resources: ["deployments"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
  - 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: ["secrets"]
    verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: jenkins
  namespace: kube-ops
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins
    namespace: kube-ops

5、創(chuàng)建 rbac 相關(guān)的資源對(duì)象:

$ kubectl create -f rbac.yaml
serviceaccount "jenkins" created
role.rbac.authorization.k8s.io "jenkins" created
rolebinding.rbac.authorization.k8s.io "jenkins" created

6、新建一個(gè) Deployment:(jenkins.yaml)

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins
  namespace: kube-ops
spec:
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccount: jenkins
      containers:
      - name: jenkins
        image: jenkins/jenkins:lts
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources:
          limits:
            cpu: 1000m
            memory: 1Gi
          requests:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        readinessProbe:
          httpGet:
            path: /login
            port: 8080
          initialDelaySeconds: 60
          timeoutSeconds: 5
          failureThreshold: 12
        volumeMounts:
        - name: jenkinshome
          subPath: jenkins
          mountPath: /var/jenkins_home
        env:
        - name: LIMITS_MEMORY
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
              divisor: 1Mi
        - name: JAVA_OPTS
          value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
      securityContext:
        fsGroup: 1000
      volumes:
      - name: jenkinshome
        persistentVolumeClaim:
          claimName: opspvc

---
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  namespace: kube-ops
  labels:
    app: jenkins
spec:
  selector:
    app: jenkins
  type: NodePort
  ports:
  - name: web
    port: 8080
    targetPort: web
    nodePort: 30002
  - name: agent
    port: 50000
    targetPort: agent

這里為了方便,通過 NodePort 的形式來暴露 Jenkins 的 web 服務(wù),固定為30002端口,另外還需要暴露一個(gè) agent 的端口,這個(gè)端口主要是用于 Jenkins 的 master 和 slave 之間通信使用的。
創(chuàng)建 Jenkins 服務(wù):

$ kubectl create -f jenkins.yaml
deployment.extensions "jenkins" created
service "jenkins" created

排錯(cuò):

創(chuàng)建完成后,要去拉取鏡像可能需要等待一會(huì)兒,查看下 Pod 的狀態(tài):

$ kubectl get pods -n kube-ops
NAME                        READY     STATUS    RESTARTS   AGE
jenkins-7f5494cd44-pqpzs   0/1       Running   0          2m

可以看到該 Pod 處于 Running 狀態(tài),但是 READY 值確為0,然后我們用 describe 命令去查看下該 Pod 的詳細(xì)信息:

$ kubectl describe pod jenkins-7f5494cd44-pqpzs -n kube-ops
...
Normal   Created                3m                kubelet, node01    Created container
  Normal   Started                3m                kubelet, node01    Started container
  Warning  Unhealthy              1m (x10 over 2m)  kubelet, node01    Liveness probe failed: Get http://10.244.1.165:8080/login: dial tcp 10.244.1.165:8080: getsockopt: connection refused
  Warning  Unhealthy              1m (x10 over 2m)  kubelet, node01    Readiness probe failed: Get http://10.244.1.165:8080/login: dial tcp 10.244.1.165:8080: getsockopt: connection refused

可以看到上面的 Warning 信息,健康檢查沒有通過,具體原因是什么引起的呢?可以通過查看日志進(jìn)一步了解:

$ kubectl logs -f jenkins-7f5494cd44-pqpzs -n kube-ops
touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied
Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

很明顯可以看到上面的錯(cuò)誤信息,意思就是我們沒有權(quán)限在 jenkins 的 home 目錄下面創(chuàng)建文件,這是因?yàn)槟J(rèn)的鏡像使用的是 jenkins 這個(gè)用戶,而我們通過 PVC 掛載到 nfs 服務(wù)器的共享數(shù)據(jù)目錄下面卻是 root 用戶的,所以沒有權(quán)限訪問該目錄,要解決該問題,也很簡單,我只需要在 nfs 共享數(shù)據(jù)目錄下面把我們的目錄權(quán)限重新分配下即可:

$ chown -R 1000 /data/cmp/jenkins

然后重新創(chuàng)建:

$ kubectl delete -f jenkins.yaml
deployment.extensions "jenkins" deleted
service "jenkins" deleted
$ kubectl create -f jenkins.yaml
deployment.extensions "jenkins" created
service "jenkins" created

現(xiàn)在再去查看新生成的 Pod 已經(jīng)沒有錯(cuò)誤信息了:

$ kubectl get pods -n kube-ops
NAME                        READY     STATUS        RESTARTS   AGE
jenkins-7f5494cd44-smn2r   1/1       Running       0          25s

等到服務(wù)啟動(dòng)成功后,可以根據(jù)Node節(jié)點(diǎn)的 IP:30002 端口就可以訪問 jenkins 服務(wù)了,可以根據(jù)提示信息進(jìn)行安裝配置即可:
setup jenkins

初始化的密碼可以在 jenkins 的容器的日志中進(jìn)行查看,也可以直接在 nfs 的共享數(shù)據(jù)目錄中查看:

$ cat /data/cmp/jenkins/secrets/initAdminPassword

然后選擇安裝推薦的插件即可。
setup plugin

安裝完成后添加管理員帳號(hào)即可進(jìn)入到 jenkins 主界面:
jenkins home

二、配置jenkins動(dòng)態(tài)slave

第1步. 需要安裝kubernetes plugin, 點(diǎn)擊 Manage Jenkins -> Manage Plugins -> Available -> Kubernetes plugin 勾選安裝即可。(如果搜索沒有Kubernetes plugin,即選擇Kubernetes),然后重啟jenkins,使之生效。

image.png

第2步. 安裝完畢后,點(diǎn)擊 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 選擇 Kubernetes,然后填寫 Kubernetes 和 Jenkins 配置信息。

1.jpg

注意 namespace,我們這里填 kube-ops,然后點(diǎn)擊Test Connection,如果出現(xiàn) Connection test successful 的提示信息證明 Jenkins 已經(jīng)可以和 Kubernetes 系統(tǒng)正常通信了,然后下方的 Jenkins URL 地址:http://jenkins.kube-ops.svc.cluster.local:8080,這里的格式為:服務(wù)名.namespace.svc.cluster.local:8080,根據(jù)上面創(chuàng)建的jenkins 的服務(wù)名填寫,我這里是之前創(chuàng)建的名為jenkins2,如果是用上面創(chuàng)建的就應(yīng)該是jenkins

另外需要注意,如果這里 Test Connection 失敗的話,很有可能是權(quán)限問題,這里就需要把 jenkins 的 serviceAccount 對(duì)應(yīng)的 secret 添加到這里的 Credentials 里面。

第3步. 配置 Pod Template,其實(shí)就是配置 Jenkins Slave 運(yùn)行的 Pod 模板,命名空間同樣使用 kube-ops,Labels 這里也非常重要,對(duì)于后面執(zhí)行 Job 的時(shí)候需要用到該值,這里使用的是 cnych/jenkins:jnlp 這個(gè)鏡像,這個(gè)鏡像是在官方的 jnlp 鏡像基礎(chǔ)上定制的,加入了 kubectl 等一些實(shí)用的工具。

2.jpg

需要在下面掛載兩個(gè)主機(jī)目錄,一個(gè)是/var/run/docker.sock,該文件是用于 Pod 中的容器能夠共享宿主機(jī)的 Docker,使用 docker in docker 的方式,Docker 二進(jìn)制文件已經(jīng)打包到上面的鏡像中了,另外一個(gè)目錄下/root/.kube目錄,將這個(gè)目錄掛載到容器的/root/.kube目錄下面這是為了能夠在 Pod 的容器中能夠使用 kubectl 工具來訪問 Kubernetes 集群,方便后面在 Slave Pod 部署 Kubernetes 應(yīng)用。
3.jpg

另外還有幾個(gè)參數(shù)需要注意,如下圖中的Time in minutes to retain slave when idle,這個(gè)參數(shù)表示的意思是當(dāng)處于空閑狀態(tài)的時(shí)候保留 Slave Pod 多長時(shí)間,這個(gè)參數(shù)最好保存默認(rèn)就行了,如果你設(shè)置過大的話,Job 任務(wù)執(zhí)行完成后,對(duì)應(yīng)的 Slave Pod 就不會(huì)立即被銷毀刪除。(如沒有,請(qǐng)忽略))
kubernetes plugin config4

另外在配置了后運(yùn)行 Slave Pod 的時(shí)候出現(xiàn)了權(quán)限問題,因?yàn)?Jenkins Slave Pod 中沒有配置權(quán)限,所以需要配置上 ServiceAccount,在 Slave Pod 配置的地方點(diǎn)擊下面的高級(jí),添加上對(duì)應(yīng)的 ServiceAccount 即可:
kubernetes plugin config5

還有一個(gè)問題在配置完成后發(fā)現(xiàn)啟動(dòng) Jenkins Slave Pod 的時(shí)候,出現(xiàn) Slave Pod 連接不上,然后嘗試100次連接之后銷毀 Pod,然后會(huì)再創(chuàng)建一個(gè) Slave Pod 繼續(xù)嘗試連接,無限循環(huán),類似于下面的信息:(如沒有,請(qǐng)忽略)
image

如果出現(xiàn)這種情況的話就需要將 Slave Pod 中的運(yùn)行命令和參數(shù)兩個(gè)值給清空掉
image

到這里 Kubernetes Plugin 插件就算配置完成了。

三、測試

Kubernetes 插件的配置工作完成了,接下來添加一個(gè) Job 任務(wù),看是否能夠在 Slave Pod 中執(zhí)行,任務(wù)執(zhí)行完成后看 Pod 是否會(huì)被銷毀。

在 Jenkins 首頁點(diǎn)擊create new jobs,創(chuàng)建一個(gè)測試的任務(wù),輸入任務(wù)名稱,然后我們選擇 Freestyle project 類型的任務(wù):
注意在下面的 Label Expression 這里要填入hwzx-cmp,就是前面我們配置的 Slave Pod 中的 Label,這兩個(gè)地方必須保持一致


5.jpg

然后往下拉,在 Build 區(qū)域選擇Execute shell


6.jpg

然后輸入測試命令
echo "測試 Kubernetes 動(dòng)態(tài)生成 jenkins slave"
echo "==============docker in docker==========="
docker info

echo "=============kubectl============="
kubectl get pods
7.jpg

現(xiàn)在直接在頁面點(diǎn)擊做成的 Build now 觸發(fā)構(gòu)建即可,然后觀察 Kubernetes 集群中 Pod 的變化

$ kubectl get pod -n kube-ops -o wide
NAME                        READY   STATUS              RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
jenkins-7cc9df89dd-bwqwd   1/1     Running             0          59m   172.20.4.14   10.8.13.85   <none>           <none>
jnlp-8s1p4                  0/1     ContainerCreating   0          53s   <none>        10.8.13.84   <none>           <none>

可以看到在擊立刻構(gòu)建的時(shí)候可以看到一個(gè)新的 Pod:jnlp-8s1p4 被創(chuàng)建了,這就是我們的 Jenkins Slave。任務(wù)執(zhí)行完成后我們可以看到任務(wù)信息,比如這里是 花費(fèi)了 5.2s 時(shí)間在jnlp-8s1p4 這個(gè) Slave上面
jnlp slave

同樣也可以查看到對(duì)應(yīng)的控制臺(tái)信息:


image.png

image.png

到這里證明任務(wù)已經(jīng)構(gòu)建完成,然后這個(gè)時(shí)候再去集群查看 Pod 列表,發(fā)現(xiàn) kube-ops 這個(gè) namespace 下面已經(jīng)沒有之前的 Slave 這個(gè) Pod 了。
$ kubectl get pod -n kube-ops -o wide
NAME                        READY   STATUS              RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
jenkins-7cc9df89dd-bwqwd   1/1     Running             0          59m   172.20.4.14   10.8.13.85   <none>           <none>

到這里就完成了使用 Kubernetes 動(dòng)態(tài)生成 Jenkins Slave 的方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容