k8s部署jenkins
概述
持续构建与发布是我们日常工作中必不可少的一个步骤,目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程,然而传统的 Jenkins Slave 一主多从方式会存在一些痛点,比如:主 Master 发生单点故障时,整个流程都不可用了;每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲;资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态;最后资源有浪费,每台 Slave 可能是实体机或者 VM,当 Slave 处于空闲状态时,也不会完全释放掉资源,提到基于Kubernete的CI/CD,可以使用的工具有很多,比如Jenkins、Gitlab CI已经新兴的drone之类的,我们这里会使用大家最为熟悉的Jenins来做CI/CD的工具
优点
Jenkins 安装完成了,接下来我们不用急着就去使用,我们要了解下在 Kubernetes 环境下面使用 Jenkins 有什么好处,我们知道持续构建与发布是我们日常工作中必不可少的一个步骤,目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程,然而传统的 Jenkins Slave 一主多从方式会存在一些痛点,比如:
- 主 Master 发生单点故障时,整个流程都不可用了
- 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲
- 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态
- 资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源 正因为上面的这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker 虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图
从图上可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除,这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态 那么我们使用这种方式带来了哪些好处呢?
- 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用
- 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况
- 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展 是不是以前我们面临的种种问题在 Kubernetes 集群环境下面是不是都没有了啊?看上去非常完美
部署jenkins
Kubernetes 集群当中,新建jenkins.yaml文件
[root@k8s-master jenkins]# cat jenkins.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: jenkins
rules:
- 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/v1
kind: RoleBinding
metadata:
name: jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: jenkins
---
apiVersion: v1
kind: Service
metadata:
name: jenkins
namespace: jenkins
spec:
selector:
app: jenkins
type: ClusterIP
ports:
- name: http
port: 8080
targetPort: 8080
protocol: TCP
- name: agent
port: 50000
protocol: TCP
targetPort: 50000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 0
template:
metadata:
labels:
app: jenkins
spec:
securityContext:
fsGroup: 1000
serviceAccountName: jenkins
containers:
- name: jenkins
#选其一即可(一个包含blueocean)
#image: jenkins/jenkins:lts-alpine
image: jenkinsci/blueocean:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: web
protocol: TCP
- containerPort: 50000
name: agent
protocol: TCP
volumeMounts:
- name: jenkins-home
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
volumes:
- name: jenkins-home
persistentVolumeClaim:
claimName: jenkins-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-data
namespace: jenkins
labels:
app: jenkins
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: managed-nfs-storage
为了方便我们测试,我们这里通过 ingress的形式来访问Jenkins 的 web 服务,Jenkins 服务端口为8080,50000 端口为agent,这个端口主要是用于 Jenkins 的 master 和 slave 之间通信使用的(jenkins-ingress.yaml)
[root@k8s-master jenkins]# cat jenkins-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jenkins-ing
namespace: jenkins
spec:
rules:
- host: jenkins.k8s.com
http:
paths:
- backend:
serviceName: jenkins
servicePort: 8080
path: /
一切准备的资源准备好过后,我们直接创建 Jenkins 服务
[root@k8s-master jenkins]# kubectl create ns jenkins
namespace/jenkins created
[root@k8s-master jenkins]# kubectl apply -f jenkins.yaml
serviceaccount/jenkins created
role.rbac.authorization.k8s.io/jenkins created
rolebinding.rbac.authorization.k8s.io/jenkins created
deployment.extensions/jenkins created
service/jenkins created
persistentvolumeclaim/jenkins-data created
[root@k8s-master jenkins]# kubectl apply -f jenkins-ingress.yaml
ingress.extensions/jenkins-ing created
创建完成后,要去拉取镜像可能需要等待一会儿,然后我们查看下Pod的状态
[root@k8s-master jenkins]# kubectl get -n jenkins po
NAME READY STATUS RESTARTS AGE
jenkins-5986dd847f-w5ktn 1/1 Running 0 69s
[root@k8s-master jenkins]# kubectl get -n jenkins svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
jenkins ClusterIP 10.110.195.139 <none> 8080/TCP,50000/TCP 72s
[root@k8s-master jenkins]# kubectl get -n jenkins ing
NAME HOSTS ADDRESS PORTS AGE
jenkins-ing jenkins.k8s.com 80 70s
根据ingress 服务域名(jenkins.k8s.com)就可以访问 jenkins 服务了,可以根据提示信息进行安装配置即可
初始化的密码我们可以在 jenkins 的容器的日志中进行查看,也可以直接在 nfs 的共享数据目录中查看
[root@k8s-master jenkins]# kubectl logs -n jenkins jenkins-5986dd847f-w5ktn
......
Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:
5074bcbde59a45da837778399bd3d693
This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
......
#或者在nfs上查看
[root@nfs ~]# cat /storage/jenkins-jenkins-data-pvc-4aaf2d86-0fff-4850-be6e-233b7cbad151/secrets/initialAdminPassword
5074bcbde59a45da837778399bd3d693
然后选择安装推荐的插件即可
安装完成后添加管理员帐号即可进入到 jenkins 主界面: jenkins home
配置jenkins
接下来我们就需要来配置 Jenkins,让他能够动态的生成 Slave 的 Pod jenkins依赖插件清单
- kubernetes
- managed scripts
第1步. 我们需要安装kubernetes plugin, 点击 Manage Jenkins -> Manage Plugins -> Available -> Kubernetes plugin 勾选安装即可, kubernetes plugin
第2步. 安装完毕后,点击 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 选择 Kubernetes,然后填写 Kubernetes 和 Jenkins 配置信息
注意 namespace,我们这里填 jenkins,然后点击Test Connection,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:http://jenkins.jenkins.svc.cluster.local:8080,这里的格式为:服务名.namespace.svc.cluster.local:8080,根据上面创建的jenkins 的服务名填写
第3步. 配置 Pod Template,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用jenkins,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 cnych/jenkins:jnlp 这个镜像,这个镜像是在官方的jnlp镜像基础上定制的,加入了kubectl等一些实用的工具 注意:由于新版本的 Kubernetes 插件变化较多,如果你使用的 Jenkins 版本在 2.176.x 版本以上,注意将上面的镜像替换成cnych/jenkins:jnlp6,否则使用会报错
另外需要注意我们这里需要在下面挂载一个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了,如果在slave agent中想要访问kubernetes 集群中其他资源,我们还需要绑定之前创建的Service Account 账号:jenkins
另外还有几个参数需要注意,如图中的Time in minutes to retain slave when idle,这个参数表示的意思是当处于空闲状态的时候保留 Slave Pod 多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除,,到这里我们的 Kubernetes Plugin 插件就算配置完成了
测试
Kubernetes 插件的配置工作完成了,接下来我们就来添加一个 Job 任务,看是否能够在 Slave Pod 中执行,任务执行完成后看 Pod 是否会被销毁 在 Jenkins 首页点击create new jobs,创建一个测试的任务,输入任务名称,然后我们选择 Freestyle project 类型的任务:haimaxy-jnlp-slave-demo
注意在下面的 Label Expression 这里要填入haimaxy-jnlp,就是前面我们配置的 Slave Pod 中的 Label,这两个地方必须保持一致
然后往下拉,在 Build 区域选择Execute shell,输入测试命令,点击保存
echo "测试 Kubernetes 动态生成 jenkins slave"
echo "==============docker in docker==========="
docker info
echo "=============kubectl============="
kubectl get pods -n kube-system
直接build创建的jenkins-demo,观察 Kubernetes 集群中 Pod 的变化
可以看到在我们点击立刻构建的时候可以看到一个新的 Pod:jnlp-mqxkh 被创建了,这就是我们的 Jenkins Slave。任务执行完成后我们可以看到任务信息,比如我们这里是 花费了 3.3s 时间在 jnlp-mqxkh 这个 Slave上面
同样也可以查看到对应的控制台信息:
到这里证明我们的任务已经构建完成,然后这个时候我们再去集群查看我们的 Pod 列表,发现 jenkins这个namespace下面已经没有之前的Slave这个Pod了