Gitlab-ci 替代 webhook 觸發Jenkins job

網易雲信發表於2022-03-17

導讀: 網易雲信的 gitlab 伺服器搭建在外網,Jenkins 伺服器搭建在內網,因此 gitlab 沒辦法直接把 webhook 傳送給Jenkins,而 pipeline 的搭建採用第三方 relay 轉發的方式,但是這個 relay 經常“罷工”。本文根據網易雲信的落地實踐,詳細介紹瞭如何藉助 Gitlab-ci 替代 webhook 觸發 Jenkins job。

文|鄒李勇 網易雲信資深 C++ 開發/DevOps 工程師

背景

Gitlab 如何觸發 Jenkins job?

我們 push 程式碼或提交 merge request 的時候,gitlab 會傳送一個webhook(單純地理解成一個 http 請求或 restful api 即可)給 Jenkins,請求執行某個 job。

困境

網易雲信使用的 gitlab 服務由杭研維護,伺服器搭建在外網。而我們的  Jenkins 伺服器搭建在內網,這意味著 gitlab 沒辦法直接把 webhook 傳送給Jenkins,而我們的 pipeline 在搭建之初採用了第三方 relay 轉發的方式,把 gitlab 的 webhook 轉發給 Jenkins。雖然把 webhook 從 gitlab 傳送到 Jenkins 的目的達到了,但是這個免費的第三方 relay 可能經常“罷工”。

藉助 Gitlab-ci 替代 webhook 觸發 Jenkins job

gitlab-ci

簡單來講,gitlab-ci 是 gitlab 自帶的特性,通過原始碼根目錄下的 .gitlab-ci.yml 檔案配置。在程式碼  push 或 merge request 的時候自動執行。

以下是官方的解釋:

GitLab Auto DevOps is a collection of pre-configured features and integrations that work together to support your software delivery process.

GitLab CI/CD can automatically build, test, deploy, and monitor your applications by using Auto DevOps.

To use GitLab CI/CD, you need:

  • Application code hosted in a Git repository.
  • A file called .gitlab-ci.yml in the root of your repository, which contains the CI/CD configuration.

官方的 you need 中其實還少了一點 —— GitLab runner,執行 GitLab-ci job 的地方,也就是說 .gitlab-ci.yml 中定義的所有工作都會分發到這裡。

在我們這個實踐中,我們會在 GitLab runner 中搭建一套 python 環境和 python-jenkins 模組,然後通過 python 指令碼觸發 Jenkins job,最後把這個觸發 Jenkins job 的 python 指令碼部署到 gitlab-ci 中。

因此整個實踐分成3個部分。

  1. 搭建 Gitlab runner
  2. 通過 python-jenkins 觸發 Jenkins job
  3. 使用 gitlab-ci 在 push 程式碼和 merge request 時呼叫 python 指令碼

搭建 gitlab runner

安裝 gitlab runner 客戶端

官方: GitLab Runner can be installed and used on GNU/Linux, macOS, FreeBSD, and Windows.

因此選擇任意一臺長期線上的打包機即可。筆者推薦優先選擇 GNU/Linux,通常 linux 上搭建常駐服務要比其他系統更方便,MacOS 次之,最後是 Windows。

以下是 GNU/Linux 平臺的安裝方法的搬運:

  • Download

    To download the appropriate package for your system:

  • Find the latest file name and options athttps://gitlab-runner-downloa...
  • Choose a version and download a binary, as described in the documentation for downloading any other tagged releases for bleeding edge GitLab Runner releases.

For example, for Debian or Ubuntu:

# Replace ${arch} with any of the supported architectures, e.g. amd64, arm, arm64# A full list of architectures can be found here https://gitlab-runner-downloads.s3.amazonaws.com/latest/index.htmlcurl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_${arch}.deb"

For example, for CentOS or Red Hat Enterprise Linux:

​​​​​​​

# Replace ${arch} with any of the supported architectures, e.g. amd64, arm, arm64# A full list of architectures can be found here https://gitlab-runner-downloads.s3.amazonaws.com/latest/index.htmlcurl -LJO "https://gitlab-runner-downloads.s3.amazonaws.com/latest/rpm/gitlab-runner_${arch}.rpm"
  • Install

    • Install the package for your system as follows.

    For example, for Debian or Ubuntu:

dpkg -i gitlab-runner_<arch>.deb

For example, for CentOS or Red Hat Enterprise Linux:

rpm -i gitlab-runner_<arch>.rpm

其他平臺的安裝方案請點選官方教程傳送門:

https://docs.gitlab.com/runne...

通常作為一個 trigger 代理,任務開銷很小,我們可以把 /etc/gitlab-runner/config.toml 配置裡的 concurrent 可以改得大一些,以支援更高的併發量。

註冊 gitlab runner

  • 注意

    • 搭建 gitlab runner 每臺 runner 只需要執行一次
    • 註冊 gitlab runner 每個 git 倉庫每臺 runner 都需要單獨註冊

<!---->

  • 準備工作

    - 先登入 gitlab,進入對應的 git 倉庫(project)

    - 展開左邊側邊欄最下面 Settings -> CI/CD。點選頁面上 Runners 欄右邊的 Expand,頁面往下滾動一點可看到大概如下,注意紅框中的內容。

  • 註冊

    以官方 GNU/Linux 為例,其他平臺高度雷同。

    1. Run the following command:

sudo gitlab-runner register

2. Enter your GitLab instance URL.

上圖紅框中 Register the runner with this URL 下面的內容。

3. Enter the token you obtained to register the runner.

上圖紅框中 And this registration token 下面內容。

4. Enter a description for the runner. You can change this value later in the GitLab user interface.

在 gitlab 中顯示的 runner 描述,該實踐中我們把他當名字用,叫 scapegoat-01。

5. Enter the tags associated with the runner, separated by commas. You can change this value later in the GitLab user interface.

tag 相當於Jenkins中的 label, 用於 runner 分類。該實踐中輸入 scapegoat。

6. Provide the runner executor. For most use cases, enter docker.

這裡我們選 shell, window 可選 powershell。

其他平臺命令相同,官方傳送門:

https://docs.gitlab.com/runne...

註冊成功後可在紅框下面 Available specific runners 裡看到我們剛剛註冊的 runner,類似下圖。其中:

  • scapegoat-01 是 runner 的 description
  • scapegoat 對應 runner 的 tag,tag 可以不只一個

通過 python-jenkins 觸發 Jenkins job

環境安裝

  • 在 gitlab runn er 裡安裝 python

    Linux 發行版中一般自帶,Debian 系列可以使用以下命令,其他平臺可以 Google 檢視。

sudo apt install python或sudo apt install python3
  • 安裝 pip

    python3 通常自帶,python 需要自己安裝。

sudo apt install python-pip
  • 安裝 python-jenkins
python -m pip install python-jenkinsor python3 -m pip install python-jenkins
  • 獲取 gitlab access token

    登入 jenkins,點選選單欄右邊的使用者圖示。

    在彈出的左側邊欄點選 Configure。

    在 APT Token 欄點選 Add new token,輸入 token 名,點選 Generate。

如下紅框中的內容就是新生成的 token,在下面 python 指令碼中要用。

編寫 python 指令碼

#!/usr/bin/python# file name: Jenkins-Compile-trigger.py
import jenkinsimport requestsimport timeimport sysimport os
jenkins_url = "http://yunxin-jenkins.netease.im:8080"# gitlab的登入賬號jenkins_user = "zouliyong" #獲取gitlab access token章節獲取到的tokenjenkins_token = "xxxxxxxxxxxxxxxxx" #Jenkins job namejob_name = "Lava-CI"#從gitlab-ci中獲取Jenkins job的引數,按需修改job_parameters = {                      "_GitlabSourceBranch"    : os.getenv("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"),                    "_GitlabTargetBranch"    : os.getenv("CI_MERGE_REQUEST_TARGET_BRANCH_NAME"),                    "_GitlabMergeRequestLastCommit"  : os.getenv("CI_COMMIT_SHA"),                    "_GitlabSourceRepoHomepage"  : os.getenv("CI_PROJECT_URL"),                    "_GitlabMergeRequestIid"  : os.getenv("CI_MERGE_REQUEST_IID"),                    "_GitlabPipelineId"  : os.getenv("CI_PIPELINE_ID"),                    "_GitlabPipelineUrl" : os.getenv("CI_PIPELINE_URL"),                    "_GitlabJobId"  : os.getenv("CI_JOB_ID"),                    "_GitlabUserName"  : os.getenv("GITLAB_USER_NAME"),                    "_GitlabUserEmail"   : os.getenv("GITLAB_USER_EMAIL")}
build_number = 0build_info = {"building" : False}                 print("jenkins job name: ", job_name)print("jenkins job parameters: ", job_parameters)
#連線Jenkins服務server = jenkins.Jenkins(jenkins_url, username= jenkins_user, password= jenkins_token)
#獲取Jenkins job最後一次build的build numberdef last_build_number(server, job):    last_build = server.get_job_info(job)['lastBuild']    return 1 if None == last_build else last_build['number']

last_build = server.get_job_info(job_name)['lastBuild']next_build_number = last_build_number(server, job_name) + 1
#觸發Jenkinsjobqueue_id = server.build_job(job_name, parameters=job_parameters)print("Jenkins build is waiting for running [queue id = %d] ..." % queue_id)sys.stdout.flush()
#到這裡,Jenkins job已經被放到執行佇列裡了,#只是觸發不用等待Jenkins job結束的話,python指令碼可以到此為止
#等待Jenkins job開始執行while True:    if next_build_number <= last_build_number(server, job_name):        try:            build_info = server.get_build_info(job_name, next_build_number)        except requests.exceptions.RequestException as e:            print(e)            server = jenkins.Jenkins(jenkins_url, username= jenkins_user, password= jenkins_token)            build_info = server.get_build_info(job_name, next_build_number)        if queue_id == build_info["queueId"]:            build_number = next_build_number            print("build number: %d" % build_number)            break        next_build_number = next_build_number + 1    time.sleep(0.1)
print("Jenkins build is running [build number = %d] ..." % build_number)print("Jenkins job URL: %s/job/%s/%d/display/redirect" % (jenkins_url, job_name, build_number))sys.stdout.flush()
#到這裡Jenkins job已經被正式排程,並開始執行
#等待Jenkins job執行結束while build_info["building"]:    try:        build_info = server.get_build_info(job_name, build_number)    except requests.exceptions.RequestException as e:        print(e)        server = jenkins.Jenkins(jenkins_url, username= jenkins_user, password= jenkins_token)        build_info = server.get_build_info(job_name, build_number)    time.sleep(1)
# 獲取執行結果result = server.get_build_info(job_name, build_number)["result"]print("jenkins build result: %s" % result)assert("SUCCESS" == result)

執行如上指令碼即可觸發對應的 Jenkins job,

python Jenkins-Compile-trigger.py

使用 gitlab-ci 在 push 程式碼和 merge request 時呼叫 python 指令碼 編寫 .gitlab-ci.yml 檔案。

#file name: .gitlab-ci.yml
stages:    - build
# 在程式碼push的時候觸發Compilation:    stage: build    tags:        - scapegoat # runner tag 參照上面註冊gitlab runner章節    script:        - python Jenkins-Compile-trigger.py
# 只在merge request的時候觸發CI:    stage: build    tags:        - scapegoat    script:        # 這個指令碼可以根據情況參照Jenkins-Compile-trigger.py自行修改        - python Jenkins-CI-trigger.py     only:        - merge_requests

把如上 .gitlab-ci.yml 和觸發用 Jenkins job 的 python 一起放在原始碼根目錄下,同程式碼一起上傳倉庫。通過 gitlab-ci 觸發 Jenkins job 的功能就實現了。

作者介紹

鄒李勇,網易雲信資深 C++ 開發/DevOps 工程師。Linux 平臺 RTC SDK 開發,負責 DevOps 系統開發和運維。

相關文章