jenkins 構建 job 並獲取其狀態的實現


leoninew 原創,轉載請注明來自博客園

BACKGROUND

使用 jenkins rest api 觸發的 job 會先進入任務隊列,然后異步執行,而無法直接獲取到被觸發的 job 構建記錄編號。雖然 job 的描述信息中 lastBuild 字段告知了最后的構建記錄,但無論是先獲取 lastBuild,自增其編號作為下次構建 id,還是請求內等待 lastBuild 更新作為構建記錄的做法,都存在若干問題:

  1. 由於構建任務延遲觸發,先觸發 job 構建再緊接着獲取 lastBuild 的多數情況下將返回歷史而非當前的構建記錄,不可行。
  2. 以 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)

  1. 在多個調用方同時進行 job 構建時,無法判斷誰的構建先觸發,出現后續的狀態/日志錯位。

INVESTIGATION I

雖然 jenkins 在觸發 job 構建的請求中僅返回了 201(No content),但 jenkins 提供了內置隊列查詢接口。另一方面閱讀網頁和查看 jenkins sdk 的 python 版本 python-jenkins 實現后,得知 jenkins 的 job 構建接口有以下實現細節,其中第 1、3 種情況下,返回的響應會攜帶 Location 字段,攜帶了隊列編號。

  1. 無參: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)
  1. 有參: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 命令執行以下操作

  1. 觸發名為 demo 的 job 構建
  2. 觸發名為 rdc-pipline 的 job 構建,使用參數 branch=A
  3. 觸發名為 demo 的 job 構建
  4. 觸發名為 rdc-pipline 的 job 構建,使用參數 branch=A
  5. 觸發名為 rdc-pipline 的 job 構建,變更參數,使用參數 branch=B

可以看到分別返回了以下 Location

可以看到最后的參數變化生成了兩條隊列記錄,目前為止我們有以下結論:

1. 連續觸發的相同 job 構建不會重復入隊

job 構建的成本因內內容不同差異很大,但觸發 job 構建的成本很小,我們可以輕易地提交大量構建請求,基於以下認知可以理解 jenkins 不進行重復構建的意義:

  1. 雖然代碼可能觸發前后的短時間內變化,但這是小概率事件;
  2. 常規情況下我們並不需要獲取多份各自獨立的編譯結果,將請求入隊以節流方式構建是可行的;

所以 jenkins 只會為短時間內重復觸發的 job 構建一次。

2. 連續觸發的不同 job 構建會各自入隊

雖然 jenkins 不會重復構建相同 job,但在多個 job 同時觸發構建的時候,執行構建仍是必須的,入隊只是前置。

為什么 jenkins 不在隊列為空、資源可用的情況下避免入隊環節直接構建?這里沒有深入調查。

3. 參數變動的相同 job 構建將分別入隊

雖然有避免重復構建相同 job 的必要,但是當參數變化時,構建仍是必須的,否則就丟失了請求,這是不能容忍的。

目前所用 sdk 只實現了基於 /job/:job-name/build 的構建接口,因入隊后隊列編號的意義重大,后續的討論基於實現和使用 /job/:job-name/buildWithParameters 之上。

4. 允許同時觸發構建將有數據錯亂的可能

雖然參數相同的構建會被 jenkins 以排重形式處理,但參數不同時,沒有區分策略可言

  1. user1 和 user2 准備發起相同名稱但參數不同的 job 構建;
  2. 無論當前的構建隊列是否為空,user1 和 user2 觸發的構建都會入隊
  3. 檢查 job 信息,無法知曉 lastBuild 或 nextBuildNumber 的歸屬
sequenceDiagram participant user1 participant user2 participant jenkins user1-->>jenkins: {...} user2-->>jenkins: {...} jenkins->>jenkins: enqueue and dequeue user1->>jenkins: which job? user2->>jenkins: which job?

5. 分布式鎖強制使得入隊或構建觸發串行化不可行

  1. 強制使構建過程串行化,在計算資源及時間成本上不可接受;
  2. 強制使入隊串行化,意味着 jenkins 的排重機制不生效,將出現構建過程串行化;

這類做法違背了 jenkins 的設計意圖,加劇了服務器負載,使得響應時間延長,有導致服務器不可用的風險。

INVESTIGATION II

看起來無法把希望寄托於目前 sdk 提供的 job 信息中 lastBuild 信息上,應在隊列上繼續調查。閱讀到 Check Jenkins job status after triggering a build remotely 后,找到可行方法,其步驟如下:

  1. 提交 job 構建請求到地址 /job/:job-name/buildWithParameters ,並解析響應中 Location 字段得到隊列編號
  2. 從 /queue/item/:queue-id/api/json 檢查隊列信息,此時 executable 可能為空
  3. 間斷發起輪詢,直到返回的 job 攜帶非空的 executable
  4. 使用 job.executable.number 作為構建編號,從 /job/:job-name/:job-id/api/json 獲取到 job 狀態等。
sequenceDiagram 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 原創,轉載請注明來自博客園


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM