为什么需要使用 Kubenetes


大多数公司在使用 Jenkins 的时候都会使用集群方式来组成 CI/CD 流程,但是传统的 jenkins 一主多从的方式仍然存在一些不足:

  • 存在单点故障 :如果 Master 节点故障,则整个流程将中断。
  • 维护繁琐:多个 Slave 基础环境与配置存在差异,维护起来比较麻烦。
  • 资源使用不均衡:有的 Slave 上运行的 job 消耗资源多出现排队等待,而其他 Slave 处于空闲状态,集群资源不能得到充分利用。
  • 资源闲置率高:我们平常的 job 序列中 80% 基本都是消耗资源较低的 job ,只有 20% 的 job 占用资源较多,在资源空闲时并不能释放资源给其他 Slave 节点使用

Docker 虚拟化容器及 Kubernetes 集群可以很好的解决上述问题,基于 Kubernetes 搭建 Jenkins 集群的示意图如下:

架构图
架构图

如上图所示,Jenkins Master 和 Jenkins Slave 将以 pod 的形式运行在 Kubenretes 集群中,其中 Master 将持续运行在 Kubernetes 集群中,使用 Persistent Volume 把数据持久化存储到介质;Slave 运行在集群中节点上,但 是 Slave 节点不会持续运行,其生命周期与 Job 状态有关,kubernetes 将根据 Job 任务动态创建和释放 Slave。

流程大致为:当 Jenkins Master 接受到 Build 请求时,Kubernetes 会根据配置的 Label 和模板动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 JenkinsMaster 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。

基于 Kubernetes 的 Jenkins 集群主要有一下几个优点:

  • 服务高可用: Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master Pod,并且将持久化的 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
  • 资源弹性伸缩:每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,能有效提高集群内部资源利用率。
  • 支持多任务并发:jenkins slave扩展性强,只要kubernetes 集群性能足够,jnekins 可以支持多任务并行,可以扩展足够多的 jnekins slave节点来执行任务,解决 大量job 排队的情况。

下面就详细介绍下 jenkins 在 kubernetes 集群中的具体实践。

安装 Jenkins 到 Kubernetes 集群


首先要将 jenkins master 节点部署到集群中,并将 jenkins 的数据持久化存储到本地或者 nfs 上。持久化存储的主要目的是防止 jenkins pod 意外中止或重启导致数据丢失。当然 jenkins master 也可以直接部署在主机上而非集群中,因为后续的jenkins slave 都将在集群中产生,所以建议将 jenkins master 也部署在 kubernetes 集群中。

Deployment

kubernetes 集群中 jenkins 的部署比较简单,因为我是部署到现有的namespace中只需三个文件即可,使用 Deployment 方式部署:

Deployment 文件:jenkins-dep.yml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
 name: jenkins-dep
 namespace: zcfw
spec:
 template:
   metadata:
     labels:
       app: jenkins-dep
   spec:
     terminationGracePeriodSeconds: 10
     serviceAccountName: operator
     containers:
     - name: jenkins
       image: jenkins/jenkins:2.190.2
       imagePullPolicy: IfNotPresent
       ports:
       - containerPort: 8080
         name: web
         protocol: TCP
       - containerPort: 50000
         name: agent
         protocol: TCP
       resources:
         limits:
           cpu: 1000m
           memory: 1Gi
         requests:
           cpu: 500m
           memory: 512Mi
       livenessProbe:
         httpGet:
           path: /login
           port: 8080
         initialDelaySeconds: 60
         timeoutSeconds: 5
         failureThreshold: 12
       readinessProbe:
         httpGet:
           path: /login
           port: 8080
         initialDelaySeconds: 60
         timeoutSeconds: 5
         failureThreshold: 12
       volumeMounts:
       - name: jenkins-persistent-storage
         subPath: jenkins
         mountPath: /var/jenkins_home
       - name: gitlabrepo-persistent-storage
         mountPath: /home/deploy_file
       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
     securityContext:
       fsGroup: 1000
     volumes:
     - name: jenkins-persistent-storage
       persistentVolumeClaim:
         claimName: jenkins-pv-data-claim
     - name: gitlabrepo-persistent-storage
       persistentVolumeClaim:
         claimName: gitlabrepo-jenkins-pv-claim
     nodeSelector: 
       kubernetes-app: allow

有两个地方需要注意:一个是 jnlp 通信端口 50000,这个端口一定要定义,不然后续会出现 jenkins slave 节点无法与 master 通信造成 jenkins slave pod 不停重启。二是使用 PersistentVolume 对 jenkins 数据目录做持久化存储。

持久化存储卷

定义持久化存储卷与声明,集群里有 StorageClass 的话只用定义 PersistentVolumeClaim,PersistentVolume 与 PersistentVolumeClaim 文件:jenkins-pv-and-pvclaim.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv-data
  labels:
    pv: jenkins-pv-data
spec:
  capacity:
    storage: 6Gi
  accessModes:
    - ReadWriteOnce
  nfs:
    path: /data/volumes/jenkins/jenkins_home
    server: 10.xx.xx.xx
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pv-data-claim
  namespace: zcfw
  annotations:
    volume.beta.kubernetes.io/storage-class: ""
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 6Gi
  selector:
    matchLabels:
      pv: jenkins-pv-data
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: gitlabrepo-jenkins-pv
  labels:
    pv: gitlabrepo-jenkins-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    path: /data/volumes/jenkins/deploy_file
    server: 10.xx.xx.xx
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: gitlabrepo-jenkins-pv-claim
  namespace: zcfw
  annotations:
    volume.beta.kubernetes.io/storage-class: ""
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  selector:
    matchLabels:
      pv: gitlabrepo-jenkins-pv

Service 与 Ingress

创建 service 服务暴露容器内部端口,创建 ingress 将集群内 jenkins master 服务暴露到集群外部访问。集群内部访问 service 服务可以使用 url : http://serviceName.namespace.svc.cluster.local ,例如:http://jenkins-svc.zcfw.svc.cluster.local (本例中 jenkisn master service)

service 与 ingress 文件:jenkins-svc.yml

apiVersion: v1
kind: Service
metadata:
  name: jenkins-svc
  namespace: zcfw
  labels:
    app: jenkins-dep
spec:
  type: ClusterIP
  ports:
    - name: jenkins-web
      port: 80
      targetPort: 8080
    - name: agent
      port: 50000
      targetPort: 50000
  selector:
    app: jenkins-dep
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: jenkins-web
  namespace: zcfw
spec:
  rules:
  - host: xxx.xxx.com
    http:
      paths:
      - path: /
        backend:
          serviceName: jenkins-svc
          servicePort: jenkins-web

使用命令 kubectl apply -f *.yml 部署 jenkins master ,使用 kubectl get pods -n zcfw -o wide 查看 pod 状态。

在集群中我们使用 Traefik 提供反向代理与负载均衡,Ingress 把集群内部服务暴露到外部,将集群外的URL访问请求转发到内部不同的 service 上。Traefik是一款开源的反向代理与负载均衡工具。它最大的优点是能够与常见的微服务系统直接整合,实现自动化动态配置。Traefik通过不断地跟 kubernetes API 打交道,实时的感知后端 service、pod 等变化,比如pod,service 增加与减少等;当得到这些变化信息后,自动更新配置并热重载 ,达到服务发现的作用。Traefik 的使用与介绍将会在另一篇文章中详细介绍,此处不再展开。

如下图所示,部署完成后,在 Traefik 面板中可以看到相应的 Ingress 服务状态以及详细信息,通过 http://xxx.xxx.com/ 即可访问 jenkins master 。
到此我们的 jenkins master 就已经部署到 kubernetes 集群中了,下一篇我们将介绍 jenkins master 在集群中的具体使用以支持多并发集成。

traefik 面板
traefik 面板