使用Jenkins Operator在k8s中部署Jenkins
目錄
1、Jenkins Operator 介紹
2、Jenkins Operator 的架構(gòu)和設(shè)計(jì)
3、使用 Operator 部署 Jenkins
3.1 前提條件
3.2 獲取并創(chuàng)建 CRD
3.3 部署 Jenkins Operator
3.4 部署 Jenkins

1、Jenkins Operator 介紹
Kubernetes Operator[1]是一種特定于應(yīng)用的控制器,可擴(kuò)展Kubernetes API的功能,來代表Kubernetes用戶創(chuàng)建、配置和管理復(fù)雜應(yīng)用的實(shí)例
這里對(duì)Operator的相關(guān)介紹就不多贅述了,主要還是回到Jenkins Operator這個(gè)話題
基于k8s上Jenkins的常規(guī)安裝是使用yaml資源清單,更為方便一點(diǎn)的是helm chart,但是常常我們?cè)诎惭b后還需要做很多的動(dòng)作。例如插件問題,這盡管可以通過Configuration as Code的方式來解決,根據(jù)個(gè)人實(shí)際經(jīng)驗(yàn)來看,還是存在一定幾率會(huì)因主鏡像版本、環(huán)境等存在諸多不可預(yù)知的問題。當(dāng)然不同的部署方式都各有利弊,大家根據(jù)實(shí)際情況選擇即可。
直到官方支持Jenkins可以在k8s中通過Operator方式部署,在4月中旬,Jenkins blog[2]說道:Jenkins Operator 正式成為了 Jenkins 的子項(xiàng)目,填補(bǔ)了Jenkins與Kubernetes間的縫隙。也就是說,最初由(個(gè)人)三方團(tuán)隊(duì)編寫的Jenkins Operator被Jenkins官方認(rèn)可了
參考官方說明,Jenkins Operator可以幫我們解決以下問題:
安裝指定版本的插件
即使最新版本插件不兼容或具備安全漏洞,還是為了插件穩(wěn)定性而使用(因?yàn)槌3?huì)出現(xiàn)我們通過一鍵升級(jí)插件導(dǎo)致很多問題而去手動(dòng)安裝舊版本插件的情況)
更好的自定義配置
包含在安裝指定版本插件時(shí)指定插件配置等聲明式配置
開箱即用的安全配置
可靈活調(diào)整的
debug錯(cuò)誤調(diào)試備份和還原作業(yè)歷史記錄
......
2、Jenkins Operator 的架構(gòu)和設(shè)計(jì)

參考Jenkins Operator Architecture and design[3]
Jenkins Operator的設(shè)計(jì)包含以下概念
監(jiān)視清單的任何更改,并根據(jù)已部署的自定義資源清單維護(hù)所需的狀態(tài) 實(shí)現(xiàn)主 reconciliation循環(huán),由兩個(gè)較小的reconciliation循環(huán):base和user
Base reconciliation 循環(huán)負(fù)責(zé)監(jiān)聽Jenkins基礎(chǔ)配置:
確認(rèn)清單-監(jiān)聽清單中發(fā)生的任何更改 確保 Jenkins Pod狀態(tài),創(chuàng)建和驗(yàn)證Jenkins Server Pod的狀態(tài)確認(rèn) Jenkins的配置,包括安全加固、初始化配置等確認(rèn) Jenkins API token,生成token并初始化Jenkins Client
User reconciliation循環(huán)負(fù)責(zé)協(xié)調(diào)用戶提供的配置:
確保恢復(fù)任務(wù),創(chuàng)建恢復(fù)任務(wù),并確保恢復(fù)已成功執(zhí)行 確保 Seed Jobs,創(chuàng)建Seed Jobs并確保所有這些工作都已成功執(zhí)行確保用戶配置,執(zhí)行用戶提供的配置,如 groovy腳本,配置為代碼或插件確保備份任務(wù),創(chuàng)建備份任務(wù)并確保備份成功

Operator狀態(tài)Operator狀態(tài)保存在自定義資源狀態(tài)部分中,該部分用于存儲(chǔ)Operator管理的任何配置事件或Job狀態(tài)
即使操作者或Jenkins重新啟動(dòng),它也能幫助保持或恢復(fù)所需的狀態(tài)
3、使用 Operator 部署 Jenkins
3.1 前提條件
參考Jenkins Operator官方文檔[4],需要有一個(gè)1.11+版本的Kubernetes集群,這里我的環(huán)境如下
# kubectl version -o yaml
clientVersion:
buildDate: "2020-12-08T17:59:43Z"
compiler: gc
gitCommit: af46c47ce925f4c4ad5cc8d1fca46c7b77d13b38
gitTreeState: clean
gitVersion: v1.20.0
goVersion: go1.15.5
major: "1"
minor: "20"
platform: darwin/amd64
serverVersion:
buildDate: "2021-01-13T13:20:00Z"
compiler: gc
gitCommit: faecb196815e248d3ecfb03c680a4507229c2a56
gitTreeState: clean
gitVersion: v1.20.2
goVersion: go1.15.5
major: "1"
minor: "20"
platform: linux/amd64
3.2 獲取并創(chuàng)建 CRD
獲取yaml并創(chuàng)建crd,當(dāng)然也可以通過直接apply遠(yuǎn)程地址,這里先將其保存到本地
# wget -c https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml
# kubectl apply -f jenkins_v1alpha2_jenkins_crd.yaml
Warning: apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition
customresourcedefinition.apiextensions.k8s.io/jenkins.jenkins.io created
customresourcedefinition.apiextensions.k8s.io/jenkinsimages.jenkins.io created
3.3 部署 Jenkins Operator
有以下兩種方式部署Jenkins Operator
使用
yaml一鍵安裝,默認(rèn)將安裝在default命名空間下# kubectl apply -f https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/deploy/all-in-one-v1alpha2.yaml
# kubectl get pods -w使用
helm并自定義安裝,依賴helm在v3以上版本
創(chuàng)建ns
# kubectl create ns jenkins
添加helm倉(cāng)庫(kù)并獲取chart
# helm repo add jenkins https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart
"jenkins" has been added to your repositories
# helm pull jenkins/jenkins-operator
# tar xf jenkins-operator-0.4.3.tgz && cd jenkins-operator
修改value yaml部分內(nèi)容,可以定義關(guān)于jenkins實(shí)例、operator deployment、backup備份相關(guān)、Configuration配置相關(guān)字段
指定 ns 指定插件 默認(rèn)情況只持久化了備份卷,這里將數(shù)據(jù)卷也做持久化, sc使用csi-rbd-sc默認(rèn)開啟 configurationAsCode,并通過configmap和secret注入
jenkins:
...
namespace: jenkins
...
basePlugins:
- name: kubernetes
version: "1.28.6"
- name: workflow-job
version: "2.40"
- name: workflow-aggregator
version: "2.6"
- name: git
version: "4.5.0"
- name: job-dsl
version: "1.77"
- name: configuration-as-code
version: "1.46"
- name: kubernetes-credentials-provider
version: "0.15"
plugins:
- name: simple-theme-plugin
version: "0.6"
# plugins: []
...
# volumes used by Jenkins
# By default, we are only using backup
volumes:
- name: backup # PVC volume where backups will be stored
persistentVolumeClaim:
claimName: jenkins-backup
# volumeMounts are mounts for Jenkins pod
volumeMounts: []
...
backup:
# enabled is enable/disable switch for backup feature
# By default the feature is enabled
enabled: true
# image used by backup feature
# By default using prebuilt backup PVC image by VirtusLab
image: virtuslab/jenkins-operator-backup-pvc:v0.1.0
# containerName is backup container name
containerName: backup
# interval defines how often make backup in seconds
interval: 30
# makeBackupBeforePodDeletion when enabled will make backup before pod deletion
makeBackupBeforePodDeletion: true
# backupCommand is backup container command
backupCommand:
- /home/user/bin/backup.sh
# restoreCommand is backup restore command
restoreCommand:
- /home/user/bin/restore.sh
getLatestAction:
- /home/user/bin/get-latest.sh
# pvc is Persistent Volume Claim Kubernetes resource
pvc:
# enabled is enable/disable switch for PVC
enabled: true
# size is size of PVC
size: 5Gi
# className is storageClassName for PVC
# See https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1 for more details
className: "csi-rbd-sc"
# env contains container environment variables
# PVC backup provider handles these variables:
# BACKUP_DIR - path for storing backup files (default: "/backup")
# JENKINS_HOME - path to jenkins home (default: "/jenkins-home")
# BACKUP_COUNT - define how much recent backups will be kept
env:
- name: BACKUP_DIR
value: /backup
- name: JENKINS_HOME
value: /jenkins-home
- name: BACKUP_COUNT
value: "3" # keep only the 3 most recent backups
# volumeMounts holds the mount points for volumes
volumeMounts:
- name: jenkins-home
mountPath: /jenkins-home # Jenkins home volume
- mountPath: /backup # backup volume
name: backup
...
configuration:
configurationAsCode: {}
# - configMapName: jenkins-casc
# content: {}
groovyScripts: {}
# - configMapName: jenkins-gs
# content: {}
# secretRefName of existing secret (previously created)
secretRefName: ""
# secretData creates new secret if secretRefName is empty and fills with data provided in secretData
secretData: {}
執(zhí)行安裝
# helm install jenkins jenkins-operator -n jenkins --values ./jenkins-operator/values.yaml
NAME: jenkins
LAST DEPLOYED: Sun May 16 19:42:32 2021
NAMESPACE: jenkins
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Watch Jenkins instance being created:
$ kubectl --namespace jenkins get pods -w
2. Get Jenkins credentials:
$ kubectl --namespace jenkins get secret jenkins-operator-credentials-jenkins -o 'jsonpath={.data.user}' | base64 -d
$ kubectl --namespace jenkins get secret jenkins-operator-credentials-jenkins -o 'jsonpath={.data.password}' | base64 -d
3. Connect to Jenkins (actual Kubernetes cluster):
$ kubectl --namespace jenkins port-forward jenkins-jenkins 8080:8080
Now open the browser and enter http://localhost:8080
檢查創(chuàng)建的operator
# kubectl get pods -n jenkins
NAME READY STATUS RESTARTS AGE
jenkins-jenkins 1/2 Running 0 44s
jenkins-jenkins-operator-996887c4b-wftz2 1/1 Running 0 1m29s
# kubectl -n jenkins get jenkins
NAME AGE
jenkins 70s
3.4 部署 Jenkins
一旦上面的Jenkins Operator部署后啟動(dòng)并正常運(yùn)行,就自動(dòng)會(huì)部署一個(gè)Jenkins實(shí)例Pod了
實(shí)際上可以看到,通過Jenkins Operator部署的Jenkins的控制器不是場(chǎng)景k8s自帶的三大控制器,而是由operator自己管控
觀察operator的日志如下
# kubectl -n jenkins logs -f jenkins-jenkins-operator-996887c4b-wftz2
2021-05-16T11:59:05.017Z INFO controller-jenkins jenkins/jenkins_controller.go:432 Setting default Jenkins API settings {"cr": "jenkins"}
2021-05-16T11:59:05.073Z INFO controller-jenkins jenkins/handler.go:88 *v1alpha2.Jenkins/jenkins has been updated {"cr": "jenkins"}
2021-05-16T11:59:06.568Z INFO controller-jenkins base/pod.go:159 Creating a new Jenkins Master Pod jenkins/jenkins-jenkins {"cr": "jenkins"}
觀察jenkins pod中jenkins master的日志如下,正在下載插件(此步驟稍慢)
# kubectl -n jenkins logs -f jenkins-jenkins -c jenkins-master
...
> bootstrap4-api depends on font-awesome-api:5.15.2-2,jquery3-api:3.5.1-3,popper-api:1.16.1-2
Downloading plugin: font-awesome-api from https://updates.jenkins.io/dynamic-2.263//latest/font-awesome-api.hpi
Downloading plugin: jquery3-api from https://updates.jenkins.io/dynamic-2.263//latest/jquery3-api.hpi
Downloading plugin: popper-api from https://updates.jenkins.io/dynamic-2.263//latest/popper-api.hpi
如果在有限時(shí)間(健康檢查時(shí)間)內(nèi)沒有下載成功,這通常是由于網(wǎng)絡(luò)原因引起的,Operator會(huì)中斷該Pod并重新創(chuàng)建
# kubectl -n jenkins logs -f jenkins-jenkins-operator-996887c4b-wftz2
2021-05-16T12:09:42.854Z INFO controller-jenkins base/reconcile.go:370 Container 'jenkins-master' is terminated, status '{Name:jenkins-master State:{Waiting:nil Running:nil Terminated:&ContainerStateTerminated{ExitCode:137,Signal:0,Reason:Error,Message:,StartedAt:2021-05-16 12:05:54 +0000 UTC,FinishedAt:2021-05-16 12:09:42 +0000 UTC,ContainerID:docker://342349d9e4045cd312345937797a2d9f048f3623fc1ce6bf1c2b77ff4f04d8da,}} LastTerminationState:{Waiting:nil Running:nil Terminated:nil} Ready:false RestartCount:0 Image:jenkins/jenkins:2.263.2-lts-alpine ImageID:docker-pullable://jenkins/jenkins@sha256:496142509b7d3e3f22f5cdc81b1d1322db61ec929d34dfd66b9ec3257bca13e5 ContainerID:docker://342349d9e4045cd312345937797a2d9f048f3623fc1ce6bf1c2b77ff4f04d8da Started:0xc0005aa2c6}' {"cr": "jenkins"}
可行的一個(gè)解決辦法是將value.yaml中的健康檢查時(shí)間微調(diào)或者臨時(shí)去掉健康檢查,并helm更新讓其正常啟動(dòng)并持久化后再次恢復(fù),或者新創(chuàng)建一個(gè)Jenkins控制器將其覆蓋
# helm -n jenkins upgrade jenkins jenkins-operator --values ./jenkins-operator/values.yaml
最終直到看見這樣的日志,就表示Jenkins啟動(dòng)成功了
2021-05-16 13:26:14.221+0000 [id=28] INFO o.s.c.s.AbstractApplicationContext#obtainFreshBeanFactory: Bean factory for application context [org.springframework.web.context.support.StaticWebApplicationContext@295b7e33]: org.springframework.beans.factory.support.DefaultListableBeanFactory@52880f75
2021-05-16 13:26:14.223+0000 [id=28] INFO o.s.b.f.s.DefaultListableBeanFactory#preInstantiateSingletons: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@52880f75: defining beans [filter,legacy]; root of factory hierarchy
2021-05-16 13:26:14.489+0000 [id=29] INFO jenkins.InitReactorRunner$1#onAttained: Completed initialization
2021-05-16 13:26:14.767+0000 [id=20] INFO hudson.WebAppMain$3#run: Jenkins is fully up and running
到這里,通過Jenkins Operator部署Jenkins就完成了(盡管看上去也沒多少比helm或傳統(tǒng)方式部署的優(yōu)勢(shì)),其實(shí)Jenkins Operator還有更為好用的的其他功能,后續(xù)再介紹
See you ~
參考資料
Kubernetes Operator: https://www.redhat.com/zh/topics/containers/what-is-a-kubernetes-operator
[2]Jenkins blog: https://www.jenkins.io/blog/2021/04/15/jenkins-operator-sub-project/
[3]Jenkins Operator Architecture and design: https://jenkinsci.github.io/kubernetes-operator/docs/how-it-works/architecture-and-design/
[4]Jenkins Operator官方文檔: https://jenkinsci.github.io/kubernetes-operator/docs/
