因为项目需要,在GKE的集群上需要创建一个CICD的环境,记录一下安装部署一个分布式Jenkins集群的过程。
分布式Jenkins由一个主服务器和多个Agent组成,Agent可以执行主服务器分派的任务。如下图所示:
如上图,Jenkins Agent可以运行不同的操作系统,执行主服务器分派的编译打包或测试等任务。
在Jenkins的官网上介绍了在K8S上安装的几种方式,包括了Helm, operator等。但是我直接用Helm最新版的Jenkins安装始终遇到问题,日志报错信息是Google的OAUTH插件编译有问题,如果用旧版本的可以安装成功,但是我不想用旧的版本,而新版本的问题暂时没找到解决的思路,另外Helm安装的values配置过于繁杂。我考虑用最传统的Yaml manifest的方式来部署这个Jenkins。
创建Jenkin主服务器
首先是创建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-standardfstype: ext4
volumeBindingMode: WaitForFirstConsumer
定义PVC,保存Jenkins的相关数据
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: jenkins-pvc namespace: jenkins
spec:accessModes:- ReadWriteOncestorageClassName: "storage-jenkins" resources:requests:storage: 30Gi
然后就是创建一个deployment,部署Jenkins pod,注意在containers的env里面增加了一个环境变量,定义了jenkins uri的前缀,然后在probe的path里也相应的加了/jenkins前缀。
apiVersion: apps/v1
kind: Deployment
metadata:name: jenkinsnamespace: jenkins
spec:selector:matchLabels:app: jenkinsreplicas: 1template:metadata:labels:app: jenkinsspec:volumes:- name: pvc-jenkins persistentVolumeClaim:claimName: jenkins-pvc containers:- name: jenkinsimage: "jenkins/jenkins:lts"securityContext:runAsUser: 0volumeMounts:- mountPath: "/var/jenkins_home/"name: pvc-jenkinsenv:- name: JENKINS_OPTSvalue: --prefix=/jenkins ports:- containerPort: 8080- containerPort: 50000 resources:limits:cpu: "2"memory: "4Gi"requests:cpu: "1"memory: "2Gi"livenessProbe:httpGet:path: "/jenkins/login"port: 8080initialDelaySeconds: 90periodSeconds: 10timeoutSeconds: 5failureThreshold: 5readinessProbe:httpGet:path: "/jenkins/login"port: 8080initialDelaySeconds: 60periodSeconds: 10timeoutSeconds: 5failureThreshold: 3
创建一个service来暴露Jenkins的地址和端口,注意这里加了GCP的neg注解,使得可以使用ingress来暴露,另外指定了jenkins agent和jenkis master之间通讯的接口50000
apiVersion: v1
kind: Service
metadata:name: jenkins-svcnamespace: jenkinsannotations:cloud.google.com/neg: '{"ingress": true}'labels:app: jenkins
spec:ports:- name: jenkinsportport: 8080targetPort: 8080- name: slaveconnectorsport: 50000targetPort: 50000selector:app: jenkins
再创建一个ingress,使得可以从公网访问
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: jenkins-ingressnamespace: jenkins
spec:rules:- http:paths:- path: /jenkinspathType: Prefixbackend:service:name: jenkins-svcport:number: 8080
最后创建一个kustomization.yaml,把以上部分组合起来
resources:- namespace.yaml- storage-class.yaml- 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的用户
安装Jenkins动态Agent
有了主服务器之后,我们就可以配置动态Agent了。以下是Jenkins创建动态Agent的流程图
- 当触发Jenkins任务的时候,Jenkins kubernetes插件会给K8S服务器发起一个API调用,创建一个Agent pod.
- 这个Jenkins agent pod会运行在K8S上来运行Jenkins任务,当任务完成时会自动中止。
- 当Jenkins agent pod启动时,会读取环境变量配置并和Jenkins主服务器通过JNLP的方式沟通。
安装Jenkins kubernetes插件
在Jenkins的Manage Jenkins页面,选择Plugin,搜索Kubernetes并安装。安装完成后重启Jenkins
1. 连接Kubernetes
在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,如果正常就会显示连接成功。
2. 配置Jenkins URL
输入Jenkins service的internal DNS的名称,因为我们之前定义了jenkins前缀,所以URL是http://jenkins-svc.jenkins.svc.cluster.local:8080/jenkins
3. 创建Pod和Container template
Pod label key设置为jenkins,value设置为slave。Pod template name输入jenkins-slave,namespace输入Jenkins,labels输入slave。Containers里面先不加container template,Jenkins默认是用Jenkins/inbound-agent这个镜像
然后需要Add volume detail,因为我们的Jenkins agent是docker in docker,所以需要选择Host path volume,然后把kubernetes节点的docker daemon socket mount到Jenkins agent pod. 在Host path和mount path分别输入/var/run/docker.sock
4. 配置Service Account
因为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即可完成配置。
5. 设置Agent连接端口
回到Manage jenkins的manage globle security里面,在Agent的设置下选择指定端口,填入50000
运行Jenkins CICD pipeline
现在可以构建一个CICD pipeline了。下图是构建CICD的一个流程图
这里面包括了几个步骤
- 往Git仓库提交代码,通过hook触发jenkins任务
- Jenkins启动agent pod,运行打包任务
- 把打包的镜像推送到registry
- 更新部署的manifest,更新镜像的标签
- 把应用部署到GKE
我这里将扩展一下这些步骤,增加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环境。