Jenkins 分布式和并发构建
1. 分布式构建
2. 并发构建
1. 分布式构建
当持续集成系统管理了特别多的项目时,所有的任务都在主节点上同时执行,那么默认一个节点只能有 2 个 executor 执行任务,其他的就必须等待,这样会大大影响执行的效率,同时也不能满足在不同环境下的兼容性测试。
本节将介绍如何用 Jenkins 进行分布式构建任务。Jenkins 的分布式也是基于 master-slave 模式的,我们演示用的 master 节点在 windows 上,分别添加一个 linux 的从节点和 windows 的从节点。
1.1 添加 linux 节点
1)Jenkins-->Manage Jenkins-->Manage Nodes
2)新建节点
3)配置节点名称
Permanent Agent 表示的是常驻代理客户端。
4)节点详细配置
- 名称:上一步创建的节点名称。
- 并发构建数:指的是该节点最多有多少个执行器(executor),执行器是真正工作的单元,一个执行器就是一个单独的线程。
- 远程工作目录:代理或者从节点上的工作目录,尽量使用绝对路径;目录不存在的话会自动创建,而且必须有写权限,否则会报错:hudson.util.IOException2: Failed to copy xxxx。
- 标签:也叫 tag,用来区分或者标识某类节点,经常以工具链,操作系统等信息标识。
- 用法:标识代理或者从节点的使用策略,有两种方式:
- Use this mode as much as possible:尽可能的使用此节点。
- Only build jobs with label expression matching this node:构建任务时指定的标签匹配本节点时才使用。
- 启动方法:有 3 种方法启动:
- Launch agent agents via SSH:通过 SSH 通道连接节点(安装了 SSH Slaves plugin 插件才能看到)。
- Launch agent by connecting it to the master:通过 jnlp,javaweb 的方式连接。
- Launch agent via execution of command on the master:通过主节点的控制台连接子节点。
- 我们选择的第 2 种方式,对应的配置如下:
-
- 注意:如果定义了自定义工作目录,就不会使用代理的根目录,执行的日子信息会存到主节点中。而且该选项目前无法使用环境变量,因此建议使用绝对路径。
-
- 可用性:决定 Jenkins 的启动和停止:
- 1)Keep this slave on-line as much as possible:尽可能保持节点在线【推荐】
- 该模式下,Jenkins 会尽可能让代理保持在线。
- 如果该代理由于临时性网络故障,Jenkins 会定期尝试重启它。
- 2) Bring this agent online according to a schedule:让代理在特定的时间段内在线或者离线
- 该模式下,Jenkins 会根据一个计划表来启动代理,并保持指定的时长。如果在计划周期内代理掉线,Jenkins 会定期尝试重启它。
- 当代理在线时间达到字段计划启动的时间,它将会被下线。
- 如果勾选了当有构建时保持在线,并且根据计划表应该下线,Jenkins 会等所有的构建任务完成后再下线。
- 3)Bring this agent online when in demand,and take offline when idle:当代理被需要时保持在线,空闲时离线。
- 该模式下,当代理被需要时 Jenkins 将会让代理上线,例如有排队的构建任务满足下列条件:
- 在队列中排队时间达到需求延迟时间限制。
- 被构建任务指定(例如有一个匹配的标签表达式)。
- 如果发生下述情况,代理将会被下线:
- 代理没有需要构建的任务。
- 该代理已经空闲了指定的时间。
- 代理没有需要构建的任务。
- 该模式下,当代理被需要时 Jenkins 将会让代理上线,例如有排队的构建任务满足下列条件:
- 1)Keep this slave on-line as much as possible:尽可能保持节点在线【推荐】
- 节点属性:根据需要配置即可:
- Disable deferred wipeout on this node:在当前节点上,是否开启延迟清理;
- Environment variables:配置环境变量,可以在脚本中引用;
- Tool Locations:工具的目录【推荐】。可以替换系统设置的各种工具目录。如:JDK 目录、Ant 目录、Maven 目录等。好处就是在不更改 Job 配置的情况下,不同环境(如 Windows 和 Linux)的 Job 配置通用。
5)配置完成后节点状态
Jenkins 提供了两种方式让 agent 和 master 进行连接,我们选择第 2 种,因为命令行的形式更方便自动化。
6. 下载 agent.jar
文件可通过此网址 http://127.0.0.1:8080/jenkins/jnlpJars/agent.jar 或者单击以下超链接:
7. 将 agent.jar 文件上传到 linux 节点中
进入到代理节点的工作目录 jenkins 下,执行 rz 命令上传文件。注意:远程的代理节点中一定已经安装好了 jdk。
8. 获取 windows 主节点的 IP 地址
9. linux 节点执行以下命令启动节点
java -jar agent.jar -jnlpUrl http://192.168.1.10:8080/jenkins/computer/centos7/slave-agent.jnlp -secret 6b317df862212450891bb851e8f673d2d18e0c0b15bc46117bebe51c176d56b5 -workDir "/jenkins"
注意:以上 IP 为主节点的 IP 地址。
10. 执行 pineline 脚本
pipeline { agent { label 'linux' } stages { stage('Hello') { steps { echo 'Hello World' } } } }
构建日志:
切换到 linux 节点中 Jenkins 的工作目录,可以看到配置节点时指定的 remoting 目录,下面有节点的执行日志:
1.2 添加 windows 节点
和上述添加 linux 节点的步骤大同小异,此处不再赘述。
一般 Jenkins 都是部署到 linux 系统,因为 windows 系统相当不稳定。
2. 并发构建
2.1 原理
并行构建指的是某个 stage 或者 step 同时执行,比如说我们的 UI 测试脚本要在 Chrome、IE 和 Firefox 3 种浏览器下同时执行,或者是 APP 需要在不同型号的手机上执行,或者执行不同分支的代码等等,以上场景如果是顺序执行的话,显然效率是很低的,而且这些场景也都是相对独立的,并无依赖关系,那么我们能不能实现并行执行呢?在 pipeline 中并行构建需要用到 parallel 指令。
parallel{} 里面包含多个 stage{…},只有最后一个 stage{…} 内部支持嵌套多个 stages{…}。在 parallel{…} 中如果要设置只要里面有一个 stage{…} 运行失败就强制停止,可以使用表达式 failFast true 来进行控制。
pipeline 脚本如下:
pipeline { agent any stages { stage('Non-Parallel Stage') { steps { echo 'This stage will be executed first.' } } stage('Parallel Stage') { failFast true parallel { stage('并行一') { steps { echo "并行一" } } stage('并行二') { steps { echo "并行二" } } stage('并行三') { stages { stage('Nested 1') { steps { echo "In stage Nested 1 within Branch C" } } stage('Nested 2') { steps { echo "In stage Nested 2 within Branch C" } } } } } } } }
成功执行结果
并行 1、并行 2 和并行 3 三个 stage 之间的关系是并行的,下图显示都执行成功:
失败执行结果
将并行 2 中 echo 改为 printf 指令,查看 pipeline 执行结果,如下:
如果有需要一个不通过,无须执行后面 pipeline 脚本的场景,可以使用 failFast true 语句。
如上图所示,原本并行一、并行二和并行三下面两个嵌套的 stage 都在同一时间并发执行,由于设置了 failFast true,在并行二这个 stage 发生了报错后,导致并行三下面两个前提的 stage 都显示 aborted 了,从控制台日志也可以看出来。
2.2 示例:分别用 chrome/IE/Firefox 并行测试
@Library('share-lib') _ pipeline{ agent any options { buildDiscarder(logRotator(numToKeepStr:'3')) } environment { config_file = "\\Config\\ProjVar.py" } stages{ //从 github 拉取测试脚本 stage("checkout test script") { steps{ script { checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CloneOption', noTags: false, reference: '', shallow: true, timeout: 20]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gtihub', url: 'https://github.com/kongsh8778/keywordFramework']]]) } } } //执行测试 stage("Parallel Selenium Test"){ failFast true parallel { //firefox 浏览器 stage("firefox"){ steps{ script{ //修改配置文件中浏览器类型 try{ setkey.setKeyValueByWriteFile("browser", "firefox", config_file) file_content = readFile config_file //println file_content }catch (Exception e) { error("Error met:" + e) } //执行测试 bat("py -3 main.py") } } } //chrome 浏览器 stage("chrome"){ steps{ script{ //修改配置文件中浏览器类型 try{ setkey.setKeyValueByWriteFile("browser", "chrome", config_file) file_content = readFile config_file //println file_content }catch (Exception e) { error("Error met:" + e) } //执行测试 bat("py -3 main.py") } } } } } } post{ always { echo 'This will always run' //workspace 下的临时目录 deleteDir() } success { echo 'This task is successful!' //记录日志信息 script { emailext attachLog: true, body: text, compressLog: true, mimeType: 'text/html', subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!', to: '455576105@qq.com' } } } }