JavaLearn Jenkins the hard way - Jenkins的存储模型

Posted 请Java和Android开发者吃点干货

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn Jenkins the hard way - Jenkins的存储模型相关的知识,希望对你有一定的参考价值。

前言

在上篇文章中我们主要讲解了Jenkins的页面与路由,在本章中我们要讲解下Jenkins的数据持久化机制。在Jenkins中数据的持久化是通过文件进行存储的,大家平时使用Hibernate进行持久化的时候,我们只需要关心哪些地方是需要存储的,哪些位置是不需要储存的,并且在不需要存储的位置添加transient关键字即可,持久化的框架会自动帮我做好Java Object与数据库存储之间的序列化与反序列化的过程,而在Jenkins中由于数据的存储都是通过文件的方式进行存储的,有必要让大家了解下一个Jenkins是如何将数据进行组织与存储的。

从JENKINS_HOME讲起

JENKINS_HOME是Jenkins启动的时候会识别的一个环境变量,这个环境变量的作用是设定Jenkins的持久化根目录,所有的Jenkins持久化文件会以此目录为根进行创建与存储,而Jenkins中任务、构建、账户等信息都会以文件的方式存储,因此这个文件目录会在Jenkins的使用过程中容量快速膨胀,对于需要维护Jenkins的同学建议单独挂一块盘。
JENKINS_HOME这个目录下的结构大致如下

可以发现JENKINS_HOME下有两种命名的规则,一种是标准的命名,例如config.xml或者nodes的文件夹;还有一种类似类名的文件名,例如hudson.model.UpdateCenter.xml这种的文件。这两种文件的命名可以推测出一种是Jenkins内部实现的一些存储,而另一种是Jenkins提供出来的通用文件存储机制。那么我们打开一个文件来看下存储的内容是什么样子的,比如hudson.model.UpdateCenter.xml,里面的内容如下

<?xml version='1.0' encoding='UTF-8'?><sites>
  <site>
    <id>default</id>
    <url>http://updates.jenkins-ci.org/update-center.json</url>
  </site></sites>

文件是以标准的XML的格式进行持久化的,熟悉JAVA序列化与反序列化的同学已经可以猜测到,这个文件应该是对应着Jenkins中的一个对象,Jenkins通过将一个Java对象序列化和反序列化来进行存储。对于上面两种不同命名方式的文件,可以发现文件内容都是XML格式的Java对象序列化格式,唯一的区别在于存储路径上会有所不同,这是为什么呢。

标准命名的一些路径和文件大部分是Jenkins Master的一些实现,比如job、nodes等等,是Jenkins Master直接使用的一些内部实现的存储,因为这些存储的信息通常会有特殊的意义或者行为,比如任务或者节点,如果存储在单文件中会造成难以查询、隔离等等的问题,因此Jenkins对于一些对象的存储进行了优化。

通用格式的存储例如com.aliyun.www.cos.DeployBuilder.xml则大部分是插件的存储文件,开发过Jenkins插件的开发者可能知道,在Jenkins的插件中并没有读取配置文件的动作,只有一个save方法,开发者需要进行配置存储的时候,只需要调用父类中继承下来的save方法即可。而这个save的方法的实现会自动以上面的这种类名为文件名的方式存储在JENKINS_HOME目录下,因此,这也就是为什么不建议大家直接存储配置的秘钥到自己的插件中,而是需要调用credentials插件来实现,因为Jenkins在默认存储的时候不会直接对你的秘钥信息进行加密,会有很高的安全风险。那么Jenkins在何时会反序列化数据的呢?答案是Jenkins启动时。Jenkins在启动的时候,首先会启动主进程server,然后加载所有的插件,再加载数据。在加载插件的时候,会通过类名查找相应的配置文件,再将配置中的信息反序列化Java的对象,因此如果在Jenkins的插件中存储了大量的数据,会造成Jenkins重启加载异常缓慢的问题,如果在插件中有大量的存储需求,建议大家将存储代理给Jenkins的Job,虽然Job在加载的时候也是在Jenkins启动时通过文件的方式加载的,但是每个Job的配置是隔离的,不会造成单次大文件的读取效率瓶颈。

从存储模型看Jenkins高可用

很多开发者一直在思考如何将Jenkins做成高可用的,毕竟单节点的Jenkins从某种意义上来讲总像一个定时炸弹。但是很遗憾的告诉大家,标准的Jenkins很难做到完整的高可用方案。那么问题的根源在什么?这要从我们刚才谈到的存储模型谈起,上面我们提到了Jenkins的启动顺序为:主程序启动 ——> 插件加载 ——> 数据加载。而当数据加载到内存后,Jenkins的数据读取和存储模型就会变成内存读异步写,也就是说一旦Jenkins启动,就再也不会尝试从磁盘重新加载这几个任务了。假设我们从磁盘中读取了10个任务,如果有从Jenkins UI页面或者API提交修改任务的请求,那么Jenkins会去去读内存中的任务配置信息,然后修改内存数据,再异步刷新到磁盘上。换言之,Jenkins是有状态的,而且是单机有状态的,如果想通过简单的共享存储与负载均衡或者反向代理的方式实现Jenkins的高可用基本是不行的。

那么有的开发者在想是否可以通过通知然后异步更新内存数据的方式进行数据的共享呢。比如一个Jenkins Master更新了任务信息,然后通过开发一个插件通知另一个Jenkins Master在内存中更新任务的配置呢。这种方式看样子是可行的,但是实际操作中我们会发现如下问题。首先如果是单纯单个文件的变更或者变化理论上是可行的,但是需要这个插件在所有涉及存储的扩展点上fire出事件,并进行处理,并且部分信息的变更还涉及相关插件的重新加载,而这些信息是无法从单纯的事件信息中得出的。其次如果涉及文件夹或者多个文件的变更,那么此时需要进行全目录的扫描或者加载,会触发全量的数据加载,一旦这个操作涉及任务或者构建,那么延时将是秒级以上的,如果在这个阶段有任何的更新操作,极有可能造成数据的脑裂。最后,由于Jenkins没有公共的cache,会造成登录态共享等等问题。

因此,目前标准的Jenkins还没有很好的高可用方案。但是,大家对于Jenkins的可靠性不用过于担心,一个4C8G的VM,进行JVM调优后可以轻松应对上百的构建并发,对于大多数中小型公司而言是足够了,对于更大型的公司会更倾向于使用多个独立的Jenkins Master来分担风险。

Jenkins,廉颇老矣?

Jenkins没有办法做到高可用,是否意味着Jenkins的架构已经过于陈旧或者老迈了,我们是否还继续选择Jenkins。诚然Jenkins的架构很古老,这种存储模型也有他避免不了的问题。但是就目前来看,Jenkins是唯一的选择,无论是GitLab CI、Spinnaker还是Travis CI等等,相比Jenkins都在功能或者生态上有所欠缺,而且如果不是超大型的企业对于目前Jenkins的性能问题基本上也是无感知的,可以说Jenkins目前是刚刚好的状态。对于CI/CD来讲,我们需要的并不是多么超前的技术或者多么炫的页面,更多的是如何通过DevOps来保证质量和集成交付流程,对于不同场景和不同业务的开发者而言,上千个Jenkins插件和多余牛毛的介绍文章可以企业的DevOps快速进行实施。

在今年7月,阿里云推出了CodePipeline服务,CodePipeline是基于Jenkins进行二次开发的,在CodePipeline中,我们通过对存储模型的改造实现了一个高可用的方案,对于Jenkins高可用有需求的开发者或者公司可以在公有云或者私有云中尝试使用CodePipeline来替代Jenkins,对Jenkins的全兼容可以让开发者快速上手完成自己的持续交付流程。

阅读全文请点击下面的阅读原文

以上是关于JavaLearn Jenkins the hard way - Jenkins的存储模型的主要内容,如果未能解决你的问题,请参考以下文章

解决Jenkins的错误“The Server rejected the connection: None of the protocols were accepted”

JenkinsJenkins权威指南英文版:jenkins-the-definitive-guide.pdf

JavaLearn # 面向对象编程_3

Jenkins出现No valid crumb was included in the reques

JavaLearn # Java的常用类

JavaLearn # 异常