文章

快速搭建高可用CI工具:基于K8S的Jenkins Master-Slave架构

主流CI工具对比

以下持续集成工具本人进行使用后做出的一些总结:

image-20240322213131415

不同项目需要根据企业要求选择合适的CI工具,例如:企业中采购了gitlab作为所有项目的代码仓库,可以尝试使用Gitlab CI。如果采购了Github企业版本,使用GitHub Actions也是一个不错的选择。同理,企业云战略选择Azure云,完全可以尝试Azure DevOps一站式解决Code Repository以及CI Pipeline。不要局限于特定的工具,按需求按场景进行调整即可。

Jenkins是老牌的CI工具,这里先以Jenkins为例,为大家快速演示如何搭建企业级的CI工具。

高可用 Jenkins 集群

高负载的情况下,经常出现不可用等情况时,是非常影响项目团队的开发效率和体验的。那么可以尝试结合K8S来搭建高可用的Jenkins集群。

image-20240322214844581

这里可以看到通过启动Slave Pod来执行不同的Task任务,这样我们可以通过Docker Image来自定义任何想要的镜像,如:支持Java语言、Golang语言、Node等等。甚至可以嵌套容器将MySQL、Redis等有状态的中间件使用容器的方式运行,用来满足执行集成测试或者API测试。

同理,目前很多CI工具也可以看到类似的设计,如:Drone CI工具,也是分为了 Drone Server和Drone Runner。

流水线的执行流程

image-20240322220228738

举个🌰

环境准备

  • Kubernetes 1.26.5

  • Kubectl

  • Helm v3.12.1

  • Text Editor (Yaml)

安装Jenkins Master

官方配置微调

以下是我通过Helm微调后后的Jenkins Master的部署脚本,调整的地方:

1)替换定制化Jenkins镜像:这里替换成我自己定制化的镜像,和官方的区别在于将主要的一些plugins直接打包进了Docker Image中,而不用在Yaml文件中,每次部署都需要重新下载,经常出现网络断开无法下载的情况,非常恶心。

插件列表如下:

 git
 credentials-binding
 configuration-as-code
 build-timestamp
 build-timeout
 htmlpublisher
 build-monitor-plugin
 kubernetes

2)关闭Agent Flag、调整Storage Class、调整javaOpts等,具体可以查看部署文件。

部署脚本

jenkins-local-pv.yaml

 apiVersion: v1
 kind: PersistentVolume
 metadata:
   name: local-pv-jenkins
   labels:
     app: local-pv-jenkins
 spec:
   capacity:
     storage: 8Gi
   volumeMode: Filesystem
   accessModes:
     - ReadWriteOnce
   storageClassName: local-storage
   persistentVolumeReclaimPolicy: Retain
   local:
     path: /Users/eric/Documents/data/jenkins
   nodeAffinity:
     required:
       nodeSelectorTerms:
         - matchExpressions:
             - key: kubernetes.io/hostname
               operator: In
               values:
                 - lima-rancher-desktop # <changeme>

local-jenkins-values.yaml

 ​
 # https://github.com/jenkinsci/helm-charts/blob/main/charts/jenkins/values.yaml
 # helm pull jenkins/jenkins --version 3.0.2 --untar
 ​
 controller:
   componentName: "jenkins-master"
   image: "amuguelove/jenkins"
   tag: "alpine-plugins-20210425"
   imagePullPolicy: "IfNotPresent"
   imagePullSecretName:
 ​
   serviceType: ClusterIP
 ​
   adminSecret: true
   adminUser: "admin"
   # adminPassword: "xxx"   ## <defaults to random>
   admin:
     existingSecret: ""  # changeme
     userKey: jenkins-admin-user
     passwordKey: jenkins-admin-password
 ​
   resources:
     requests:
       cpu: "50m"
       memory: "512Mi"
     limits:
       cpu: "2000m"
       memory: "4096Mi"
 ​
   javaOpts: >-
     -Djenkins.install.runSetupWizard=false
     -Duser.timezone=Asia/Shanghai
     -Dhudson.slaves.NodeProvisioner.initialDelay=0
     -Dhudson.slaves.NodeProvisioner.MARGIN=50
     -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85
     -DsessionTimeout=1440
     -DsessionEviction=43200
 ​
   installPlugins: []
 ​
   JCasC:
     defaultConfig: true
     configScripts: {}
     securityRealm: |-
       local:
         allowsSignup: false
         enableCaptcha: false
         users:
         - id: "${chart-admin-username}"
           name: "Jenkins Admin"
           password: "${chart-admin-password}"
     # Ignored if authorizationStrategy is defined in controller.JCasC.configScripts
     authorizationStrategy: |-
       loggedInUsersCanDoAnything:
         allowAnonymousRead: false
 ​
   ingress:
    enabled: false
 ​
 agent:
   enabled: false
 ​
 persistence:
   enabled: true  # changeme
   storageClass: "local-storage" # changeme
   size: "8Gi"
 ​
 serviceAccount:
   create: true
   name: jenkins-master
 ​
 rbac:
   create: true

执行脚本

 # 准备
 kubectl create namespace jenkins
 kubectl apply -f jenkins-local-pv.yaml
 ​
 # 安装Jenkins
 helm upgrade --install \
   jenkins-master jenkins/jenkins \
   -n jenkins \
   -f local-jenkins-values.yaml \
   --version 3.3.18

执行结果

 Release "jenkins-master" does not exist. Installing it now.
 NAME: jenkins-master
 LAST DEPLOYED: Sat Mar 23 10:06:23 2024
 NAMESPACE: jenkins
 STATUS: deployed
 REVISION: 1
 NOTES:
 1. Get your 'admin' user password by running:
   kubectl exec --namespace jenkins -it svc/jenkins-master -c jenkins -- /bin/cat /run/secrets/chart-admin-password && echo
 2. Get the Jenkins URL to visit by running these commands in the same shell:
   echo http://127.0.0.1:8080
   kubectl --namespace jenkins port-forward svc/jenkins-master 8080:8080
 ​
 3. Login with the password from step 1 and the username: admin
 4. Configure security realm and authorization strategy
 5. Use Jenkins Configuration as Code by specifying configScripts in your values.yaml file, see documentation: http:///configuration-as-code and examples: https://github.com/jenkinsci/configuration-as-code-plugin/tree/master/demos
 ​
 For more information on running Jenkins on Kubernetes, visit:
 https://cloud.google.com/solutions/jenkins-on-container-engine
 ​
 For more information about Jenkins Configuration as Code, visit:
 https://jenkins.io/projects/jcasc/

登陆

 ### 使用host主机8080端口登陆
 kubectl --namespace jenkins port-forward svc/jenkins-master 8080:8080
 ​
 ### 查看admin用户的登陆密码
 kubectl exec --namespace jenkins -it svc/jenkins-master -c jenkins -- /bin/cat /run/secrets/chart-admin-password && echo
 4nrtG6h2l1n3AE25aZt6th

通过http://localhost:8080访问Jenkins web服务了。

配置Jenknis Slave

接下来我们就需要来配置 Jenkins,让它能够动态的生成 Slave 的 Pod。

第一步:安装Kubernetes Plugin(定制化镜像中有,可忽略),点击 Manage Jenkins -> Manage Plugins -> Available -> Kubernetes plugin 勾选安装即可。

jenkins-slave-step1

第二步:填写 Kubernetes Cloud Details,点击 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 选择 Kubernetes

jenkins-slave-step2

这里需要点击Test Connection去验证是否可以连接你的Kubernetes集群。

第三步:填写Pod Template和Container Template

jenkins-slave-step3注意这里的名称最好都一致,例如我这边都使用jnlp-agent,之前因为名字不同导致无法完美运行。

第四步:添加卷信息

jenkins-slave-step4

可以看到这里分别挂载了主机的docker,kubectl等工具,其中Mount path根据实际情况进行调整。

如果碰到没有权限执行/var/run/docker.sock,可登陆到对应的node节点或者本地机器进行授权:

 chmod 666 /var/run/docker.sock

第五步:配置权限

image-20240323111944631

这里填写jenkins-master ,是我们在jenkins master中创建的一个Service Account。如果不填的话,会导致执行kubectl命令会出现权限不足。

验证

新建一个Freestyle的Item:

jenkins-slave-test1

填写在Container Template中的Label:

jenkins-slave-test2

添加一个Build Shell:

 echo '=== 测试动态jenkins slave === '
 ​
 echo '=== 打印docker信息 ==='
 docker info
 ​
 echo '=== 获取pods ==='
 kubectl get pods

jenkins-slave-test3

在Jenkins web中点击立即构建Build Now,在控制观察kuberntes Pod的状态:

jenkins-slave-test4

执行完毕后,控制台会有如下输出:

 Building remotely on jnlp-agent-l58sk (jnlp-agent) in workspace /home/jenkins/agent/workspace/jnlp-demo
 [jnlp-demo] $ /bin/sh -xe /tmp/jenkins4069627081916824161.sh
 + echo === 测试动态jenkins slave === 
 === 测试动态jenkins slave === 
 + echo === 打印docker信息 ===
 === 打印docker信息 ===
 + docker info
 Containers: 20
  Running: 19
  Paused: 0
  Stopped: 1
 Images: 13
 Server Version: 18.06.3-ce
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Plugins:
  Volume: local
  Network: bridge host macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
 Swarm: inactive
 Runtimes: runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: e6b3f5632f50dbc4e9cb6288d911bf4f5e95b18e (expected: 468a545b9edcd5932818eb9de8e72413e616e86e)
 runc version: f56b4cbeadc407e715d9b2ba49e62185bd81cef4 (expected: a592beb5bc4c4092b1b1bac971afed27687340c5)
 init version: fec3683
 Security Options:
  seccomp
   Profile: default
 Kernel Version: 4.14.105-19-0008
 Operating System: CentOS Linux 7 (Core)
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 3.476GiB
 Name: VM_0_12_centos
 ID: 5EKE:FP2N:QFR7:BHIV:5LLJ:OUUK:CDV6:PRPI:5TW2:GAKT:JCMH:OFF3
 Docker Root Dir: /var/lib/docker
 Debug Mode (client): false
 Debug Mode (server): false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Registry Mirrors:
  https://mirror.ccs.tencentyun.com/
 Live Restore Enabled: true
 ​
 + echo === 获取pods ===
 === 获取pods ===
 + kubectl get pods
 NAME                       READY   STATUS    RESTARTS   AGE
 jenkins-587dc776c6-79hlg   1/1     Running   0          23m
 jnlp-agent-l58sk           2/2     Running   0          17s
 Finished: SUCCESS

到这里基于Kubernetes的动态Jenkins Slave就搭建完毕了。这里演示了一个简单的demo,如果在项目中有Jenkinsfile,可以定义node属性,指定使用jnlp-agent即可使用slave构建你的项目了。

可能碰到的坑与解决思路

  1. Slave Pod执行完任务后,会进行Pod销毁,这时在CI过程中所产生的数据也将丢失,如:流水线过程中的各种报告(Jacoco覆盖率报告、测试报告等),需要将这些有用的数据发布到Master上。

  2. 每次执行一次流水线,编译代码时,前后端的pod都会去下载很多依赖,如:Jar,npm文件等,如果每次Slave Pod启动都要重新下载这些文件,那么流水线的执行时长将远比虚拟机部署方式要长,注意这些依赖文件的缓存,每次Slave Pod启动能够访问到这些文件。

  3. 云厂商的选择上,PV挂载的远程云磁盘,是否支持在线扩容?

License:  CC BY 4.0