Stories

Detail Return Return

50多張圖詳細記錄——使用Jenkins完成前端項目CICD自動化部署教程(不踩坑!) - Stories Detail

前言概述

本文記錄使用docker安裝jenkins以後,推送代碼到giteegitlab上,而後執行構建前端項目的過程,對應cicd的操作設置流程環節

包含:手動觸發構建和webhook自動化構建

50多張圖,詳細記錄過程,且按照筆者的這篇文章操作,不會踩坑被卡好久(筆者已經踩過坑了)

前端CICD自動化部署角色分工

前端開發過程中,關於項目發佈上線,標準化而言涉及四個角色(假設是四台硬件設備)

四個角色分工

  1. 自己的電腦——用來寫代碼(通過git push把本機代碼推送到gitlab服務器)
  2. gitlab服務器——用來存代碼(注意,gitlab上面的代碼,一般無法直接pushjenkins服務器上,需要jenkins主動從gitlab那裏去“撈”代碼
  3. jenkins服務器——用來執行自動化cicd操作(jenkins把“撈”到的代碼執行npm run build構建操作,並把構建產物傳送到生產服務器)
  4. 生產服務器——存放前端構建產物,並使用nginx代理對應html\css\js靜態資源給用户訪問用

若是前端寫的node中間層BFF服務(BFF服務不需要再執行npm run build,直接用源碼js跑),則使用pm2進行管理,推薦也使用nginx做代理

比如nginx把/bff/*前綴過來的請求轉發給http://localhost:3000上運行的fastify服務(不暴露接口ip統一使用nginx作為入口)

角色分工圖示

如圖

獲取gitlab的代碼,可以手動,也可以自動

上圖中,代碼git push推送不用贅述,關鍵在於gitlab和jenkins的通信這一塊

————如何讓jenkins能夠從gitlab中獲取(撈)對應代碼、如何觸發?以及

————如何讓jenkins能夠收到gitlab發來webhook請求,如何配置

當然還有jenkins和測試/生產服務器的通信——把代碼丟過去(畢竟生產服務器不允許隨意上傳文件操作命令啥的)

我們先看:如何讓jenkins能夠從gitlab中獲取(撈)對應代碼、如何觸發?

jenkins撈gitlab代碼的兩種方式

以下兩種方式的前提是相關配置已經提前做好了

  • 方式一,人工手動點擊撈代碼觸發構建

    • 我們直接登錄jenkins系統,找到對應項目列表中
    • 點擊“立即構建”按鈕,觸發jenkins去gitlab拿代碼,如下圖

  • 方式二,使用gitlab自帶的webhook

    • 當代碼寫完push到gitlab後,gitlab能夠感知到有人提交代碼了
    • 並使用自帶的webhook鈎子函數功能,給jenkins發送一個請求,告知jenkins可以過來拿代碼了
    • jenkins收到請求後,就會自動拉取gitlab的代碼了,如下圖

最終jenkins構建打包dist以後,就可以把dist目錄裏面的代碼,丟到生產或者測試服務器上去了(筆者使用rsync命令傳輸文件的)

方式一、手動點擊觸發jenkins拉取gitee代碼構建CICD步驟流程

自動webhook觸發後文也會記錄

接下來,我們在jenkins創建一個項目(做相應配置),採取手動觸發的方式(點一下),讓jenkins拉取gitee倉庫的代碼,並通過編寫的shell腳本,構建dist產物,最後通過rsync把產物傳輸的生產目錄,再接入釘釘機器人告知構建成功

筆者用這台烏班圖服務,集成在一塊了,既有gitlab、又有jenkins,也有生產的nginx,就是服務運行端口不同

1. 安裝gitee插件

在jenkins中安裝gitee插件,插件很多很豐富,視情況安裝

注意,最初安裝jenkins的時候,這裏選擇默認安裝推薦的插件

2. 新建一個自由風格的jenkins任務,做基礎信息填寫

自由風格和流水線用的比較多一些

流水線更加靈活,我們先看一下自由風格的

然後填寫項目描述,丟棄舊的構建,只保留三個歷史構建結果

選擇git,填寫gitee倉庫,並準備新建憑證,並使用這個憑證

然後這一步,先停下來

3. 接下來打開gitee,準備生成一個私人令牌

個人主頁-->設置-->安全設置-->私人令牌

注意權限把控,具體勾選那個,看實際情況

生成私人令牌

4. 妥善保存私人令牌,然後在jenkins中添加憑據使用此令牌

然後選擇剛剛新建的憑證

注意,憑證可以在:系統管理-->憑據頁面 進行後續管理(比如刪除、更新等)

5. 構建基礎設置

指定master分支的代碼,為構建分支代碼(就是jenkin會通過上述的認證,讓gitee允許它進行代碼拉取操作,拉取master分支代碼)

Delete workspace before build starts

所謂的工作空間,就是一個文件夾目錄,裏面存放了新建的jenkins任務

因為筆者的jenkins是通過docker部署的,所以進入這個docker容器內部,能看到一些jenkins具體信息,比如工作空間

6. 編寫shell腳本進行構建

Build Steps-->增加構建步驟

這裏選擇使用使用shell腳本進行構建

構建腳本如下

shell腳本代碼

# 驗證環境
node -v
npm -v
rsync --version
curl --version

# 修改國內鏡像源頭
npm config set registry https://registry.npmmirror.com

# 安裝依賴並構建
npm install
npm run build

# 把構建的dist丟到宿主機目錄
rsync -avz --delete /var/jenkins_home/workspace/gitee/dist/ root@172.17.0.1:/var/www/html/reactExamples/

# 通知釘釘機器人構建成功了
curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=access_token" -H "Content-Type: application/json" -d "{\"msgtype\": \"text\", \"text\": {\"content\": \"Jenkins 構建成功\"}}"

注意事項

  • 1 因為要執行npm run build所以jenkins中,需要有node環境 所以容器裏面要提前準備好node環境!!!
  • 2 rsync這個包比scp更好用,可以在服務器與服務器之間(也可以把容器內的文件傳到宿主機),傳輸文件 這裏所以容器裏面要提前準備好rsync包!!!
  • 3 注意,想要順利使用rsync這個包,需在容器內生成一套 SSH 密鑰對,再將公鑰配置到宿主機即可

    • 比如 ssh-keygen -t rsa -b 4096 -C "jenkins@container"
  • 4 rsync語法傳輸介紹

rsync -avz --delete /var/jenkins_home/workspace/gitee/dist/ root@172.17.0.1:/var/www/html/reactExamples/

意思是:將容器內jenkins工作空間目錄/var/jenkins_home/workspace/gitee/dist/ 的文件(裏面的html、css、js),同步到宿主機(IP 為 172.17.0.1)的 /var/www/html/reactExamples/ 目錄,並且保持兩者內容完全一致(會刪除宿主機上多餘的文件)

**`rsync`**:核心命令,用於文件同步(支持本地 / 遠程同步,增量傳輸)。

**`-avz`**:組合選項,常用核心參數:
    - `-a`(archive,歸檔模式):遞歸同步目錄,保留文件權限、所有者、時間戳等元數據(最常用的 “安全同步” 模式)。
    - `-v`(verbose,詳細輸出):顯示同步過程中的文件列表,方便查看進度。
    - `-z`(compress,壓縮傳輸):傳輸時對文件進行壓縮,節省網絡帶寬(適合遠程同步)。

**`--delete`**:關鍵選項,**刪除目標目錄中 “源目錄沒有” 的文件 / 目錄**,確保目標目錄與源目錄完全一致(避免目標目錄殘留過期文件)。例如:如果源目錄刪除了 `old.txt`,同步時會自動刪除目標目錄的 `old.txt`。

**`/var/jenkins_home/workspace/gitee/dist/`** :源路徑(容器內的目錄),末尾的 `/` 表示 “同步該目錄下的內容”(而非目錄本身)。若不加 `/`,會將 `dist` 目錄本身同步到目標路徑下(變成 `reactExamples/dist/...`)。

**`root@172.17.0.1:/var/www/html/reactExamples/`** :目標路徑(宿主機的目錄):
    - `root@172.17.0.1`:宿主機的 SSH 登錄信息(用户名 `root` + 宿主機 IP `172.17.0.1`)。
    - `:/var/www/html/reactExamples/`:宿主機上的目標目錄。

### 執行效果:
- 首次同步:將源目錄的所有文件完整複製到目標目錄。
- 後續同步:只傳輸修改過的文件(增量同步,效率高),並刪除目標目錄中源目錄沒有的文件,最終兩者完全一致。

注意,/var/www/html/reactExamples/這個目錄筆者提前使用nginx做了代理,如下:

# react的一些示例
location /reactExamples/ {
    alias /var/www/html/reactExamples/;
    try_files $uri $uri/ /reactExamples/index.html;
}

7. 通過釘釘機器人實現釘釘羣構建成功消息推送

使用curl命令(要提前新建羣,創建好釘釘機器人,用於得知構建成功消息結果)

不清楚如何創建釘釘機器人,可以參考官方文檔:https://open.dingtalk.com/document/dingstart/custom-bot-creat...

提醒一下,別忘了保存jenkins的設置

8. 人工手動構建點擊,並查看構建結果

點擊立即構建

查看構建輸出日誌

最終成功嘍

釘釘羣的消息

方式二、gitlab的webhook觸發jenkins構建CICD步驟流程

  • 一般來説,git push代碼以後,再到jenkins上手動點擊一下,發佈項目,適用於生產環境
  • 而git push代碼以後,直接就發佈了(不需要登錄jenkins),使用於測試環境

1. 什麼是webhook?

  • webhook可以理解成為鈎子函數——當某個預定義事件發生時,觸發軟件服務發送http請求
  • 好多軟件服務都提供了webhook鈎子
  • 比如釘釘、飛書、Zabbix、GitHub、GitLab、Gitee、支付寶完成、微信支付完成等

核心邏輯

  1. WebHook 的本質是 “事件回調”:當gitlab倉庫發生提交、合併等操作時,gitlab會向我配置的jenkins回調地址,發送一個包含事件信息(如提交作者、分支、變動文件列表)的http請求
  2. 這個請求僅起到 “觸發信號” 的作用,不攜帶任何代碼文件本身
  3. Jenkins 收到該通知後,會基於已配置的憑據(個人令牌或 SSH 密鑰),主動向gitlab倉庫發起拉取請求,獲取最新代碼(類似 git pull 的邏輯)。
webhook只是消息通知,不能直接傳輸代碼,核心是觸發henkins主動拉取最新代碼

方式一,筆者是給到的gitee案例,接下來使用gitlab案例進行記錄

建議購買一個雲服務器,ssl證書,真正的跑通,或者使用vmware虛擬機,把gitlab、jenkins部署好去實踐

可以參考筆者之前的文章:Ubuntu服務器上使用docker-compose部署 gitlab(圖文並茂記錄)

2. gitlab放開出站請求並初步配置webhook

首先,有一個gitlab的倉庫代碼,要首先放開出站請求

然後到代碼倉庫裏面,找到設置中的webhook如下

點擊添加webhook

先填寫點基礎信息

3. gitlab創建流水線任務

如下圖,新建任務

填寫基本信息

4. 填寫身份驗證令牌

指定身份驗證令牌,並採用/buildWithParameters?token='TOKEN_NAME'這種方式觸發構建

5. 身份驗證令牌規則

這裏使用/buildByToken/build方式配置會少一些
  • 現在,筆者的jenkin服務運行在29877端口上,TOKEN\_NAME的值是gitlab-jenkins-token-2025
  • 那麼webhook的url就是:
  • https://ashuai.site:29877/buildByToken/build?job=gitlab&token=gitlab-jenkins-token-2025
  • 注意,這裏有兩個query參數,job的值,是此構建任務的名字,也就是上方瀏覽器地址欄去掉configure的
  • 也就是構建的任務名字

即:

// jenkins服務的運行ip端口/buildByToken/build?job=任務名&token=TOKEN_NAME
https://ashuai.site:29877/buildByToken/build?job=gitlab&token=gitlab-jenkins-token-2025

6. 在gitlab中的webhook中使用對應的url

填寫

這個時候,測試跑不通的,因為還要在pipe line中編寫腳本呢(gitlab任務也要保存哦)

7. 編寫Pipeline script流水線腳本代碼

代碼如下:

pipeline {
    agent any
    
    stages {
        stage('Clean Workspace') {
            steps {
                echo '🧹 清理工作空間確保構建純淨...'
                cleanWs()
            }
        }
        
        stage('Checkout Code') {
            steps {
                echo '📥 從GitLab拉取最新代碼...'
                git branch: 'main', 
                    url: 'https://ashuai.site:12345/lss-group/react-example.git',
                    credentialsId: 'gitlab'
                sh 'ls -la'
            }
        }
        
        stage('Install Dependencies') {
            steps {
                echo '📦 安裝項目依賴,使用npm ci替代npm install安裝...'
                sh 'npm ci'
                
                script {
                    if (fileExists('node_modules')) {
                        echo "✅ 依賴安裝成功"
                    } else {
                        error "❌ 依賴安裝失敗"
                    }
                }
            }
        }
        
        stage('Build Project') {
            steps {
                echo '🏗️ 構建生產版本...'
                sh 'npm run build'
                
                sh '''
                    echo "=== 構建驗證 ==="
                    if [ -d "dist" ] && [ -f "dist/index.html" ]; then
                        echo "✅ 構建成功"
                        ls -la dist/
                        echo "構建大小: \$(du -sh dist/ | cut -f1)"
                    else
                        echo "❌ 構建失敗:缺少必要文件"
                        exit 1
                    fi
                '''
            }
        }
        
        stage('Deploy') {
            steps {
                echo '🚀 部署到宿主機目錄...'
                script {
                    
                    // 執行 rsync 部署
                    sh '''
                        echo "開始部署..."
                        echo "源目錄: /var/jenkins_home/workspace/gitlab/dist/"
                        echo "目標目錄: root@172.17.0.1:/var/www/html/reactExamples/"
                        
                        # 執行 rsync 同步代碼把構建產物發送到生產(宿主機)對應目錄
                        rsync -avz --delete /var/jenkins_home/workspace/gitlab/dist/ root@172.17.0.1:/var/www/html/reactExamples/
                        
                        # 檢查部署結果
                        if [ $? -eq 0 ]; then
                            echo "✅ 部署成功完成"
                        else
                            echo "❌ 部署失敗"
                            exit 1
                        fi
                    '''
                }
            }
        }
    }
    
    post {
        always {
            echo "📊 ===== 構建報告 ====="
            echo "🔢 構建號: #${env.BUILD_NUMBER}"
            echo "⏱️ 構建時間: ${currentBuild.durationString}"
            echo "📋 構建結果: ${currentBuild.currentResult}"
            echo "🌐 構建URL: ${env.BUILD_URL}"
            sh '''
                echo "💾 工作空間大小: \$(du -sh . | cut -f1)"
                echo "📁 構建產物: \$(find dist/ -type f 2>/dev/null | wc -l) 個文件"
            '''
        }
        success {
            echo "🎉 ===== 構建部署成功 ====="
            echo "✅ 前端項目已成功構建並打包"
            echo "📦 構建產物位於 dist/ 目錄"
            echo "🚀 已部署到宿主機: /var/www/html/reactExamples/"
            # 成功了,通過釘釘告知
            sh 'curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=access_token" -H "Content-Type: application/json" -d \'{"msgtype": "text", "text": {"content": "Jenkins 構建成功"}} \''
        }
        failure {
            echo "❌ ===== 構建失敗 ====="
            echo "🔍 請檢查構建日誌排查問題"
            echo "💡 常見問題:依賴安裝失敗、構建腳本錯誤、網絡問題、部署失敗"
            # 失敗了,也通過釘釘告知
            sh 'curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=access_token" -H "Content-Type: application/json" -d \'{"msgtype": "text", "text": {"content": "Jenkins 構建失敗"}} \''
        }
        changed {
            echo "🔄 構建狀態發生變化"
        }
    }
}

注意,這裏要特別注意這個代碼

stage('Checkout Code') {
    steps {
        echo '📥 從GitLab拉取最新代碼...'
        git branch: 'main', 
            url: 'https://ashuai.site:12345/lss-group/react-example.git',
            credentialsId: 'gitlab'
        sh 'ls -la'
    }
}
  • 先前配置的TOKEN\_NAME然後把Jenkins的對應url搭配TOKEN\_NAME,作為gitlab的webhook的地址用,這個是讓gitlab的請求能發過來
  • 但是在自動化構建過程中,我們在流水線腳本里面,手動去拉取代碼了
  • 需要需要指定對應的憑證idcredentialsId: 'gitlab'
  • 這裏的憑證id和方式一設置憑證id一樣,也是要在gitlab中生成個人令牌

然後和先前的圖示(方式一的第四步)一樣,也要提前準備好憑證

credentialsId: 'gitlab'的值 也就是

這樣的話,在流水線腳本里面拉取代碼才能成功

但是 這個時候 webhook還是跑不通的 我們繼續往下看

8. 批准Pipeline script流水線腳本

腳本不批准,構建會報錯

或者在系統管理裏面 也能看到待批准的腳本

點擊 Approve 按鈕批准

但是 這個時候 webhook還是跑不通的 我們繼續往下看

9. 安裝Build Authorization Token Root Plugin插件

  • 前面提到了gitlab的webhook的url的值填成:
  • https://ashuai.site:29877/buildByToken/build?job=gitlab&token=gitlab-jenkins-token-2025
  • 使用/buildByToken/build這種方式,一定要搭配Build Authorization Token Root Plugin插件
  • 否則會403錯誤,這個是jenkins的安全策略,如下報錯圖

  • 原因是Jenkins啓用了CSRF 保護(跨站請求偽造防護) ,而GitLab的Webhook請求中沒有包含有效的crumb(校驗令牌)
  • 去設置校驗令牌有些麻煩,所以筆者採用了漸變一些的方式,使用Build Authorization Token Root Plugin這個插件——token認證繞過了CSRF保護
  • 安裝啓用

這樣的話,就能夠正常收到gitlab的webhook發來的請求了,測試也成功了

10. 成功構建,自動發佈

也能看到對應構建日誌

釘釘也能收到對應消息

這樣的話,當筆者把代碼直接推送到gitlab的倉庫後,就自動觸發倉庫設置好的webhook發請求給jenkins,jenkins收到請求後,執行pipe line流水線的腳本,拉取代碼,構建項目,傳送到生產環境宿主機裏對應文件夾目錄,然後通知釘釘


  • 代碼push提交,系統自動完成構建、測試、部署全過程
  • 即為:cicd自動化部署

關於前端部署的三種方式

分為純手動部署,腳本半自動化部署、自動化cicd部署

方式一、純手動部署

  1. 傳統前端項目如React、Vue的部署
  • 第一步,本地執行npm run build把前端項目打包成dist文件夾
  • 第二步,通過winscp或xmanager之類的文件傳輸工具遠程鏈接服務器
  • 第三步,找到對應服務器上的文件夾目錄(存放生產服務文件)
  • 第四步,先刪再增,把現在本機電腦的dist文件夾裏面的靜態資源文件替換掉服務器上的
  1. NodeJs中間層服務如Express、Koa、Fastify的部署
  • 第一步,通過Git 或者winscp更新最新代碼(刪除原來的)
  • 第二步,命令行執行pm2 restart myNodeProject重新加載項目

需要人工操作各個步驟,稍微有些耗時,且不優雅穩妥

方式二、腳本半自動化部署

  1. 傳統前端項目如React、Vue的部署
  • Js腳本方式,比如可參考筆者之前的文:https://segmentfault.com/a/1190000044616092
  • Shell腳本大致是這樣的,比如我們新建一個deploy.sh腳本
#!/bin/bash
# 前端部署shell腳本

# --------------------------------- 全局的配置 ------------------------------
LOCAL_DIST="./dist"                  # 當前項目打包dist目錄
SERVER="root@192.168.1.100"         # 服務器賬號+IP(例:root@1.2.3.4)
SERVER_DIR="/usr/share/nginx/html"   # 服務器存放前端文件的目錄(Nginx默認是這個)
# ----------------------------------------------------------------------

# 1. 本地打包
echo "1. 開始打包..."
npm run build || { echo "打包失敗!"; exit 1; }

# 2. 刪服務器舊文件(先清再傳,避免殘留)
echo "2. 刪除服務器舊文件..."
ssh $SERVER "rm -rf $SERVER_DIR/*" || { echo "刪舊文件失敗!"; exit 1; }

# 3. 傳新文件到服務器
echo "3. 上傳新文件..."
scp -r $LOCAL_DIST/* $SERVER:$SERVER_DIR || { echo "傳文件失敗!"; exit 1; }

# 4. 部署完成
echo "✅ 部署成功!"

這種方式,需要確保腳本權限,終端執行

chmod +x deploy.sh

前端npm run build打包完畢以後,直接./deploy.sh就能直接部署了

不過得輸服務器密碼,可以考慮配置 SSH 密鑰免密登錄,就是本地生成 SSH 密鑰對,再把公鑰複製一份傳到服務器上,這樣就不用輸入密碼了

  1. NodeJs中間層服務如Express、Koa、Fastify的部署
#!/bin/bash

SERVER="root@192.168.1.100"         
SERVER_PROJECT_DIR="/opt/my-node-app"  
PM2_APP_NAME="my-node-service"     # pm2 管理的服務名稱(啓動時定義的名稱)

# 1. 登錄服務器,拉取倉庫最新代碼(提前做好服務器的ssh秘鑰免密登錄,確保能拉取到gitlab代碼)
echo "1. 拉取最新代碼..."
ssh $SERVER "cd $SERVER_PROJECT_DIR && git pull origin main" || { echo "拉取代碼失敗!"; exit 1; }

# 2. 安裝生產環境依賴(忽略 devDependencies)
echo "2. 安裝依賴..."
ssh $SERVER "cd $SERVER_PROJECT_DIR && npm install --production" || { echo "安裝依賴失敗!"; exit 1; }

# 3. 用 pm2 重啓服務(使新代碼生效)
echo "3. 重啓服務..."
ssh $SERVER "pm2 restart $PM2_APP_NAME" || { echo "重啓服務失敗!"; exit 1; }

# 4. 部署完成
echo "✅ Node.js 服務部署成功!"

shell腳本需要熟悉一下linux命令,推薦這兩個網站 https://www.linuxcool.com/和https://www.masswerk.at/jsuix/ind...

方式三、自動化Jenkins部署

就是上述本文操作流程...

補充bff的jenkins部署

  • 流程和上面一樣,依舊是git push提交代碼
  • 觸發gitlab的webhook告知jenkins可以拉取最新代碼了
  • 然後使用Publish Over SSH 插件

  • Add SSH Server,並做對應的信息填寫
  • 就可以在流水線腳本里面派發命令了
stages {
  stage('派發命令到生產服務器') {
    steps {
      sshPublisher(publishers: [
        sshPublisherDesc(
          configName: '生產服務器',
          transfers: [
            sshTransfer(
              execCommand: """
                pm2 restart fastify-app-bff || true
              """
            )
          ]
        )
      ])
    }
  }
}
  • 或者直接使用jenkins的執行shell腳本搭配SSH命令(適合複雜場景)
  • 篇幅原因,不再贅述

額外的docker部署

  • 如果服務器上,要部署多個node項目,多個node版本環境要求嚴格
  • 主推使用docker優雅部署,筆者也有一篇docker部署前端項目的文:https://segmentfault.com/a/1190000047275173
  • 實際上,項目部署是很靈活的,打包構建這一塊也不一定都得由Jenkins去做(假設Jenkins服務器配置不太好)
  • 假設本機配置高,也可以把npm run builddocker build -t 本地鏡像名:版本號 .交給本機來做
  • 然後,給再把構建好的鏡像打個標籤,推送到Harbor倉庫裏面
  • 再推送代碼到gitlab倉庫裏面,依舊能夠通過webhook觸發Jenkins的執行
  • jenkins不用再去gitlab裏面拉取代碼了,直接把harbor裏面的鏡像拉取過來
  • 然後通過 SSH 連接生產服務器,最後執行 docker run 啓動容器完成部署

所以,誰來做構建、誰來做傳輸,亦或是派發命令執行,本就是靈活的選擇

總而言之,jenkins是一個很靈活的東西,具體怎麼設置看實際情況...
user avatar kobe_fans_zxc Avatar gushiio Avatar wuyagege Avatar yangy5hqv Avatar mihuartuanr Avatar liulhf Avatar awbeci Avatar huobaodejianpan Avatar carlsonblog Avatar familyaboveall Avatar xiaoxxuejishu Avatar ouysh1981 Avatar
Favorites 18 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.