目录

  • 传统的 Jenkins Slave 方式存在的问题
  • 解决方案
  • 技术细节
  • 最佳实践



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

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

  • slave 节点发生故障时,可能会造成整个流程不可用
  • 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲;
  • 资源分配不均衡,没有一个好的调度算法来调度 job 去最空闲的 slave 上运行。导致有些 slave 很空闲,有些很忙碌
  • 资源浪费,每台 Slave 可能是实体机或者 VM,当 Slave 处于空闲状态时,也不会完全释放掉资源。
  • 如果没有容器化部署 slave,那么制作 slave 的成本很高,迁移也很麻烦。 因为 salve 上的依赖可能很多很复杂
  • 每台 slave 节点都需要安装 jdk,配置 ssh 服务等,即便我只是想运行一个 python 任务。 这样即便我们容器化部署,但也使我们的 dockerfile 中增加了额外的步骤造成额外的维护成本,并且镜像制作速度也会下降。

解决方案

基于上面的问题我们之前的改造方式是把 salve 做成镜像部署到 k8s 中, 比如我们的 UI 自动化所需要的 slave 就是测试 k8s 集群中一个 pod。 这样解决了我们上面说的第一个问题,就是如果节点出现故障,那么 k8s 会帮我们把这个 pod 迁移到其他可用节点,但是它无法很好的解决其他几个问题。 所以最终我们希望将 jenkins 和 k8s 进行整合。

抛开 master 节点的实现 (我们的 jenkins mater 没有部署在 k8s 中),在这里 jenkins 能调用 k8s 的接口,动态的在 k8s 中创建 pod 并作为 slave 运行我们的测试任务, 任务运行完毕后删除 pod。 并且它充分利用了 k8s 的特性, 创建的 pod 中负责与 jenkins master 连接的 slave 容器是 jenkins 团队发布的 docker 镜像 jenkins/jnlp-slave, 而且 jenkins 的 k8s 插件会自动帮助我们定义 pod 的配置,我们只需要定义一个基本的 pod 信息,jenkins 会把我们的配置与它自己的配置进行 merge。

技术细节

那么接下来我们看一下这里面的技术细节。 这个 pod 里面定义了两个容器, 一个是 jnlp, 这里需要注意一下。 jenkins 默认把名字叫 jnlp 的容器当做 slave 容器,所以如果你想要更换这个镜像的话, 就像上面做的一样即可。 也就是我们自己写一个 dockerfile 然后继承 jenkins/jnlp-slave 对它进行扩展。 这样我们既保留了 slave 的能力又扩展了自己的运行依赖。 比如下面:

jenkins配置k8s插件的pods jenkins pipeline k8s_jenkins


之所以要扩展 jenkins/jnlp-slave 主要有三个原因:

  • 默认的 jenkins/jnlp-slave 镜像启动容器的时候是使用 jenkins 这个用户,而不是 root 用户,这样会导致我们后续与其他容器协作的时候出现权限问题
  • 默认的镜像不知道什么原因没办法解析我们公司的 gitlab 域名, 我到现在也不知道什么原因。只有我集成它并安装自己的镜像就可以。 很诡异的问题。
  • 直接安装 maven,配置未我们公司的私服。 这样我们就可以直接把这个镜像也作为 java 的 slave 容器来运行, 一举两得。

最佳实践

不要在像以前做一个大而全的 slave 镜像, 这样镜像维护的成本太高,一旦有一点改动就要重新 build 好久,中间出一点错重新 build 的时间成本太高。 取而代之的是我们每个场景定义一个小而美的镜像。 比如要测试 python sdk,那就用官方的 python 镜像就可以了, 如果需要 go 语言的依赖,就做一个 golang 的镜像就可以了。 我们只需要在运行时在不同的阶段切换到对应的镜像运行就可以了。 反正他们是共享 worksapce 目录的, 所以不论怎么切换都没问题的。

只有在具体运行测试任务的时候切换到对应语言的容器中去,其他的都在 jnlp 这个 slave 容器中运行。 这是因为 jenkins 与其他容器通信的机制走的是 k8s client 的 exec 。这个方式有很大的坑,就是这种方式运行的 shell 环境没有设置很多的环境变量,网络设置等等。 我在高可用测试工具中也使用的这个方式去与其他容器通信,经常遇到这个问题。 所以如果你在其他容器中执行 git 这种拉取代码操作。 有可能会遇见诸如 canot resolve hostname 这种网络请求问题, 我也在执行 make build 命令的时候出现过由于没有 预先设置 LANG=utf-8 这种问题 而导致 java 字符转义失败。而一个正常的系统是有这些预先设置这些变量的。 所以为了防止被坑,那就只在必要的时候切换到其他容器中运行,平时我们只在 jnlp 容器中操作。