前言

这篇写好一段时间了,一直也没发布上来,今天稍微整理下了交下作业,部分内容偷懒引用了一些别人的内容。
使用Jenkins做持续集成/持续交付,当业务达到一定规模的时候,Jenkins本身就很容易成为整条流水线的瓶颈,各个业务端都依靠Jenkins,部署Jenkins服务时如何保障服务的高可用变得尤为重要。
以微医为例,目前Jenkins的业务承载量:>1,000 Build Jobs,>5,000 Buils/Day,光依靠单master已经无法承载高并发的性能压力,瓶颈来自多方面,不仅仅是Jenkins 应用本身占用 memory 和 CPU 资源,也包括各个job编译、测试、部署等的资源开销,随着job数量的增加,大量的workspace也会耗尽服务器的存储空间,严重影响整个技术团队的工作效率和部署节奏。

一、Jenkins分布式集群架构

Jenkins 分布式架构是由一个 Master 和多个 Slave Node组成的 分布式架构。在 Jenkins Master 上管理你的项目,可以把你的一些构建任务分担到不同的 Slave Node 上运行,Master 的性能就提高了。
Master/Slave相当于Server和agent的概念。Master提供web接口让用户来管理job和slave,job可以运行在master本机或者被分配到slave上运行构建。
一个master(jenkins服务所在机器)可以关联多个slave用来为不同的job或相同的job的不同配置来服务。

二、传统的Jenkins Slave方式存在的问题

传统的 Jenkins Slave 一主多从式会存在一些痛点。比如:

  • 主 Master 发生单点故障时,整个流程都不可用了;
  • 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲;
  • 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态;
  • 资源有浪费,每台 Slave 可能是实体机或者 VM,当 Slave 处于空闲状态时,也不会完全释放掉资源。

     

    是不是很丑陋?

三、基于 Kubernetes 搭建容器化Jenkins集群实践

3.1 基于 Kubernetes 的Jenkins 集群架构

由于以上种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而虚拟化容器技术能很好的解决这个痛点,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图。

 

Jenkins Master 和 Jenkins Slave 以 Docker Container 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。
这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Docker Container 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且 Docker Container 也会自动删除,恢复到最初状态。
这种方式带来的好处有很多:

  • 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
  • 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。
  • 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。

3.2 部署 Jenkins Master

在保证Jenkins Master高可用的前提下,可以按传统方式war包方式部署,可以使用docker方式部署,也可以在Kubernetes Node中部署,这部相对简单,不再展开详述。

3.3 Jenkins 配置 Kubernetes Plugin

管理员账户登录 Jenkins Master 页面,点击 “系统管理” —> “管理插件” —> “可选插件” —> “Kubernetes plugin” 勾选安装即可。

 

安装完毕后,点击 “系统管理” —> “系统设置” —> “新增一个云” —> 选择 “Kubernetes”,然后填写 Kubernetes 和 Jenkins 配置信息。

 

3.4 使用Jenkins Pipeline测试验证

接下来,我们可以配置 Job 测试一下是否会根据配置的 Label 动态创建一个运行在 Docker Container 中的 Jenkins Slave 并注册到 Master 上,并且在运行完 Job 后,Slave 会被注销并且自动删除 Docker Container。
创建一个 Pipeline 类型 Job 并命名为"pipeline_kubernetes_demo1,然后在 Pipeline 脚本处填写一个简单的测试脚本如下:

pipeline {
agent {
kubernetes {
//cloud 'kubernetes'
label 'k8s-jenkins-jnlp'
containerTemplate {
name 'jnlp'
image 'harbor.guahao-inc.com/base/jenkins/jnlp-slave:latest'
}
}
}
stages {
stage('Run shell') {
steps {
script {
git 'https://github.com/nbbull/demoProject.git'
sh 'sleep 5'
}
}
}
} }

执行构建,此时去构建队列里面,可以看到有一个构建任务,第一次构建的时候会稍慢,因为k8s的node需要去下载jnlp-slave的镜像。
稍等一会就会看到k8s-jenkins-jnlp-8gqtp-j9948的容器正在创建,然后开始运行,Job 执行完毕后,jenkins-slave 会自动注销并删除容器,我们通过 kubectl 命令行,可以看到整个自动创建和删除过程,整个过程自动完成。

[root@kubernetes-master1 ~]# kubectl get pods|grep jenkins 
k8s-jenkins-jnlp-8gqtp-j9948 0/1 ContainerCreating 0 4s
[root@kubernetes-master1 ~]# kubectl get pods|grep jenkins
k8s-jenkins-jnlp-8gqtp-j9948 1/1 Running 0 18s

具体的构建日志参考如下:

 

3.5 自定义 jenkins-slave 镜像

通过 kubernetest plugin 默认提供的镜像 jenkinsci/jnlp-slave 可以完成一些基本的操作,它是基于 openjdk:8-jdk 镜像来扩展的,但是对于我们来说这个镜像功能过于简单,比如我们想执行 Maven 编译或者其他命令时,就有问题了,那么可以通过制作自己的镜像来预安装一些软件,既能实现 jenkins-slave 功能,又可以完成自己个性化需求,dockfile如下:

FROM harbor.guahao-inc.com/base/jenkins/jnlp-slave:latest
USER root
//下载安装必要组件
RUN apt-get update && apt-get install -y sudo && rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y vim && apt-get install -y sshpass
//下载和配置maven
COPY apache-maven-3.2.6-GH /usr/greenline/install/apache-maven-3.2.6-GH
RUN ln -s /usr/greenline/install/apache-maven-3.2.6-GH /usr/greenline/maven3
//下载和配置jdk
COPY jdk1.8.0_91 /usr/greenline/install/jdk1.8.0_91
RUN ln -s /usr/greenline/install/jdk1.8.0_91 /usr/greenline/jdk_1.8
ENV JAVA_HOME=/usr/greenline/jdk_1.8
ENV CLASSPATH=.:/usr/greenline/jdk_1.8/lib/dt.jar:/usr/greenline/jdk_1.8/lib/tools.jar:/usr/greenline/jdk_1.8/lib/rt.jar
ENV PATH=/usr/greenline/jdk_1.8/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/greenline/maven3/bin USER jenkins ENTRYPOINT ["jenkins-slave"]

除了在Pipeline中定义slave的image,我们也可以使用非 Pipeline 类型指定运行该自定义 slave,那么我们就需要修改 “系统管理” —> “系统设置” —> “云” —> “Kubernetes” —> “Add Pod Template” 修改配置 “Kubernetes Pod Template” 信息如下:

3.6 Jenkins启动参数调整

默认情况下,Jenkins保守地生成代理。比如,如果队列中有2个构建,它将不会立即生成2个执行程序。它会产生一个执行器并等待一段时间让第一个执行器被释放,然后再决定产生第二个执行器。Jenkins确保它产生的每个执行者都得到最大限度的利用。如果要覆盖此行为并立即为队列中的每个构建生成执行程序,可以在Jenkins启动时参加一下参数:

-Dhudson.slaves.NodeProvisioner.initialDelay=0
-Dhudson.slaves.NodeProvisioner.MARGIN=50
-Dhudson.slaves.NodeProvisioner.MARGIN0=0.85

四、使用GlusterFS共享存储

Jenkins的日常工作包括大量的编译构建,构建过程中会涉及到大量依赖包的下载(比如jar包,npm包等),采用原生容器的方式由于没有持久化本地仓库,每次构建都需要对这些依赖包重新下载,严重影响效率。
这里需要解决公共依赖包持久化存储的问题,一种做法是配置宿主机目录挂载的方式,把文件挂载到宿主机,这样虽然方便但是不够安全,而且Kubernetes集群一般有多个Node节点,如果容器在挂了被重新拉起的时候被调度到其他的Node节点,那映射在原先主机上的数据还是在原先主机上,新的容器还是没有原来的数据。
所以推荐的方法一般都是把数据存储在远程服务器上如:NFS,GlusterFS,ceph等,目前主流的还是使用GlusterFS。事实上,Kubernetes的选择很多,目前Kubernetes支持的存储有下面这些:

GCEPersistentDisk
AWSElasticBlockStore
AzureFile
AzureDisk
FC (Fibre Channel)
FlexVolume
Flocker
NFS
iSCSI
RBD (Ceph Block Device)
CephFS
Cinder (OpenStack block storage)
Glusterfs
VsphereVolume
Quobyte Volumes
HostPath (就是刚才说的映射到主机的方式,多个Node节点会有问题)
VMware Photon
Portworx Volumes
ScaleIO Volumes
StorageOS

Kubernetes有这么多选择,GlusterFS只是其中之一,但为什么可以脱颖而出呢?GlusterFS,是一个开源的分布式文件系统,具有强大的横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS的Volume有多种模式,复制模式可以保证数据的高可靠性,条带模式可以提高数据的存取速度,分布模式可以提供横向扩容支持,几种模式可以组合使用实现优势互补。

4.1 GlusterFS集群的部署:

安装环境: 192.168.XX.A , 192.168.XX.B
GlusterFS集群的部署比较简单,在各台机器分别安装

# yum install centos-release-gluster
# yum install glusterfs-server
# /etc/init.d/glusterd start

在一台上面建立信任关系

# gluster peer probe 192.168.XX.B   #后面跟另外一台的IP
# gluster peer status

创建名称为“jenkins_public”的分布式卷:

# gluster volume create jenkins_public 192.168.XX.A:/data/exp1 1192.168.XX.B:/data/exp2 force
# gluster volume info 查看逻辑卷信息
# gluster volume start jenkins_public #启动逻辑卷

4.2 如何在Kubernetes中使用GlusterFS

Kubernetes用PV(PersistentVolume)、PVC(PersistentVolumeClaim)来使用GlusterFS的存储。PV与GlusterFS的Volume相连,相当于提供存储设备;PVC消耗PV提供的存储,由应用部署人员创建,应用直接使用PVC进而使用PV的存储。
官方文档对配置过程进行了介绍:https://github.com/kubernetes/examples/blob/master/staging/volumes/glusterfs/README.md

4.2.1 在Kubernetes中创建GlusterFS的端点定义(endpoints)

data1-volume-pv-cluster.json:

 {
"kind": "Endpoints",
"apiVersion": "v1",
"metadata": {
"name": "data1-volume-pv-cluster"
},
"subsets": [
{
"addresses": [{ "ip": "192.168.XX.A" }],
"ports": [{ "port": 20 }]
},
{
"addresses": [{ "ip": "192.168.XX.B" }],
"ports": [{ "port": 20 }]
}
]
}

备:该subsets字段应填充GlusterFS集群中节点的地址。可以在port字段中提供任何有效值(从1到65535)。

##创建端点:
[root@k8s-master-01 ~]# kubectl create -f data1-volume-pv-cluster.json
##验证是否已成功创建端点
[root@k8s-master-01 ~]# kubectl get ep |grep data1-volume-pv-cluster
glusterfs-cluster 192.168.XX.A:20,192.168.XX.B:20

4.2.2 配置 service

我们还需要为这些端点(endpoint)创建服务(service),以便它们能够持久存在。我们将在没有选择器的情况下添加此服务,以告知Kubernetes我们想要手动添加其端点
data1-volume-sv-cluster.json:

{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "data1-volume-pv-cluster",
"namespace": "default",
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 20,
"targetPort": 20
}
]
}
}

创建服务

[root@k8s-master-01 ]# kubectl create -f  data1-volume-sv-cluster.json
##查看service
[root@k8s-master-01 ]# kubectl get service | grep data1-volume-sv-cluster

4.2.3.配置PersistentVolume(简称pv)

创建jenkins-public-pv.yaml文件,指定storage容量和读写属性
jenkins-public-pv.yaml:

kind: PersistentVolume
apiVersion: v1
metadata:
labels:
name: jenkins-public-pv
namespace: default
name: jenkins-public-pv
spec:
capacity:
storage: 600Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
glusterfs:
endpoints: data1-volume-pv-cluster
path: jenkins_public
readOnly: false

然后执行:

[root@k8s-master-01 ~]# kubectl create -f jenkins-public-pv.yaml
## 查看pv
[root@k8s-master-01 ~]# kubectl get pv|grep jenkins-public-pv

4.2.4 配置PersistentVolumeClaim(简称pvc)

创建jenkins-public-pvc.yaml文件,指定请求资源大小

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins-public-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 600Gi
selector:
matchLabels:
name: jenkins-public-pv

然后执行:

[root@k8s-master-01 ~]# kubectl create -f jenkins-public-pvc.yaml
## 查看pvc
[root@k8s-master-01 ~]# kubectl get pvc|grep jenkins-public-pvc

4.2.5 部署Jenkins Slave挂载pvc

修改pipeline,把pvc挂载到容器内的/home/maven_repository,下面是完整的Pipeline:

pipeline {
agent {
kubernetes {
cloud 'kubernetes'
label 'k8s-jenkins-jnlp'
defaultContainer 'jnlp'
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
node-label: "k8s-jenkins-jnlp-${UUID.randomUUID().toString()}"
spec:
containers:
- name: jnlp
image: harbor.guahao-inc.com/base/jenkins/jnlp-slave:1123_20
volumeMounts:
- mountPath: /home/maven_repository
name: docker-socker-file
volumes:
- name: docker-socker-file
persistentVolumeClaim:
claimName: jenkins-public-pvc
"""
}
}
stages {
stage('Run shell') {
steps {
script {
git 'https://github.com/nbbull/demoProject.git'
sh 'sleep 50'
}
}
}
} }

可以把agent的逻辑统一抽象到共享库,就可以实现所有的job都通过K8S进行构建,至此部署完成。

五、Jenkins性能调优

除了结合K8S搭建Jenkins集群保证高可用以外,最后再补充总结一些jenkins自身的性能调优技巧,这里不再展开,具体可在使用中去体会和补充。
• 使用Pipeline方式比配置方式运行更快。
• 让Pipeline做中间组织的工作,而不是取代其他工具。
• 能用脚本实现的,就不要用插件。
• 根据资源情况限制并发执行的job数量。
• 使用共享库抽象公共的代码并持续优化。
• 不要写太复杂的Pipeline脚本(>1000行),包括共享库代码在内。
• 不要对外网环境有强依赖(万恶的墙)。
• 使用“@NonCPS”注解高耗代码方法。
• 使用SSD硬盘等高性能硬件
• Durability/Speed Options

[持续交付实践] Jenkins Pipeline 高可用设计方法的更多相关文章

  1. [持续交付实践] Jenkins 中国用户大会参会见闻

    前言 上周日在上海召开了Jenkins中国用户大会(Jenkins User Confluence China),这应该是Jenkins在中国第一次举办吧.Jenkins的创始人Kohsuke Kaw ...

  2. [置顶] Docker学习总结(7)——云端基于Docker的微服务与持续交付实践

    本文根据[2016 全球运维大会•深圳站]现场演讲嘉宾分享内容整理而成 讲师简介 易立 毕业于北京大学,获得学士学位和硕士学位:目前负责阿里云容器技术相关的产品的研发工作. 加入阿里之前,曾在IBM中 ...

  3. 云端基于Docker的微服务与持续交付实践

    云端基于Docker的微服务与持续交付实践笔记,是基于易立老师在阿里巴巴首届在线技术峰会上<云端基于Docker的微服务与持续交付实践>总结而出的. 本次主要讲了什么? Docker Sw ...

  4. 第 18 章 高可用设计之 MySQL 监控

    前言: 一个经过高可用可扩展设计的 MySQL 数据库集群,如果没有一个足够精细足够强大的监控系统,同样可能会让之前在高可用设计方面所做的努力功亏一篑.一个系统,无论如何设计如何维护,都无法完全避免出 ...

  5. MySql(十八):MySql架构设计——高可用设计之 MySQL 监控

    前言: 一个经过高可用可扩展设计的 MySQL 数据库集群,如果没有一个足够精细足够强大的监控系统,同样可能会让之前在高可用设计方面所做的努力功亏一篑.一个系统,无论如何设计如何维护,都无法完全避免出 ...

  6. MySQL性能调优与架构设计——第 18 章 高可用设计之 MySQL 监控

    第 18 章 高可用设计之 MySQL 监控 前言: 一个经过高可用可扩展设计的 MySQL 数据库集群,如果没有一个足够精细足够强大的监控系统,同样可能会让之前在高可用设计方面所做的努力功亏一篑.一 ...

  7. Docker学习总结(14)——从代码到上线, 云端Docker化持续交付实践

    2016云栖大会·北京峰会于8月9号在国家会议中心拉开帷幕,在云栖社区开发者技术专场中,来自阿里云技术专家罗晶(瑶靖)为在场的听众带来<从代码到上线,云端Docker化持续交付实践>精彩分 ...

  8. Kafka 高可用设计

    Kafka 高可用设计 2016-02-28 杜亦舒 Kafka在早期版本中,并不提供高可用机制,一旦某个Broker宕机,其上所有Partition都无法继续提供服务,甚至发生数据丢失对于分布式系统 ...

  9. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

随机推荐

  1. 时间Date.js

    <span style="line-height: 25.2px;">/** * 日期解析,字符串转日期 * @param dateString 可以为2017-02- ...

  2. git 入门教程

    git 入门教程之协同开发 前面我们已经介绍过远程仓库的相关概念,不过那时并没有深入探讨,只是讲解了如何创建远程仓库以及推送最新工作成果到远程仓库,实际上远程仓库对于团队协同开发很重要,不仅仅是团队协 ...

  3. PL/SQL连接远程服务器数据库,出现ORA-12154: TNS: 无法解析指定的连接标识符。

    故障环境:上礼拜新装了一台服务器(win server2008r2),并在服务器上安装了ORACLE 11g database.且已经做好监听配置,开通了1521端口. 我又在局域网内另一台pc端装了 ...

  4. Java Web 学习笔记 1

    Java Web 学习笔记 1 一.Web开发基础 1-1 Java Web 应用开发概述 1.1.1 C/S C/S(Client/Server)服务器通常采用高性能的PC机或工作站,并采用大型数据 ...

  5. HTML复习 2019-2-11

    HTML复习 2019-2-11 <!doctype html> <html> <!-- 常见问题答疑 Question 1:HTML标签可以大写吗? 大小写都可以,比如 ...

  6. myBatis---接口代理开发(demo)

    一.概述 使用接口代理开发,可以不用写接口的实现类,而采用的是MapperFactoryBean代理的实现类. * 接口代理方式开发,遵循四大原则 * 1.方法名 == mapper.xml的id名 ...

  7. Intel:从屌丝逆袭成业界大佬

    原创文章,转载请标明出处哈,Thanks♪(・ω・)ノ. 参考<Linux内核情景分析><深入理解计算机系统><深入理解linux内核><Orange'S:一 ...

  8. 虚拟机安装精简版centos7过程

    虚拟机配置工作如下所示 1.创建虚拟机  使用键盘组合键CTRL+N2.选择自定义(高级) 如图所示: 3.默认如何所示: 4.选择 稍后安装操作系统 如图所示: 5.选择对应的操作系统 如何所示 6 ...

  9. Git自动化合并多个Commit

    目录 git rebase逻辑 git editor的修改 处理git-rebase-todo文件 Python实现 当我们有多个commit或者从开源处拿到多个commit时,想合成一个commit ...

  10. day-09内存管理

    内存管理 引用计数:垃圾回收机制的依据 # 1.变量的值被引用,该值的引用计数 +1# 2.变量的值被解绑,该值的引用计数 -1# 3.引用计数为0时就会被垃圾回收机制回收​ 引用计数会出现循环引用问 ...