jenkins 构建 job 并获取其状态的实现
leoninew 原创,转载请注明来自博客园
BACKGROUND
使用 jenkins rest api 触发的 job 会先进入任务队列,然后异步执行,而无法直接获取到被触发的 job 构建记录编号。虽然 job 的描述信息中 lastBuild 字段告知了最后的构建记录,但无论是先获取 lastBuild,自增其编号作为下次构建 id,还是请求内等待 lastBuild 更新作为构建记录的做法,都存在若干问题:
- 由于构建任务延迟触发,先触发 job 构建再紧接着获取 lastBuild 的多数情况下将返回历史而非当前的构建记录,不可行。
- 以 lastBuild 编号自增作为下次 job 构建编号的做法不可靠,部分机制如插件可以修改自增步进,见 Changing Jenkins build number: If you have access to the script console (Manage Jenkins -> Script Console), then you can do this following:
Jenkins.instance.getItemByFullName("YourJobName").updateNextBuildNumber(45)
- 在多个调用方同时进行 job 构建时,无法判断谁的构建先触发,出现后续的状态/日志错位。
INVESTIGATION I
虽然 jenkins 在触发 job 构建的请求中仅返回了 201(No content),但 jenkins 提供了内置队列查询接口。另一方面阅读网页和查看 jenkins sdk 的 python 版本 python-jenkins 实现后,得知 jenkins 的 job 构建接口有以下实现细节,其中第 1、3 种情况下,返回的响应会携带 Location 字段,携带了队列编号。
- 无参:POST /job/:job-name/build,无 Content-Type 要求
$ curl -i -X POST http://localhost:8080/job/demo/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 10:14:36 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/queue/item/110/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
- 有参:POST /job/:job-name/build,要求表单格式(application/x-www-form-urlencoded),请求消息体有特殊格式要求
- 以 name+value 键值对集合作为请求参数,再进行序列化,形如
{"parameter":[{"name":"branch","value":"test"}]} - 将请求参数转义,以表单格式(application/x-www-form-urlencoded)发送,键为固定值
json。
以 curl 形式调用的命令为
$ curl -i http://localhost:8080/job/rdc-pipline/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw==' -d "json=%7B%22parameter%22%3A%5B%7B%22name%22%3A%22branch%22%2C%22value%22%3A%22test%22%7D%5D%7D"
HTTP/1.1 302 Found
Date: Wed, 14 Oct 2020 09:23:36 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/job/rdc-pipline/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
贴出 fiddler 捕获结果
POST http://localhost:8080/job/rdc-pipline/build HTTP/1.1
Host: localhost:8080
User-Agent: python-requests/2.24.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Jenkins-Crumb: d5a1f46c7e02e9633a1d73741a264fa98bc3729e1e4ebdb4974f2a5b4004afb3
Cookie: JSESSIONID.0e0c708f=node018835f7lya5y821r3looclhze104.node0
Content-Length: 93
Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw==
Content-Type: application/x-www-form-urlencoded
json=%7B%22parameter%22%3A%5B%7B%22name%22%3A%22branch%22%2C%22value%22%3A%22test%22%7D%5D%7D
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 10:17:18 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/job/rdc-pipline/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
- 有参:POST /job/:job-name/buildWithParameters?...,无 Content-Type 要求,参数拼接到 QueryString 中,该形式对参数格式没有示例。
以 curl 形式调用的命令为
$ curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 09:26:09 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/queue/item/98/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
贴出 fiddler 捕获结果
POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A HTTP/1.1
Authorization: basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw==
User-Agent: PostmanRuntime/7.26.5
Accept: */*
Postman-Token: 41dda8ba-a376-44f5-b3e5-f59e8fa2fca7
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: JSESSIONID.0e0c708f=node06gr4lsp9c2wg2dg1vlgm9er496.node0
Content-Length: 0
HTTP/1.1 201 Created
Date: Wed, 14 Oct 2020 10:23:11 GMT
X-Content-Type-Options: nosniff
Location: http://localhost:8080/queue/item/118/
Content-Length: 0
Server: Jetty(9.4.30.v20200611)
对比和测试两种有参请求,有如下区别
| URL | 状态码 | 其他 |
|---|---|---|
| /job/:job-name/buildWithParameters | 201 | Location 携带队列编号,形如 http://localhost:8080/queue/item/118/ |
| /job/:job-name/build | 201 或 302,规律未知 | Location 仅为 Job 地址,形如 http://localhost:8080/job/rdc-pipline/ |
同时观察第一种方式 jenkins 返回的响应,可以看到出现了相同的队列编号,这表示同时提交的构建任务不会重复入队。编排一系列请求如下,以对入队不同 job 及参数变化的情况进行观察。
curl -i -X POST http://localhost:8080/job/demo/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/demo/build -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=A -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
curl -i -X POST http://localhost:8080/job/rdc-pipline/buildWithParameters?branch=B -H 'Authorization: Basic YWRtaW46MTFiNDY0NGYwMDRkYWM3MWU0YjI4ODMyNmQwZWUwNmFhMw=='
5条 curl 命令执行以下操作
- 触发名为 demo 的 job 构建
- 触发名为 rdc-pipline 的 job 构建,使用参数 branch=A
- 触发名为 demo 的 job 构建
- 触发名为 rdc-pipline 的 job 构建,使用参数 branch=A
- 触发名为 rdc-pipline 的 job 构建,变更参数,使用参数 branch=B

可以看到分别返回了以下 Location
- http://localhost:8080/queue/item/128/
- http://localhost:8080/queue/item/129/
- http://localhost:8080/queue/item/128/
- http://localhost:8080/queue/item/129/
- http://localhost:8080/queue/item/130/
可以看到最后的参数变化生成了两条队列记录,目前为止我们有以下结论:
1. 连续触发的相同 job 构建不会重复入队
job 构建的成本因内内容不同差异很大,但触发 job 构建的成本很小,我们可以轻易地提交大量构建请求,基于以下认知可以理解 jenkins 不进行重复构建的意义:
- 虽然代码可能触发前后的短时间内变化,但这是小概率事件;
- 常规情况下我们并不需要获取多份各自独立的编译结果,将请求入队以节流方式构建是可行的;
所以 jenkins 只会为短时间内重复触发的 job 构建一次。
2. 连续触发的不同 job 构建会各自入队
虽然 jenkins 不会重复构建相同 job,但在多个 job 同时触发构建的时候,执行构建仍是必须的,入队只是前置。
为什么 jenkins 不在队列为空、资源可用的情况下避免入队环节直接构建?这里没有深入调查。
3. 参数变动的相同 job 构建将分别入队
虽然有避免重复构建相同 job 的必要,但是当参数变化时,构建仍是必须的,否则就丢失了请求,这是不能容忍的。
目前所用 sdk 只实现了基于 /job/:job-name/build 的构建接口,因入队后队列编号的意义重大,后续的讨论基于实现和使用 /job/:job-name/buildWithParameters 之上。
4. 允许同时触发构建将有数据错乱的可能
虽然参数相同的构建会被 jenkins 以排重形式处理,但参数不同时,没有区分策略可言
- user1 和 user2 准备发起相同名称但参数不同的 job 构建;
- 无论当前的构建队列是否为空,user1 和 user2 触发的构建都会入队
- 检查 job 信息,无法知晓 lastBuild 或 nextBuildNumber 的归属
participant user1
participant user2
participant jenkins
user1-->>jenkins: {...}
user2-->>jenkins: {...}
jenkins->>jenkins: enqueue and dequeue
user1->>jenkins: which job?
user2->>jenkins: which job?
5. 分布式锁强制使得入队或构建触发串行化不可行
- 强制使构建过程串行化,在计算资源及时间成本上不可接受;
- 强制使入队串行化,意味着 jenkins 的排重机制不生效,将出现构建过程串行化;
这类做法违背了 jenkins 的设计意图,加剧了服务器负载,使得响应时间延长,有导致服务器不可用的风险。
INVESTIGATION II
看起来无法把希望寄托于目前 sdk 提供的 job 信息中 lastBuild 信息上,应在队列上继续调查。阅读到 Check Jenkins job status after triggering a build remotely 后,找到可行方法,其步骤如下:
- 提交 job 构建请求到地址 /job/:job-name/buildWithParameters ,并解析响应中 Location 字段得到队列编号
- 从 /queue/item/:queue-id/api/json 检查队列信息,此时 executable 可能为空
- 间断发起轮询,直到返回的 job 携带非空的 executable
- 使用 job.executable.number 作为构建编号,从 /job/:job-name/:job-id/api/json 获取到 job 状态等。
participant user1
participant user2
participant jenkins
user1->>+jenkins: {...}
jenkins->>-user1: /queue/item/1
user2->>+jenkins: {...}
jenkins->>-user2: /queue/item/2
jenkins->>jenkins: enqueue and dequeue
user1->>+jenkins: which job for /queue/item/1?
jenkins->>-user1: /job/101
user2->>+jenkins: which job for /queue/item/2?
jenkins->>-user2: /job/102
LINQPad 的执行结果如下:

拿到的 job 即使已经出队,也可能没有第1时间被运行,至此状态字段 Result 可能为空,仍然需要刷新其任务状态的后续工作。
FUTHER MORE
虽然可以使用轮询获取到构建编号,但因不是原生手段可能有少许延迟。jenkins 提供了名为 JENKINS-CLI 的交互工具,见管理页 "Tools and Actions" 下的 Jenkins CLI 项,我的本地地址是 http://localhost:8080/cli/
$ java -jar jenkins-cli.jar -s http://localhost:8080/ -webSocket -auth admin:admin build --help
java -jar jenkins-cli.jar build JOB [-c] [-f] [-p] [-r N] [-s] [-v] [-w]
-w : Wait until the start of the command (default: false)
其中 job 构建命令支持 -w 参数等待构建触发
If you use the -w option, the command will not return until the build starts and then it will print "Started build #N"
$ java -jar jenkins-cli.jar -s http://localhost:8080/ -webSocket -auth admin:admin build demo
$ java -jar jenkins-cli.jar -s http://localhost:8080/ -webSocket -auth admin:admin build demo -w
Started demo #53
可以看到返回了 job 编号,然而以 http 形式调用并使用抓包工具检查。

发现请求并未使用其约定的地址 /job/:job-name/build 或 /job/:job-name/buildWithParameters,而是向地址 /cli?remoting=false 发起了请求,正文应是预先约定的特定格式
c
00000007000005build
b
00000006000004demo
9
00000004000002-w
a
00000005020003GBK
c
00000007010005en_US
5
0000000003
在远期开发中,该 sdk 值得深入挖掘。
SUMMARY
这里梳理了本人调查和使用 jenkins rest api 构建 job 并获取状态的过程,并试用了 jenkins cli 发现其并未调用常规 endpoints。到目前为止,jenkins 的 job 状态和日志查询已没有巨大的障碍,但许多实现路径有着各种差别,另行讨论。
leoninew 原创,转载请注明来自博客园
jenkins 构建 job 并获取其状态的实现的更多相关文章
- jenkins构建结果企业微信提醒
每当jenkin在构建之后我们想把构建结果SUCCESS/FAILURE或者其他信息通知给其他人,也许有人会说,不是有邮件提醒吗?但是我这里的环境邮件提醒的话所被通知者并不会第一时间去阅读,所以我们用 ...
- Jenkins获取编译状态
背景:在通过python的API调用Jenkins,启动Jenkins的job任务时,是需要知道Jenkins的编译状态,获取编译状态为 status=server.get_build_info(jo ...
- python回调函数应用-获取jenkins构建结果
需求背景: 现在用jenkins构建自动化测试(2个job),公司现将自动化纳入到发布系统 要求每次构建成功之后,把测试结果发送给发布系统.这就需要先获取jenkins构建的结果,如果构建结束,才能发 ...
- jira 改变issue状态触发jenkins构建/发布
目录 jira中issue状态的改变触发Jenkins构建 jira中定制新的workflow,作为jenkins发布使用流程 大家可以参考我的这个workflow 设置workflow 使用Tran ...
- jenkins+sonar发送结果邮件的状态问题修复
在我的这篇博文中:使用jenkins+sonar进行代码扫描,并发送自定义邮件 邮件的配置为默认的$PROJECT_DEFAULT_SUBJECT 所以发送的邮件标题中的状态是jenkins构建的状态 ...
- 通过jenkins-Python在后台操作Jenkins构建job
最近要开发1个接口,接收到1个指令后自动触发自动化测试,虽然也可以通过shell命令做这一步,但因为目前所有构建自动化的的动作都通过jenkins完成,所以想要尝试能不能用python去控制jenki ...
- Jenkins 实现Gitlab事件自动触发Jenkins构建及钉钉消息推送
实现Gitlab事件自动触发Jenkins构建及钉钉消息推送 实践环境 GitLab Community Edition 12.6.4 Jenkins 2.284 Post build task 1. ...
- Jenkins构建时报错:No Space left on device
Jenkins在自动化构建服务的同时也在消耗服务器的磁盘空间,如果构建的项目个数很多,而Jenkins 服务器磁盘空间又不是非常大的话,每隔一段时间磁盘空间就会爆满导致,就会出现磁盘空间不足无法构建的 ...
- Jenkins构建Android项目持续集成之findbugs的使用
Findbugs简介 关于findbugs的介绍,可以自行百度下,这里贴下百度百科的介绍.findbugs是一个静态分析工具,它检查类或者 JAR 文件,将字节码与一组缺陷模式进行对比以发现可能的问题 ...
随机推荐
- javascript事件环微任务和宏任务队列原理
哈喽!大家好!我是木瓜太香,我又来嘞,今天来说说前端面试中经常别问到的 JS 事件环问题. JS 事件环 JS 程序的运行是离不开事件环机制的,这个机制保证在发生某些事情的时候我们有机会执行一个我们事 ...
- windows和linux开启防火墙时允许特定IP和端口
windows 1.进入高级安全Windows Defender防火墙,新建规则中选择自定义 2.直接下一步 3.设置协议类型.本地端口选择和端口号 4.设置允许哪些IP访问这个端口,不设置则默认任何 ...
- jmeter压测以及用Badboy录脚本
一.压测时的基本配置: 1.设置线程数.延迟几秒.循环次数打勾表示一直去循环.调度器打勾可以填写持续时间.启动时间等 线程数:就是并发的用户数 N Ramp-Up Period(in second ...
- [Java并发编程之美]第2章 并发编程的其他基础知识 补充知识
基本概念 并行与并发 并行:单位时间内多个任务同时执行(多核CPU). 并发:同一时间段内多个任务同时都在执行(CPU时间片轮转). 线程安全性问题 线程安全问题:指多个线程同时读写一个共享资源而没有 ...
- Python反转链表
# -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val = x # self.next = None ...
- 关于java for循环常见练习题
使用for循环方式计算2+4+6+…+100的值 package day02; /** * 使用for循环方式计算2+4+6+…+100的值 * @author mu * */ public clas ...
- 集群实战(2):K8S集群节点退出加入操作
以下报错网上其实也可以找到并解决,但是偏零碎我只是根据自己的在使用中遇到的问题做个汇总. 文章目录 首先删掉节点 node重新加入 参考文档 首先删掉节点 注意:以下操作都是在master下操作. 一 ...
- python一些小trick
数据去重 lst = ['1','2','3','3'] lst = list(set(lst)) 不同根目录下引用另一个库 例如 |--a--a.py |--b--b.py 在b.py中调用库a.p ...
- Luogu 3376 【模板】网络最大流
0.网络流解释:如果你还是不能理解,我们就换一种说法,假设s城有inf个人想去t城,但是从s到t要经过一些城市才能到达,(以上图为例)其中s到3城的火车票还剩10张,3到t的火车票还剩15张,其他路以 ...
- WordPress用户角色权限
[转自:群燕小站(http://www.zqunyan.com):原文链接: http://www.zqunyan.com/158.html] WordPress默认的用户角色有5个:订阅者,投稿者, ...