因为项目需要,在GKE的集群上需要创建一个CICD的环境,记录一下安装部署一个分布式Jenkins集群的过程。
分布式Jenkins由一个主服务器和多个Agent组成,Agent可以执行主服务器分派的任务。如下图所示:

如上图,Jenkins Agent可以运行不同的操作系统,执行主服务器分派的编译打包或测试等任务。
在Jenkins的官网上介绍了在K8S上安装的几种方式,包括了Helm, operator等。但是我直接用Helm最新版的Jenkins安装始终遇到问题,日志报错信息是Google的OAUTH插件编译有问题,如果用旧版本的可以安装成功,但是我不想用旧的版本,而新版本的问题暂时没找到解决的思路,另外Helm安装的values配置过于繁杂。我考虑用最传统的Yaml manifest的方式来部署这个Jenkins。
首先是创建namespace的manifest,配置如下:
- apiVersion: v1
- kind: Namespace
- metadata:
- name: "jenkins"
- labels:
- name: "jenkins"
定义Storage class的manifest
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- name: storage-jenkins
- provisioner: kubernetes.io/gce-pd
- parameters:
- type: pd-standard
- fstype: ext4
- volumeBindingMode: WaitForFirstConsumer
定义PVC,保存Jenkins的相关数据
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: jenkins-pvc
- namespace: jenkins
- spec:
- accessModes:
- - ReadWriteOnce
- storageClassName: "storage-jenkins"
- resources:
- requests:
- storage: 30Gi
再创建多一个PVC,给Jenkins Agent用来保存下载的文件,例如maven的仓库,这样可以加快Build job的执行速度。
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: maven-repo-storage
- namespace: jenkins
- spec:
- accessModes:
- - ReadWriteOnce
- storageClassName: "storage-jenkins"
- resources:
- requests:
- storage: 10Gi
然后就是创建一个deployment,部署Jenkins pod,注意在containers的env里面增加了一个环境变量,定义了jenkins uri的前缀,然后在probe的path里也相应的加了/jenkins前缀。
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: jenkins
- namespace: jenkins
- spec:
- selector:
- matchLabels:
- app: jenkins
- replicas: 1
- template:
- metadata:
- labels:
- app: jenkins
- spec:
- volumes:
- - name: pvc-jenkins
- persistentVolumeClaim:
- claimName: jenkins-pvc
- containers:
- - name: jenkins
- image: "jenkins/jenkins:lts"
- securityContext:
- runAsUser: 0
- volumeMounts:
- - mountPath: "/var/jenkins_home/"
- name: pvc-jenkins
- env:
- - name: JENKINS_OPTS
- value: --prefix=/jenkins
- ports:
- - containerPort: 8080
- - containerPort: 50000
- resources:
- limits:
- cpu: "2"
- memory: "4Gi"
- requests:
- cpu: "1"
- memory: "2Gi"
- livenessProbe:
- httpGet:
- path: "/jenkins/login"
- port: 8080
- initialDelaySeconds: 90
- periodSeconds: 10
- timeoutSeconds: 5
- failureThreshold: 5
- readinessProbe:
- httpGet:
- path: "/jenkins/login"
- port: 8080
- initialDelaySeconds: 60
- periodSeconds: 10
- timeoutSeconds: 5
- failureThreshold: 3
创建一个service来暴露Jenkins的地址和端口,注意这里加了GCP的neg注解,使得可以使用ingress来暴露,另外指定了jenkins agent和jenkis master之间通讯的接口50000
- apiVersion: v1
- kind: Service
- metadata:
- name: jenkins-svc
- namespace: jenkins
- annotations:
- cloud.google.com/neg: '{"ingress": true}'
- labels:
- app: jenkins
- spec:
- ports:
- - name: jenkinsport
- port: 8080
- targetPort: 8080
- - name: slaveconnectors
- port: 50000
- targetPort: 50000
- selector:
- app: jenkins
再创建一个ingress,使得可以从公网访问
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: jenkins-ingress
- namespace: jenkins
- spec:
- rules:
- - http:
- paths:
- - path: /jenkins
- pathType: Prefix
- backend:
- service:
- name: jenkins-svc
- port:
- number: 8080
最后创建一个kustomization.yaml,把以上部分组合起来
- resources:
- - namespace.yaml
- - storage-class.yaml
- - pvc.yaml
- - agent-pvc.yaml
- - deployment.yaml
- - service.yaml
- - ingress.yaml
然后运行命令kubectl apply -k kustomization_dir/即可部署。等待ingress创建完成后,在gcp console的网页上找到相应的地址,在浏览器上访问即可进入jenkins的配置页面。如果不能访问,我们需要检查一下Firewall是否没有放通。
初次访问配置页面,需要输入admin password,这个可以从Jenkins pod的日志查到。
kubectl logs <jenkins_pod_name> -n jenkins
之后我们可以安装建议的插件,然后创建一个Jenkins的用户
有了主服务器之后,我们就可以配置动态Agent了。以下是Jenkins创建动态Agent的流程图

在Jenkins的Manage Jenkins页面,选择Plugin,搜索Kubernetes并安装。安装完成后重启Jenkins
在Jenkins的Manage Jenkins->Clouds->New Cloud,类型选择Kubernetes.
然后在配置项里面,Kubernetes url和Kubernetes server certificate key都不用输入,因为我们的Jenkins master server是在同一个Kubernetes里面。Namespace输入jenkins。然后赋予jenkins这个namespace下的default用户cluster-admin的role。如以下命令:
kubectl create clusterrolebinding jenkins --clusterrole cluster-admin --serviceaccount=jenkins:default
点击Test Connection,如果正常就会显示连接成功。
输入Jenkins service的internal DNS的名称,因为我们之前定义了jenkins前缀,所以URL是http://jenkins-svc.jenkins.svc.cluster.local:8080/jenkins
Pod label key设置为jenkins,value设置为slave。Pod template name输入jenkins-slave,namespace输入Jenkins,labels输入slave。Containers里面先不加container template,Jenkins默认是用Jenkins/inbound-agent这个镜像
如果我们需要用docker来创建镜像,并且我们的GKE cluster是standard的类型(Autopilot类型不支持host path为/var/run/docker.sock),那么可以Jenkins agent可以采用docker in docker的方式。需要Add volume detail,选择Host path volume,然后把kubernetes节点的docker daemon socket mount到Jenkins agent pod. 在Host path和mount path分别输入/var/run/docker.sock
因为我的GKE是Autopilot类型,我将采用Kaniko的方式来Build镜像,因此这里没有add volume
因为Jenkins agent pod需要权限来调用GCP的服务,例如推送image到repository等。需要创建一个service account来给agent使用。
首先在K8S的jenkins namespace创建一个service account
kubectl create serviceaccount jenkins-sa --namespace jenkins
在GCP IAM里面创建一个service account
gcloud iam service-accounts create jenkins-sa --project=PROJECT_ID
给这个service account赋予需要的role,可以用以下命令
gcloud projects add-iam-policy-binding PROJECT_ID --member "serviceAccount:jenkins-sa@PROJECT_ID.iam.gserviceaccount.com" --role "ROLE_NAME"
把这两个service account绑定起来,使得K8S的service account可以模仿GCP的service account
- gcloud iam service-accounts add-iam-policy-binding jenkins-
- sa@PROJECT_ID.iam.gserviceaccount.com --role roles/iam.workloadIdentityUser --member
- "serviceAccount:PROJECT_ID.svc.id.goog[jenkins/jenkins-sa]"
Annotate K8S的service account,加上email地址
- kubectl annotate serviceaccount jenkins-sa --namespace jenkins
- iam.gke.io/gcp-service-account=jenkins-sa@PROJECT_ID-
- v1.iam.gserviceaccount.com
最后还要把Run as user id设为0,使得jenkins agent pod以root用户来运行,以避免权限问题。最后Save即可完成配置。
回到Manage jenkins的manage globle security里面,在Agent的设置下选择指定端口,填入50000
现在可以构建一个CICD pipeline了。下图是构建CICD的一个流程图

这里面包括了几个步骤
我这里将扩展一下这些步骤,增加Jenkin和Github webhook的集成,使得用户提交PR的时候自动触发UT检查,然后当用户执行merge操作的时候来触发打包任务。这部分的内容比较多,我将在下一篇博客中记录具体的操作过程。
现在我将建立一个简单的任务,测试一下我们配置的Jenkins是否能正常工作。
在主页面选择新建任务,然后选择pipeline,然后在pipeline script里面输入以下内容
- pipeline {
- agent{
- kubernetes{
- label 'slave'
- }
- }
- environment {
- ZONE = "us-central1"
- PROJECT_ID = "curious-athlete-401708"
- }
-
- stages{
- stage("test"){
- steps{
- script{
- sh 'echo "testing"'
- }
- }
- }
- }
- }
运行这个任务,我们可以看到页面会显示自动创建一个jenkins-slave-xxxx的pod,然后在这个pod上运行我们的pipeline。运行结束之后我们可以查看Job的console log,可以看到成功执行了pipeline里面定义的echo testing
可见我们已经成功配置了一个Kubernetes上的分布式jenkins环境。