快轉到主要內容

CI 流程串接 Jenkins、GitBucket、Gmail

·2512 字·6 分鐘·
PolloChang
作者
PolloChang
我是一隻雞

GitBucket + Jenkins 自動化建置與 Gmail 通知設定
#

如何設定 GitBucket 程式碼推送(Push)自動觸發 Jenkins 進行 Pipeline 建置(包含程式碼檢查、安全掃描、測試與覆蓋率報告),並在建置完成後自動發送 Gmail 通知。

流程概覽圖
#

  1. 開發者推送(Push)程式碼至 GitBucket (master 分支)。
  2. GitBucket 發送 Webhook 請求(帶有安全 Token)至 Jenkins。
  3. Jenkins 根據 Jenkinsfile 定義的步驟執行 CI 流程。
  4. 執行完畢後,Jenkins 透過 Gmail SMTP 發送通知郵件。

好的,沒問題!這裡直接用 Mermaid 語法幫你把這套自動化流程圖畫出來。你在支援 Markdown Mermaid 的編輯器或平台上就可以直接看到圖形:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
graph TD
    %% 節點定義
    Dev[開發者 Local Dev]
    GitBucket[GitBucket 伺服器<br>port 443 / java17]
    Nginx[Nginx 反向代理]
    Jenkins[Jenkins 伺服器<br>/jenkins 路徑]
    CI_Stages{執行 CI 階段}
    Gmail[Gmail SMTP<br>smtp.gmail.com]
    Inbox[開發者 Gmail 信箱]

    %% 流程線
    Dev -->|1. git push master| GitBucket
    
    GitBucket -->|2. 發送 Webhook 請求<br>帶上 Access Token & 使用 IP| Nginx
    
    Nginx -->|3. 轉發請求至後端| Jenkins
    
    Jenkins -->|4. 比對成功,觸發 Poll SCM<br>回 GitBucket 抓取 Jenkinsfile| GitBucket
    
    Jenkins -->|5. 依據腳本內容| CI_Stages
    
    subgraph Pipeline 執行項目
        CI_Stages -->|Stage 1| S1[./gradlew clean]
        CI_Stages -->|Stage 2| S2[./gradlew checkstyleMain]
        CI_Stages -->|Stage 3| S3[./gradlew dependencyCheckAnalyze]
        CI_Stages -->|Stage 4| S4[./gradlew test jacocoTestReport]
    end

    S1 -->|不論成功或失敗<br>進入全域 post| PostAction[執行 emailext]
    S2 -->|不論成功或失敗| PostAction
    S3 -->|不論成功或失敗| PostAction
    S4 -->|不論成功或失敗| PostAction
    
    PostAction -->|6. 透過應用程式密碼連線| Gmail
    Gmail -->|7. 發送建置報告郵件| Inbox

    %% 樣式調整
    style Dev fill:#f9f,stroke:#333,stroke-width:2px
    style Jenkins fill:#bbf,stroke:#333,stroke-width:2px
    style GitBucket fill:#bfb,stroke:#333,stroke-width:2px
    style Inbox fill:#fbb,stroke:#333,stroke-width:2px

第一部分:專案程式碼與 Jenkins Job 設定
#

為了確保架構的穩定性,捨棄傳統將腳本貼在網頁上的作法,改用 Pipeline script from SCM(基礎設施即程式碼) 形式。

1. 專案端設定(建立 Jenkinsfile)
#

在專案根目錄(與 coins 資料夾同層)建立一個無副檔名的檔案,命名為 Jenkinsfile,並寫入 Pipeline 腳本(含全域 post 寄信邏輯):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
pipeline {

    environment {
        JAVA_HOME = tool 'java-21'
        GRADLE_HOME = tool 'gradle'
        PATH = "${env.JAVA_HOME}/bin:${env.GRADLE_HOME}/bin:${env.PATH}"
    }
    
    agent any
    stages {


        stage('clean') {
            steps {
                dir('coins') {
                    sh './gradlew clean'
                }
            }
        }        
        
        stage('Code Style Check') {
            steps {
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    dir('coins') {
                        sh './gradlew checkstyleMain'
                    }
                }
            }
        }

        stage('OWASP Security Scan') {
            steps {
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    dir('coins') {
                        withCredentials([string(credentialsId: 'NVD-API-KEY', variable: 'NVD_API_KEY')]) {
                            sh './gradlew dependencyCheckAnalyze'
                        }
                    }
                }
            }
        }
        
        stage('Test & Coverage') {
            steps {
                catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
                    dir('coins') {
                        sh './gradlew test jacocoTestReport'
                    }
                }
            } // 欄位修正:在這裡就先結束 steps 區塊
            
            post { // 欄位修正:post 移到跟 steps 同層級
                always {
                    jacoco execPattern: 'coins/**/build/jacoco/*.exec', 
                           classPattern: 'coins/**/build/classes/java/main'
                }
            }
        }
    }


    post {
        always{
            mail to: [EMAIL_ADDRESS]',
            subject: "Jenkins 建置報告: ${env.JOB_NAME} - #${env.BUILD_NUMBER} - ${currentBuild.currentResult}",
            body: """專案名稱: ${env.JOB_NAME}
                    建置編號: ${env.BUILD_NUMBER}
                    建置結果: ${currentBuild.currentResult}
                    詳細連結: ${env.BUILD_URL}

                    請點擊上方連結至 Jenkins 查看詳細報告。"""
        }
    }
}
  • 動作:將此檔案 commitpush 至 GitBucket 的 master 分支。

2. Jenkins Job 基礎組態設定
#

  1. 進入 Jenkins 點選您的 Job(例如 coins-test),點擊 Configure
  2. 勾選 Build Triggers 區塊中的 Poll SCMSchedule 欄位請保持完全空白
  3. 移至 Pipeline 區塊,將 Definition 切換為 Pipeline script from SCM
  4. SCM 選擇 Git
  5. Repository URL 輸入:[email protected]:pollo-lab/ims.git
  6. Credentials 選擇:Jenkins-ssh-key
  7. Branch Specifier 輸入:*/master
  8. 點擊 Save 儲存。

第二部分:安全憑證與 Webhook 連線設定
#

1. Jenkins 端:發行安全 Token
#

  1. 依序進入 管理 Jenkins -> Security(鎖頭圖示)。
  2. 找到 Git plugin notifyCommit access tokens(或 Access tokens 按鈕)。
  3. 點擊 Add new access token,輸入名稱(如 gitbucket-ims),點擊 Generate
  4. 複製畫面上顯示的 Token 密碼,並點擊網頁最下方的 Save

2. GitBucket 端:配置 Webhook
#

  1. 進入 GitBucket 的專案儲存庫,依序點選 Settings -> Webhooks -> Add webhook
  2. Payload URL 欄位填入結合了路徑與安全 Token 的完整網址: https://127.0.0.1/jenkins/git/[email protected]:pollo-lab/ims.git&token=【您的Token】
  3. 觸發事件選擇 Push,完成建立。

第三部分:常見地雷與疑難排解 (Troubleshooting)
#

以下為本架構佈署過程中,於內部網路環境最常遭遇的三大錯誤與對應解法:

問題一:SSL 憑證不被 Java 環境信任
#

  • 錯誤訊息javax.net.ssl.SSLHandshakeException: PKIX path building failed: ... unable to find valid certification path to requested target
  • 原因:GitBucket 運行的 Java 環境不信任 Jenkins 伺服器所使用的自簽憑證或內部 CA。
  • 解決方法
  1. 在 GitBucket 伺服器上透過 openssl 抓取 Jenkins 的憑證:
1
openssl s_client -connect base.home.pollochang.work:443 -showcerts </dev/null | openssl x509 -outform PEM > /root/jenkins.crt
  1. 使用 keytool 將憑證強制匯入該 Java 版本的信任庫(預設密碼為 changeit):
1
keytool -importcert -trustcacerts -alias jenkins-server -file /root/jenkins.crt -keystore /usr/local/lib/jvm/java17-latest/lib/security/cacerts -storepass changeit -noprompt
  1. 重新啟動 GitBucket 服務。

問題二:主機名稱與憑證安全網域不符
#

  • 錯誤訊息javax.net.ssl.SSLPeerUnverifiedException: Certificate for <domain> doesn't match any of the subject alternative names: [*.localhost, localhost]
  • 原因:雖然 Java 信任了該憑證,但連線時發現網址(如 base.home.pollochang.work)並不在憑證所登記的允許域名(SAN)清單內。
  • 解決方法
  • 方案 A (最推薦):直接將 GitBucket 的 Webhook Payload URL 中的域名替換為憑證內已被允許的 IP 位址(例如 https://127.0.0.1/...)。
  • 方案 B:改用非加密的 HTTP 連線進行內部互連(如 http://base.home.pollochang.work:8080/...)。

問題三:連線遭 Jenkins 拒絕 (HTTP 401 Unauthorized)
#

  • 錯誤訊息:Webhook 測試回傳狀態碼 401 Unauthorized
  • 原因:新版 Jenkins 安全機制規定,外部系統呼叫 /git/notifyCommit 端點觸發輪詢時,必須通過身分驗證。
  • 解決方法
  • 按照【第二部分】的步驟,在 Jenkins 安全設定中核發 Git 外掛專用的 Access Token,並在 GitBucket 的 Webhook 網址後方加上 &token=... 參數。

第四部分:變更驗證流程
#

當上述設定皆完成後,請依循以下步驟進行端到端(End-to-End)測試:

  1. 首次引導:在 Jenkins 介面中手動點擊一次 Build Now(馬上建置)。這能讓 Jenkins 透過實體執行,在歷史紀錄中記住專案的 Git 網址。
  2. 自動化測試:在本地端修改程式碼,執行 git commitgit push 至 GitBucket 的 master 分支。
  3. 預期結果
  • GitBucket 的 Webhook 紀錄應顯示傳送成功(HTTP 200 或相關正確回應標頭)。
  • Jenkins 的 coins-test 專案會在幾秒內被自動勾起並開始建置。
  • 建置完成後,您的 Gmail 將會收到一封包含專案名稱、建置編號與結果的建置報告郵件。