在现代软件开发中,持续集成与持续部署(CI/CD)已经成为提高开发效率、保证代码质量的重要手段。对于前端项目来说,如何快速、稳定地将代码从开发环境推送到生产环境,是一个关键问题。本文将详细介绍如何使用 Jenkins 和 Nginx 实现前端项目的 CI/CD 流程,确保每次代码提交都能自动触发构建、测试和部署,从而减少人为操作带来的错误,提升开发团队的整体效率。
我们将从 Jenkins 的安装与配置开始,逐步介绍如何安装必要的插件(如 NodeJS Plugin 和 Publish Over SSH),配置 GitLab 代码仓库访问权限,以及如何在 Windows Server 上启用和配置 OpenSSH Server。接着,我们会讲解如何编写 Jenkins Pipeline 脚本,自动化整个构建和部署流程,并使用 Nginx 作为静态文件服务器来托管前端应用。通过本文的指导,你将能够搭建一个完整的 CI/CD 环境,轻松实现前端项目的自动化部署。
jenkins 部署 : [笔记] Jenkins 安装与配置全攻略:Ubuntu 从零开始搭建持续集成环境
java 项目部署 : [笔记] 使用 Jenkins 实现 CI/CD :从 GitLab 拉取 Java 项目并部署至 Windows Server
一. 安装插件
Manage Jenkins -> Manage Plugins -> Available plugins 安装:
- NodeJS Plugin(用于构建前端项目)
- Publish Over SSH(用于文件传输和远程执行)
二. 配置 NodeJS 环境
Manage Jenkins -> Tools -> NodeJS installations:
- Name: NodeJS
- Version: 选择需要的版本(如:NodeJS 22.13.0)
- 勾选 “Automatically install”
三. 配置代码仓库
我的代码仓库是使用的 极狐 gitlab
1. 获取 gitlab 的访问令牌(Access tokens)
期间 read_repository 必须勾选
- 仅需拉取代码:
如果 Jenkins 只需要从 GitLab 拉取代码(例如通过 Git-over-HTTP 或 Repository Files API),那么只需授予 read_repository 权限即可。- 需要与 GitLab API 交互:
如果 Jenkins 需要调用 GitLab API 来进行其他操作(比如获取项目详细信息、触发构建、管理合并请求等),你将需要授予 read_api 权限。这包括对所有群组和项目的读访问权、容器镜像库和软件包库的访问权等。- 高级集成:
在某些情况下,你可能还需要更多的权限,例如推送代码、创建标签或分支等。这些情况通常需要更高的权限级别,如 write_repository 或自定义权限设置。
2. 配置 jenkins 凭证
- 进入凭证管理
- 配置全局凭证
- 添加凭证
- 选择类型 Username with password
四. (win服务器使用) Windows Server 2019 启动 OpenSSH Server
1. 安装 OpenSSH Server
# 以管理员身份运行 PowerShell
# 安装 OpenSSH Server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# 设置自动启动
Set-Service sshd -StartupType Automatic
Set-Service ssh-agent -StartupType Automatic
# 启动 SSH 服务
Start-Service sshd
Start-Service ssh-agent
# 确认服务状态
Get-Service sshd
2. 配置 SSH 服务
编辑 C:\ProgramData\ssh\sshd_config 文件,添加或修改以下内容:
# 编辑 C:\ProgramData\ssh\sshd_config 文件,添加或修改以下内容:
PasswordAuthentication yes
PermitRootLogin yes
Subsystem powershell c:/progra~1/powershell/7/pwsh.exe
KexAlgorithms diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp256
HostKeyAlgorithms ssh-rsa,rsa-sha2-512,rsa-sha2-256
3. 重启 SSH 服务
Restart-Service sshd
4. 配置防火墙
# 添加防火墙规则
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
五. 配置 SSH Servers
1. 使用账号密码
注意 : windows 服务器的配置 Remote Directory 只能使用基础目录 , 不可修改
2. 使用秘钥
a. 生成SSH密钥对
- 打开终端或命令提示符。
- 运行以下命令生成SSH密钥对:
# 生成 RSA 密钥对
ssh-keygen -t rsa -b 4096 -C "jenkins@example.com"
# 密钥默认保存在:
# - 私钥:C:\Users\jenkins\.ssh\id_rsa
# - 公钥:C:\Users\jenkins\.ssh\id_rsa.pub
ssh-keygen
: 用于生成、管理和转换认证密钥对的命令行工具,主要用于SSH协议。通常位于/usr/bin/ssh-keygen
或/usr/local/bin/ssh-keygen
。-t rsa
:-t
指定要生成的密钥类型。rsa
表示生成RSA类型的密钥。RSA是一种非对称加密算法,广泛用于SSH连接。其他常见的密钥类型包括dsa
(较少使用)、ecdsa
和ed25519
。-b 4096
:-b
指定密钥的位数。4096
表示生成的RSA密钥长度为4096位。默认情况下,ssh-keygen
生成的RSA密钥长度为2048位。增加密钥长度可以提高安全性,但也会稍微增加计算开销。4096位是一个常用的、安全的选择。-C "your_email@example.com"
:-C
添加一个注释(comment)到生成的公钥中。"your_email@example.com"
是你提供的注释内容,通常是你的电子邮件地址。
-
按照提示操作,可以接受默认路径和文件名(通常是
~/.ssh/id_rsa
和~/.ssh/id_rsa.pub
)。
-
Enter passphrase (empty for no passphrase):
系统会提示你输入一个密码短语(passphrase)。如果你设置了密码短语,每次使用私钥时都需要输入它。这增加了安全性,但也增加了使用的复杂性。(如果你不希望设置密码短语,直接按回车键即可。)
b. 将公钥复制到 Windows Server
- 将生成的公钥文件(通常是
~/.ssh/id_rsa.pub
)的内容复制到剪贴板。 - 将公钥内容添加到
authorized_keys
文件
# 创建 authorized_keys 文件并添加公钥内容
# 将 Jenkins 服务器上 id_rsa.pub 的内容复制到这个文件
Add-Content -Path C:\Users\Administrator\.ssh\authorized_keys -Value "ssh-rsa AAAA..."
-Value
是你的~/.ssh/id_rsa.pub
的内容
c. 编辑 C:\ProgramData\ssh\sshd_config
# 启用公钥认证
PubkeyAuthentication yes
# 指定授权密钥文件位置
AuthorizedKeysFile .ssh/authorized_keys
# 可选:禁用密码认证(更安全)
PasswordAuthentication no
d. 设置文件权限
# 设置 .ssh 目录的权限
icacls "C:\Users\Administrator\.ssh" /inheritance:r
icacls "C:\Users\Administrator\.ssh" /grant "SYSTEM:(F)"
icacls "C:\Users\Administrator\.ssh" /grant "ADMINISTRATORS:(F)"
icacls "C:\Users\Administrator\.ssh" /grant "Administrator:(F)"
# 设置 authorized_keys 文件的权限
icacls "C:\Users\Administrator\.ssh\authorized_keys" /inheritance:r
icacls "C:\Users\Administrator\.ssh\authorized_keys" /grant "SYSTEM:(F)"
icacls "C:\Users\Administrator\.ssh\authorized_keys" /grant "ADMINISTRATORS:(F)"
icacls "C:\Users\Administrator\.ssh\authorized_keys" /grant "Administrator:(F)"
e. 重启 SSH 服务
# 重启 SSH 服务
Restart-Service sshd
f. 在 Jenkins 中配置 SSH Publisher:
path to key 与 key 只需要配置一个就可以
3. 测试连接
六. 配置 jobs
1. 配置参数
# 项目基础信息
parameters {
// 1. Git配置
string(
name: 'GIT_URL',
defaultValue: 'http://gitlab.mada.com/sc-wms/sc-wms-web.git',
description: 'GitLab项目地址'
)
// 2. 服务器配置
choice(
name: 'SERVER_IP',
choices: ['192.168.0.192', '192.168.0.193', '192.168.0.194'],
description: '部署服务器IP'
)
// 3. 项目名称
string(
name: 'FOLDER_NAME',
defaultValue: 'sc-wms',
description: '部署文件夹名称'
)
// 4. 构建环境
choice(
name: 'BUILD_ENV',
choices: ['prod'],
description: '构建环境'
)
// 5. 日志路径
string(
name: 'DEPLOY_LOG_PATH',
defaultValue: 'D:\CICD\deploy\web\deploy.log',
description: '日志文件路径'
)
// 6. 目标路径
string(
name: 'BASE_TARGET_PATH',
defaultValue: 'D:\CICD\project',
description: '目标基础路径'
)
// 7. 部署脚本路径
string(
name: 'DEPLOY_BAT_PATH',
defaultValue: 'D:\CICD\deploy\web\deploy.bat',
description: '部署脚本路径'
)
// 8. 前端服务端口
string(
name: 'PORT',
defaultValue: '9099',
description: '前端服务端口'
)
// 9. Nginx 安装路径
string(
name: 'NGINX_PATH',
defaultValue: 'D:\CICD\nginx',
description: 'Nginx 安装路径'
)
// 10. Nginx 配置模板路径
string(
name: 'NGINX_TEMPLATE_PATH',
defaultValue: 'D:\CICD\deploy\web\nginx.conf.template',
description: Nginx 配置模板路径
)
// 11. Nginx 配置模板路径
string(
name: 'BACKEND_PORT',
defaultValue: '9016',
description: 后端服务端口
)
}
2. 编写 Jenkins 脚本 (pipeline script)
注意 :
- 脚本中的
credentialsId
换成你的 jenkins 中配置的 gitlab 访问凭证的唯一标识 (id)。- windwos 服务器 , jenkins 只能直接调用脚本。
pipeline {
agent any
environment {
// NodeJS环境
NODE_HOME = tool name: 'NodeJS', type: 'jenkins.plugins.nodejs.tools.NodeJSInstallation'
PATH = "${NODE_HOME}/bin:${env.PATH}"
// 项目路径
TARGET_PATH = "${params.BASE_TARGET_PATH}\\${params.FOLDER_NAME}"
}
stages {
stage('Checkout') {
steps {
git branch: 'main',
url: "${params.GIT_URL}",
credentialsId: '08434a83-d80e-49fb-b3f8-53fa77e5927b'
}
}
stage('Install') {
steps {
sh 'npm install --registry=https://registry.npmmirror.com'
}
}
stage('Build') {
steps {
script {
if (params.BUILD_ENV == 'prod') {
sh 'npm run build:prod'
} else {
sh 'npm run build:dev'
}
}
}
}
stage('Deploy') {
steps {
script {
// 记录开始部署
sh """
DEPLOY_TIME=\$(date '+%Y-%m-%d %H:%M:%S')
echo "[\$DEPLOY_TIME] Starting deployment for ${params.FOLDER_NAME} to ${params.SERVER_IP}" >> "${params.DEPLOY_LOG_PATH}"
"""
// 使用SSH Plugin传输文件并执行部署脚本
sshPublisher(
publishers: [
sshPublisherDesc(
configName: "${params.SERVER_IP}",
transfers: [
sshTransfer(
sourceFiles: 'dist/**/*',
remoteDirectory: "${params.FOLDER_NAME}",
cleanRemote: false,
excludes: '',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: true,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectorySDF: false,
removePrefix: '',
execCommand: "${params.DEPLOY_BAT_PATH} \"${params.FOLDER_NAME}\" \"${params.BASE_TARGET_PATH}\\${params.FOLDER_NAME}\" \"${params.DEPLOY_LOG_PATH}\" \"${params.PORT}\" \"${params.NGINX_PATH}\" \"${params.NGINX_TEMPLATE_PATH}\" \"${params.BACKEND_PORT}\""
)
]
)
]
)
}
}
}
}
post {
success {
script {
sh """
DEPLOY_TIME=\$(date '+%Y-%m-%d %H:%M:%S')
echo "[\$DEPLOY_TIME] Deployment successful" >> "${params.DEPLOY_LOG_PATH}"
echo "==================== 部署成功 ====================" >> "${params.DEPLOY_LOG_PATH}"
echo "项目: ${params.FOLDER_NAME}" >> "${params.DEPLOY_LOG_PATH}"
echo "环境: ${params.BUILD_ENV}" >> "${params.DEPLOY_LOG_PATH}"
echo "Git地址: ${params.GIT_URL}" >> "${params.DEPLOY_LOG_PATH}"
echo "服务器: ${params.SERVER_IP}" >> "${params.DEPLOY_LOG_PATH}"
echo "部署路径: ${TARGET_PATH}\\dist" >> "${params.DEPLOY_LOG_PATH}"
echo "================================================" >> "${params.DEPLOY_LOG_PATH}"
"""
}
}
failure {
script {
sh """
DEPLOY_TIME=\$(date '+%Y-%m-%d %H:%M:%S')
echo "[\$DEPLOY_TIME] Deployment failed" >> "${params.DEPLOY_LOG_PATH}"
echo "==================== 部署失败 ====================" >> "${params.DEPLOY_LOG_PATH}"
echo "项目: ${params.FOLDER_NAME}" >> "${params.DEPLOY_LOG_PATH}"
echo "环境: ${params.BUILD_ENV}" >> "${params.DEPLOY_LOG_PATH}"
echo "Git地址: ${params.GIT_URL}" >> "${params.DEPLOY_LOG_PATH}"
echo "服务器: ${params.SERVER_IP}" >> "${params.DEPLOY_LOG_PATH}"
echo "================================================" >> "${params.DEPLOY_LOG_PATH}"
"""
}
}
cleanup {
cleanWs()
}
}
}
七. 服务器脚本与 nginx
1. 项目目录
D:\CICD\ # Windows 服务器上的自动化部署目录
├── deploy/ # 部署相关
│ ├── web/ # 前端部署相关内容
│ │ ├── deploy.bat # 前端部署脚本
│ │ └── nginx.conf.template # 前端项目 Nginx 配置模板
│ └── backend/ # 后端部署相关内容
│ └── deploy.bat # 后端部署脚本
├── nginx/ # nginx 安装目录
│ ├── conf/ # nginx 配置目录
│ │ ├── mime.types # MIME 类型配置
│ │ ├── nginx.conf # nginx 主配置
│ │ └── project_conf/ # 项目配置目录
│ ├── contrib/ # 第三方贡献
│ ├── docs/ # 文档
│ ├── html/ # 网站文件目录
│ ├── logs/ # 日志目录
│ │ ├── access.log # 访问日志
│ │ └── error.log # 错误日志
│ ├── temp/ # 临时文件目录
│ │ ├── client_body_temp/ # 客户端请求体临时文件
│ │ ├── proxy_temp/ # 代理临时文件
│ │ ├── fastcgi_temp/ # FastCGI 临时文件
│ │ ├── uwsgi_temp/ # uWSGI 临时文件
│ │ └── scgi_temp/ # SCGI 临时文件
│ └── nginx.exe # nginx 可执行文件
└── project/ # 项目相关
├── sc-wms/ # sc-wms 项目
│ ├── dist/ # 前端 dist
│ └── sc-wms.jar # 后端 jar 包
├── other-project/ # 其他项目
└── ... # 更多项目
2. 修改主配置文件 (nginx.conf)
注意修改成你的绝对路径
#user nobody;
worker_processes 1;
error_log D:/CICD/nginx/logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
pid D:/CICD/nginx/logs/nginx.pid;
events {
worker_connections 1024;
}
http {
# 临时目录配置 - 放在http块最前面
client_body_temp_path "D:/CICD/nginx/temp/client_body_temp";
proxy_temp_path "D:/CICD/nginx/temp/proxy_temp";
fastcgi_temp_path "D:/CICD/nginx/temp/fastcgi_temp";
uwsgi_temp_path "D:/CICD/nginx/temp/uwsgi_temp";
scgi_temp_path "D:/CICD/nginx/temp/scgi_temp";
include mime.types;
default_type application/octet-stream;
# 日志格式定义
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log D:/CICD/nginx/logs/access.log main; # 这里改为绝对路径
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
# 开启 gzip
gzip on;
gzip_min_length 1k;
gzip_comp_level 6;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
# 客户端请求大小限制
client_max_body_size 20m;
# 引入项目配置
include project_conf/*.conf;
}
3. 项目 nginx.conf 模板 (D:\CICD\deploy\web\nginx.conf.template)
server {
listen ${PORT};
server_name localhost;
charset utf-8;
# 前端静态文件
location / {
root ${APP_PATH};
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
# 代理后端API请求
location /prod-api/ {
proxy_pass http://localhost:${BACKEND_PORT}/;
proxy_set_header Host $host;
rewrite "^/prod-api/(.*)" /$1 break;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
4. 编写部署脚本 (D:\CICD\deploy\web\deploy.bat)
@echo off
setlocal EnableDelayedExpansion
:: 检查参数
if "%~1"=="" (
echo Error: folder_name parameter is missing
goto :usage
)
if "%~2"=="" (
echo Error: target_path parameter is missing
goto :usage
)
if "%~3"=="" (
echo Error: deploy_log parameter is missing
goto :usage
)
if "%~4"=="" (
echo Error: port parameter is missing
goto :usage
)
if "%~5"=="" (
echo Error: nginx_path parameter is missing
goto :usage
)
if "%~6"=="" (
echo Error: template_path parameter is missing
goto :usage
)
if "%~7"=="" (
echo Error: backend_port parameter is missing
goto :usage
)
:: 设置变量
set "FOLDER_NAME=%~1"
set "TARGET_PATH=%~2"
set "DEPLOY_LOG=%~3"
set "PORT=%~4"
set "NGINX_PATH=%~5"
set "NGINX_CONF_TEMPLATE=%~6"
set "BACKEND_PORT=%~7"
set "NGINX_EXE=%NGINX_PATH%\nginx.exe"
set "NGINX_CONF=%NGINX_PATH%\conf\nginx.conf"
:: 清空日志文件
echo. > "%DEPLOY_LOG%"
:: 记录部署开始
echo %date% %time% - Starting frontend deployment >> %DEPLOY_LOG%
echo Configuration: >> %DEPLOY_LOG%
echo FOLDER_NAME=%FOLDER_NAME% >> %DEPLOY_LOG%
echo TARGET_PATH=%TARGET_PATH% >> %DEPLOY_LOG%
echo PORT=%PORT% >> %DEPLOY_LOG%
echo BACKEND_PORT=%BACKEND_PORT% >> %DEPLOY_LOG%
echo NGINX_PATH=%NGINX_PATH% >> %DEPLOY_LOG%
echo TEMPLATE_PATH=%NGINX_CONF_TEMPLATE% >> %DEPLOY_LOG%
echo NGINX_CONF=%NGINX_CONF% >> %DEPLOY_LOG%
:: 检查Nginx是否安装
if not exist "%NGINX_EXE%" (
echo %date% %time% - Error: Nginx not found at %NGINX_EXE% >> %DEPLOY_LOG%
goto :error
)
:: 检查项目nginx_projectconf是否存在
if not exist "%NGINX_PATH%\conf\project_conf" (
md "%NGINX_PATH%\conf\project_conf"
echo %date% %time% - Created project_conf directory >> %DEPLOY_LOG%
)
:: 检查模板文件是否存在
if not exist "%NGINX_CONF_TEMPLATE%" (
echo %date% %time% - Error: Template file not found at %NGINX_CONF_TEMPLATE% >> %DEPLOY_LOG%
goto :error
)
:: 生成新的配置文件
echo %date% %time% - Generating Nginx configuration... >> %DEPLOY_LOG%
set "CONF_FILE=%NGINX_PATH%\conf\project_conf\%FOLDER_NAME%.conf"
:: 读取模板并替换变量
for /f "delims=" %%i in (%NGINX_CONF_TEMPLATE%) do (
set "line=%%i"
set "line=!line:${APP_PATH}=%TARGET_PATH%\dist!"
set "line=!line:${PORT}=%PORT%!"
set "line=!line:${BACKEND_PORT}=%BACKEND_PORT%!"
echo !line!>> "%CONF_FILE%.tmp"
)
move /y "%CONF_FILE%.tmp" "%CONF_FILE%" > nul
:: 移动文件
echo %date% %time% - Moving new files... >> %DEPLOY_LOG%
if exist "%USERPROFILE%\%FOLDER_NAME%\dist" (
:: 使用robocopy移动文件
robocopy "%USERPROFILE%\%FOLDER_NAME%\dist" "%TARGET_PATH%\dist" /E /MOVE /R:1 /W:1 >> %DEPLOY_LOG% 2>&1
:: 清理源目录
rd /s /q "%USERPROFILE%\%FOLDER_NAME%" >> %DEPLOY_LOG% 2>&1
echo %date% %time% - Files moved successfully >> %DEPLOY_LOG%
) else (
echo %date% %time% - Error: Source dist directory not found >> %DEPLOY_LOG%
goto :error
)
:: 测试Nginx配置
echo %date% %time% - Testing Nginx configuration... >> %DEPLOY_LOG%
"%NGINX_EXE%" -t -c "%NGINX_CONF%" >> %DEPLOY_LOG% 2>&1
if errorlevel 1 (
echo %date% %time% - Error: Invalid Nginx configuration >> %DEPLOY_LOG%
goto :error
)
:: 重新加载Nginx配置
echo %date% %time% - Reloading Nginx configuration... >> %DEPLOY_LOG%
"%NGINX_EXE%" -s reload -c "%NGINX_CONF%" >> %DEPLOY_LOG% 2>&1
echo %date% %time% - Deployment completed successfully >> %DEPLOY_LOG%
echo Project %FOLDER_NAME% deployed at http://localhost:%PORT% >> %DEPLOY_LOG%
exit /b 0
:error
echo %date% %time% - Deployment failed >> %DEPLOY_LOG%
exit /b 1
:usage
echo Usage: deploy.bat [folder_name] [target_path] [deploy_log] [port] [nginx_path] [template_path] [backend_port]
echo Current parameters:
echo folder_name: %FOLDER_NAME%
echo target_path: %TARGET_PATH%
echo deploy_log: %DEPLOY_LOG%
echo port: %PORT%
echo nginx_path: %NGINX_PATH%
echo template_path: %NGINX_CONF_TEMPLATE%
echo backend_port: %BACKEND_PORT%
exit /b 1
八. 验证
1. 运行 jobs
2. 打开网页(你发布的服务器+端口)
标签:DEPLOY,部署,CICD,前端,echo,nginx,params,ssh,PATH From: https://blog.csdn.net/LuChangQiu/article/details/145053298