Jenkins动态Slave设计思路
一、缘起CICD的思想目前对于每一个正规的软件开发团队基本都是必填项,那么一般来讲Jenkins的应用自然沦为了刚需。Jenkins目前在单台Vm虚机上基于docker容器化部署,当Jenkins用了一段时间以后,发现每天的Jenkins 构建次数日益剧增,逐渐出现了Jenkins访问速度慢,卡顿,甚至直接终止服务响应的情况。由于底层是基于Vm,那么申请了一些物理资源,暂时解决了问题。随后,随着几
一、缘起
CICD的思想目前对于每一个正规的软件开发团队基本都是必填项,那么一般来讲Jenkins的应用自然沦为了刚需。
Jenkins目前在单台Vm虚机上基于docker容器化部署,当Jenkins用了一段时间以后,发现每天的Jenkins 构建次数日益剧增,逐渐出现了Jenkins访问速度慢,卡顿,甚至直接终止服务响应的情况。由于底层是基于Vm,那么申请了一些物理资源,暂时解决了问题。
随后,随着几个项目组的构建需求频繁增长,每天Jenkins的构建次数会超过500次,此时显然原有部署结构已经不够支撑了。Jenkins服务各种卡死,无响应白屏频频发生。不过出现这个问题,也是意料之内,但是就是相对棘手了些。
由于现有服务器资源相对有限,直接开几台高配置Vm,可能会比较简单粗暴的解决问题,但成本相对较大,也不利于资源利用。因为Jenkins工作日时间也分忙时闲时。
二、解决思路
1. 痛点梳理
- 构建任务高峰期,Jenkins服务频发不可用状态
- 服务虚机资源有限,不能随意调用空闲资源
- Jenkins 服务器宕机后需要人工手动重启
2. 思路分析
A. 传统Master-Slave模式
增加3台虚机,在现有Jenkins服务上创建多个Slave,绑定新增的这几台虚机上,通过Master-Slave的形式来尝试解决问题
-
优势
- 通过增加新的vm来解决问题,问题理论上可以得到解决
- 时间的技术壁垒较低,时间较短
-
劣势
- 需要新申请虚机资源,对于资源调度分配上会有一定风险
- 3台Slave在Jenkins构建闲时也会占用资源,导致资源浪费
- 每个Slave的职能会根据业务需求随时调整,Slave的环境配置后期维护成本较高
- 当Jenkins构建任务较多,Slave分配不均的时候,也会存在排队现象
B. 基于K8s动态Slave模式
基于K8s集群,Jenkins Master 接到构建任务后会动态在集群中的一个工作节点上拉起一个Jenkins Slave Pod来干活儿,活儿干完后可及时释放Pod。
-
优势
- 基于云原生现有K8s集群来解决问题,充分的利用现有资源,无需再申请新虚机
- Slave可在构建任务来之时动态创建,工作结束后自动销毁,释放资源
- 可通过K8s原生来管理Jenkins的调度策略,防止Slave调度分配不均匀
- 通过云原生Chart来管理Jenkins配置,后期比较利于维护、扩展
- Jenkins 小概率意外宕机场景,通过K8s的机制可以自愈
-
劣势
-
增加了系统复杂度
-
有一定技术壁垒
-
实现需要时间
-
通过团队共识,果断采用基于K8s动态Slave模式,以云原生的思路解决问题,这样不但可以解决Jenkins的服务资源紧缺现象,还能充分的利用K8s集群中的工作节点资源,云原生的形式也利于后期扩展和维护。计划先采用一个小型K8s集群(1m3n)来尝试解决问题。
三、Jenkins工作原理简述
1. 启动加载
Jenkins 启动时,会读取本地持久化目录数据,加载到内存。启动后,我们对Jenkins的上层用户操作,实际上都是对内存中数据状态的修改,Jenkins会异步刷盘,数据持久化。这样设计可应对大规模客户端操作请求,运行起来会更加节约I/O,性能更快。
2. 主从工作通信机制
基于Remoting通信框架(https://github.com/jenkinsci/remoting),通过TCP (JNLP protocols) 实现长连接序列化传输,master来向其对应注册的slave发送指令,slave异步处理后返回。
- Master
-
Slave
-
参考资料
https://cloud.tencent.com/developer/article/1773184?from=article.detail.1773183
四、Dynamic Slave 工作原理
1. 概念定义
基于K8s集群,Jenkins Master 接到构建任务后会动态拉起一个Jenkins Slave Pod来干活儿,活儿干完后可及时释放Pod。
2. 设计优势
-
动态伸缩
合理的使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。
-
服务高可用
当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
-
扩展性好
当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。
五、Docker in Docker
1. 设计思路
我们都知道,基于k8s拉起的服务,实际上都是以Pod形式存在的,而Pod是个容器组,最终服务实际是以Pod内的Container来支撑运行的。那么针对Slave的应用场景,Container应该如何设计?
为了不入侵宿主机Node上的docker服务,还要在Node上的Pod中使用docker服务,那么目前最佳的思路是要引入Docker in Docker技术来完成。
2. Docker in Docker
我们先来看下标准的dind使用方式,也就是Slave的内部设计
A. 容器职能
上图中,我们看到,Slave内部引入了3个容器,分别是JNLP,docker daemon和 docker build ,先来介绍下这3个容器的职能
-
JNLP container
负责与Jenkins Master建立TCP通信,接收l来自Master发送过来的所有构建指令
-
Docker Daemon container
docker的工作原理简单说是通过C/S模式进行的,而docker daemon就是server端。
docker服务启动时,本地环境的client会和server端建立一个tcp连接,默认端口号是2375(如果使用TLS的话,则为2376),当我们运行一个docker指令时,这条指令会被发送到docker daemon,然后再返回相关信息。
-
Docker Build container
这里才是我们真正用到的build server,我们构建项目用到的各种环境(java、go、php …)都可以把环境的基础信息放到这里面来,然后docker build 来构建。
B. Dind设计优势
-
Pod网络 - 可以直接使用pod网络,通过pod的 ip可以直接访问到内部容器暴露的服务端口。
-
服务可用性 - 由于是docker daemon,那么容器内部会建立一个守护进程,经过实际测试,用原来比较丑陋的『 kill 1』的命令,是无法终止它的,那么就天然保证了docker服务的可用性。
-
Pod清理方式 - Pod的清理交给k8s管理,如果特定场景下需要自清理,那么emptyDir是个不错的选择。
-
资源调度和使用情况 - Cpu和内存申请交给Pod, 可通过k8s机制来管理container的内存和cpu使用。
六、总结
经过一番折腾,上线后完全Cover住目前项目组的构建场景,构建速度明显加快,同时最大化资源利用。总体来讲,基于K8s + dind 来玩Slave体验还是不错的,后续有时间我会继续更新本帖,把实现过程中的一些具体操作呈现给大家。当然,如果您有问题,也请在评论区给我留言。
更多推荐
所有评论(0)