Jenkins使用痛点基于k8s编译的job的Workspace访问改造
来这里找志同道合的小伙伴!
相信接触过 devops、CI/CD 这些概念的同学都听说过Jenkins,在各大中型 IT 和互联网公司中,它几乎一定存在于 devops 工具链的最核心位置。 Jenkins 是目前开源世界中用户量最大、生态最完善、功能最强大的持续集成工具,它搭配完善的插件体系,几乎能完成你能想到的 devops 链上的所有事情。
不过,Jenkins 也有一些让用户觉得很不方便的地方,本文将聚焦解决 Jenkins 使用中的一个痛点。接下来您将花7-10分钟,跟我一起来定位并解决这个问题。
痛点
在谈到待解决的问题之前,我们需要先明确一些情况。
我们都知道,Jenkins 从架构上看基本属于单机软件,它不直接支持横向扩展和数据共享。为了应对越来越多的用户和 job,Jenkins 支持了 master-slave 的执行模式,用户可以在 master 节点上创建和配置任务,不过真正执行任务却是在 slave 节点上,两者通过 jnlp 等网络协议传输数据。这样对用户来说 slave 节点几乎是透明的,对 master 节点来说又能分散运算压力,可谓是两全其美!
其中最受欢迎的 slave,恐怕要数运行在 kubernetes 集群上的动态 slave 节点了(Jenkins 通过 Kubernetes Plugin 实现了在 kubernetes 集群上按需动态起停 pod 来执行 job 任务)。动态 slave 节点的好处是:有任务才创建、运行完就销毁,对资源的利用率简直太理想了!
Jenkins + kubernetes 架构如下图所示:
▲图片来自网络
那么问题来了!
如果我要执行一个编译打包的 job,在 Jenkins master 节点上创建并配置好了任务,然后任务在 kubernetes 创建的一个 pod 里顺利执行了,然后 pod 告诉 Jenkins master 节点说 job 执行成功了,然后 pod 被销毁了…… 那我编译出来的包呢?也随着 pod 被销毁了......
以前我们为了解决这个问题,一般都需要在 Jenkins file 里面使用相关的工具(如:curl),然后将编译好的包传输到某个第三方服务上。说实话,这样操作太麻烦、不人性化、强依赖、强耦合 …… 差评!
今天我们要解决的就是这个问题!
再次明确一下我们的需求:取回 kubernetes 上运行的 job 的 Workspace,并且能够在 Jenkins 页面上访问和下载文件。
想一想怎么做
子曰:写代码之前先想好怎么做,不然写出来也是 bug!
我们先来捋一下思路,既然是编译任务,那肯定会至少包含从代码库拉取源代码和执行编译两个步骤,也就是说源码和编译出来的包肯定是在 slave 节点的某个路径上,而最后我们需要在 Jenkins 的页面上查看到这个路径的内容,并且在 kubernetes 的 pod 被销毁以后还能看到,那自然是:
需要将这个 job 的 Workspace 拷贝出来放到一个 Jenkins master 服务能访问的路径下;
考虑到页面响应的时效问题,最理想的是将这个 job 的 Workspace 拷贝到 master 节点上的 Workspace 下。
我能想到的满足以上两点,并且成本最低(不依赖第三方服务)的方法是:用 nfs-util 将 slave 节点的 Workspace 目录挂载到 master 节点上 $JENKINS_HOME 路径下的 Workspace 文件夹上,这样 master 和 slave 节点的 Workspace 就能同步了,这些文件也能被保存下来了。
完美的想法!
挂载NFS服务
在 master 节点上安装 nfs 工具并启动,设置访问权限等。这一步就不细说了,自己上百度去 google 一下,满大街都是,没有难度。
然后在该 Job 的 jenkins file 中加上下面这句:
podTemplate(......
volumes:[
nfsVolume(mountPath:slave节点的workspace,serverAddress:master节点的IP,serverPath:master节点的workspace,readOnly:false)
]
......
){......}
完事! 大功告成,验证一下!
访问 pipeline job 的 Workspace
访问 pipeline 类 job 的 Workspace,需要进入到 job 的某一次 build 页面(如上图中1处所示),然后点击左侧菜单的 Pipeline Steps(上图中2处所示)。
继续点击右边的 Allocate node: Start(如上图中3处所示),即可看到 Workspace 的入口:
点击进入:
什么情况?怎么出错了?
分析一下出错的原因
细看一下出错的 Stack trace:
哦,报了一个文件找不到的错误。登录到 Jenkins master 节点的后台去看一下,确认该 job 的 Workspace 及其内容已经全部同步到 master 上了!那应该是程序里面读取这个目录的参数有问题吧?
看到报错的源文件是org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl.java,这是一个插件的源码,通过 Github 上查询得知,属于 workflow-support 插件,那没办法了,只能把插件的源码下载下来看一下了!
看源码
根据上面的提示查看源码,发现报错的地方如下图所示:
所在文件:
org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl.java
看这个意思是 getworkspace() 这个方法返回 null 值了。再跟进 getworkspace() 方法去看看(by the way,这个方法是在 workflow-api 插件的源码中实现的,再下载 workflow-api 插件的源码):
所在文件:
org.jenkinsci.plugins.workflow.actions.WorkspaceAction.java
getworkspace() 中主要是 getNode() 和 getPath() 两个方法,但是这两方法在 workflow-api 插件中定义为抽象方法,再跟一下代码,发现这两个方法的实现还是在 workflow-support 插件中实现的:
所在文件:
org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl.java
OK!这下明白了!
原来在页面上访问 job 的 Workspace 时,是根据这个 job 所在的 node 加上 node 上的 path 来定位的。前面我们虽然将 slave 的 Workspace 挂载到 master 的 Workspace 下,但是程序访问的时候还是去 slave 节点上找,可此时 slave 节点已经被 kubernetes 销毁了,所以程序报错说找不到了!
改代码
子曰:如果没有其他的需求,我们就一切从简,实现这个访问 Workspace 的功能就行。
既然我们已经通过 nfs 把 job 的 Workspace 同步到 master 节点的 Workspace 下了,那我们强行让 node 和 path 返回 master 节点的 node 和 path 即可。
这里如果你再跟一下代码的话,会发现此时你不知道 master 节点的 node 应该返回什么。没办法,继续跟代码吧,这是一个痛苦的过程,最终,我们在 Jenkins-core 源码中发现,master 节点的 node (节点名)为""(空字符串对象,不是null)。
最终,我们通过一些条件判断之后,将 getNode() 和 getPath() 两个方法改一下即可:
所在文件:
org.jenkinsci.plugins.workflow.support.actions.WorkspaceActionImpl.java
尾声
至此,我们就完成了基于 kubernetes 编译的 job 的 Workspace 访问的改造。再次从页面上访问一下 Workspace:
全部内容可见也可下载了,完美!
------------------END------------------
下面的内容同样精彩
点击图片即可阅读
京东技术
---关注技术的公众号
长按识别二维码关注
我们都知道持续集成和持续交付是 DevOps 的组成部分,因为它们用于集成方法的多个阶段。市面上的CI/CD工具有很多,但是你知道Jenkins这个基于Java的开源CI/CD工具是最受欢迎的吗?用于测试自动化的 Jenkins 是开发人员的热门选择,因为它能够轻松地与各种测试工具集成。它一直是 DevOps 专业人士和初学者的首选。
Jenkins初学者教程:包含示例和最佳实践的综合指南
关注留言点赞,带你了解最流行的软件开发知识与最新科技行业趋势。