Jenkins CI/CD 实战:Vite 前端发布、Koa + PM2 后端部署、权限隔离与远程发布
本文以 Jenkins Freestyle Project 为基础,完整实现一套前后端项目的持续集成与持续部署流程。
前端项目采用 Vite,Jenkins 负责拉取代码、安装依赖和执行构建,生成的 dist 通过 rsync 发布到 Nginx 静态目录;后端项目采用 Koa + TypeScript,通过 npm run build 编译到 dist,再以 npm run start 作为统一启动入口,并交由 PM2 管理。
在此基础上,进一步使用 Role-based Authorization Strategy 按 Job 前缀实现项目级权限隔离,并补充 Publish Over SSH 远程部署方案,使 Jenkins 与业务服务器分离时仍能保持清晰的构建与发布边界。
示例环境如下:
Jenkins 服务器:192.168.31.88
远程业务服务器:192.168.31.90
Jenkins 端口:8080
Git 仓库:https://gitee.com/linhao-dev/side-blog.git
Jenkins 初始化管理员:linhao
前端 Job:web_test
后端 Job:api_test
前端发布目录:/home/html/web_test
后端发布目录:/home/apps/api_test
Koa 端口:3000
示例仓库采用前后端同仓结构:
side-blog/
├── web/
│ ├── src/
│ ├── package.json
│ ├── pnpm-lock.yaml
│ └── vite.config.ts
│
└── api/
├── src/
├── package.json
├── package-lock.json
└── tsconfig.json
如果前端与后端分别位于独立仓库,只需去掉后续构建脚本中的 cd web 或 cd api。
整体流程如下:
Git 仓库
│
▼
Jenkins
├── web_test
│ │
│ ├─ pnpm install
│ ├─ npm run build
│ ▼
│ web/dist
│ │
│ ├─ 同机:rsync
│ └─ 远程:Publish Over SSH
│ │
│ ▼
│ Nginx
│
└── api_test
│
├─ npm ci
├─ npm run build
▼
api/dist
│
├─ 同机:同步到运行目录
└─ 远程:Publish Over SSH
│
▼
npm run start
│
▼
PM2
│
▼
Nginx
本文涉及的核心职责边界如下:
| 层次 | 主要职责 | 使用组件 |
|---|---|---|
| 源码 | 版本管理、触发构建 | Gitee / Git |
| CI | 拉取代码、安装依赖、执行构建 | Jenkins |
| 前端发布 | 将 dist 同步到静态目录 | rsync / Publish Over SSH |
| 后端发布 | 安装生产依赖、启动或重启服务 | PM2 |
| 入口层 | 静态资源与 API 反向代理 | Nginx |
| 权限 | 控制用户可访问的 Job 范围 | Role-Based Strategy |
1. 安装 Jenkins
Jenkins 运行依赖 Java。本文使用 Java 21:
sudo dnf install -y fontconfig java-21-openjdk
java -version
添加 Jenkins LTS 仓库:
sudo wget -O /etc/yum.repos.d/jenkins.repo \
https://pkg.jenkins.io/rpm-stable/jenkins.repo
sudo rpm --import https://pkg.jenkins.io/rpm-stable/jenkins.io-2026.key
安装 Jenkins:
sudo dnf install -y jenkins
sudo systemctl daemon-reload
sudo systemctl enable --now jenkins
确认是否已经开机启动:
systemctl is-enabled jenkins
systemctl status jenkins
看到:
enabled
Active: active (running)
返回 enabled 且服务状态为 active (running),即表示 Jenkins 已正常启动并设置为开机自启。
内网或测试环境可开放 Jenkins 默认端口 8080:
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
浏览器打开:
http://192.168.31.88:8080
首次访问 Jenkins 时需要读取初始化密码:
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
初始化阶段可选择 Install suggested plugins 安装 Jenkins 推荐插件。
初始化向导中创建管理员账号:
linhao
该账号作为 Jenkins 全局管理员。后续配置项目级权限时,仅新增 api 和 web 两个普通账号。
初始化完成后进入 Jenkins 首页:
初始化完成后,Jenkins 首页应能够看到以下核心入口:
新建 Item
构建历史
Manage Jenkins
构建队列
构建执行状态
后续所有 Job、插件、工具链和权限配置都从这些入口完成。
2. 安装 Jenkins 所需插件
进入:
Manage Jenkins
→ Plugins
在 Manage Jenkins 中,本篇主要会用到以下配置入口:
Plugins → 安装 NodeJS Plugin、Role-based Authorization Strategy、Publish Over SSH
Tools → 配置 Node.js 24
System → 配置 Publish Over SSH 远程服务器
Security → 启用 Role-Based Strategy
Users → 创建 Jenkins 普通用户
本文涉及以下三个插件:
NodeJS Plugin
Role-based Authorization Strategy
Publish Over SSH
Publish Over SSH 仅用于远程发布;同机部署阶段无需配置。
NodeJS Plugin
搜索 NodeJS,安装 NodeJS Plugin:
插件安装完成后,在 Installed plugins 中应能看到:
NodeJS Plugin
Status:Enabled
如果插件已安装但未启用,需要先启用后再进入 Tools 配置 Node.js。
需要注意:服务器上即使已经通过 NVM 安装 Node,Jenkins Job 也未必能够直接使用。
Jenkins Job 默认以 Linux 用户 jenkins 执行,而 NVM 通常安装在 root 或其他用户目录。为避免构建过程依赖用户级 NVM 环境,本文通过 NodeJS Plugin 为 Jenkins 独立配置 Node 运行环境。
3. Jenkins 配置 Node 24
进入:
Manage Jenkins
→ Tools
→ NodeJS installations
新增一套:
Name:node24
Install automatically:勾选
NodeJS Version:24.x
Global npm packages to install:pnpm pm2
前端构建使用 pnpm,后端进程管理使用 PM2,因此可在该工具配置中统一安装。
如果希望 CI 环境更稳定,也可以固定主版本:
pnpm@10 pm2@6
NodeJS 工具建议配置为:
Name:node24
Install automatically:√
NodeJS Version:24.x
Global npm packages to install:pnpm pm2
如果希望 CI 环境更稳定,可以固定主版本:
pnpm@10 pm2@6
保存以后,每个 Job 在“构建环境”里勾上:
Provide Node & npm bin/ folder to PATH
然后选择:
node24
完成配置后,Execute shell 中可直接使用:
node -v
npm -v
pnpm -v
pm2 -v
4. 配置 Git 私有仓库凭据
新建 Job 后,在:
源码管理
→ Git
仓库填:
https://gitee.com/linhao-dev/side-blog.git
如果是私有仓库,Credentials 不能留空。
HTTPS 仓库我一般添加:
Kind:Username with password
Username:linhao-dev
Password:Gitee 私人令牌
Password 建议填写 Personal Access Token,不建议直接保存 Git 平台登录密码。
分支根据项目实际情况填:
*/main
如果仓库还是 master:
*/master
配置时如果出现:
Incorrect username or password (access token)
该错误通常由 Credentials 配置错误导致,应优先检查用户名、Token 与仓库访问权限。
5. Jenkins Job 命名规范
为便于后续通过 Role Pattern 进行权限隔离,Job 统一采用前缀命名,不再沿用临时测试名称 vite-web:
web_xxx 前端
api_xxx 后端
示例创建两个 Freestyle Project:
web_test
api_test
对应的权限正则可统一定义为:
^web_.*$
^api_.*$
这样新增同类 Job 时,无需为每个项目单独维护一套角色。
6. 前端 web_test:Vite 构建与同机发布
创建一个 Freestyle Project:
新建 Item
→ web_test
→ Freestyle project
Git、Credentials 与 Node 24 按前述方式配置。
同机部署场景如下:
Jenkins 和 Nginx 在同一台服务器
最终就是:
Git
↓
Jenkins
↓
cd web
↓
pnpm install
↓
npm run build
↓
web/dist
↓
rsync
↓
/home/html/web_test
↓
Nginx
Jenkins 拉取代码
│
▼
进入 web 目录
│
▼
pnpm install
│
▼
npm run build
│
▼
生成 web/dist
│
▼
rsync --delete
│
▼
/home/html/web_test
│
▼
Nginx
6.1 配置发布目录权限
服务器执行一次:
sudo mkdir -p /home/html/web_test
sudo chown -R jenkins:jenkins /home/html/web_test
sudo chmod 755 /home
sudo chmod 755 /home/html
sudo chmod -R u+rwX,go+rX /home/html/web_test
建议使用 jenkins 用户执行一次写入测试:
sudo -u jenkins touch /home/html/web_test/test.txt
sudo rm -f /home/html/web_test/test.txt
命令成功执行即表示 jenkins 用户具备目标目录写权限。
6.2 web_test 构建脚本
在 Build Steps → Execute shell 中配置以下脚本:
#!/usr/bin/env bash
set -Eeuo pipefail
readonly DEPLOY_DIR="/home/html/web_test"
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=2048}"
log() {
printf '
[%s] %s
' "$(date '+%F %T')" "$*"
}
log "检查构建环境"
node -v
npm -v
pnpm -v
log "进入前端目录"
cd "${WORKSPACE}/web"
log "安装依赖"
pnpm install \
--frozen-lockfile \
--dangerously-allow-all-builds
log "执行 Vite 构建"
npm run build
if [[ ! -d "dist" ]]; then
echo "ERROR: dist 目录不存在,终止发布。" >&2
exit 1
fi
log "同步静态资源到 Nginx 目录"
rsync -rv --delete \
dist/ \
"${DEPLOY_DIR}/"
log "web_test 发布完成"
脚本中几个关键参数的作用:
| 配置 | 作用 |
|---|---|
set -Eeuo pipefail | 任一关键命令失败时立即终止,避免失败后继续发布 |
NODE_OPTIONS | 提高 Node 构建阶段可用堆内存,避免 vue-tsc / Vite OOM |
--frozen-lockfile | CI 中严格按照 lock 文件安装依赖 |
--delete | 删除目标目录中已经不属于新版本的旧静态资源 |
${WORKSPACE} | Jenkins 当前 Job 的工作空间目录 |
发布阶段采用:
rsync -rv --delete
相比简单的 cp,rsync 更适合前端静态资源发布,因为构建文件通常包含 hash:
index-a83d.js
index-27fd.js
新版本已经没有的旧文件,--delete 会一起清掉。
如果使用:
rsync -av --delete
在部分权限配置下可能出现:
chgrp failed: Operation not permitted
静态资源发布通常无需保留源文件的 owner/group,因此使用 -rv 可以避免不必要的权限继承。
6.3 配置 Nginx 静态目录
Nginx root 指向发布目录:
server {
listen 80;
server_name web-test.example.com;
root /home/html/web_test;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
检查:
sudo nginx -t
sudo systemctl reload nginx
后续 Jenkins 仅替换静态文件,无需在每次构建后 reload Nginx。
如果使用 Vue Router history 模式,需要保留以下配置:
try_files $uri $uri/ /index.html;
7. 前端构建与发布常见问题
以下问题均发生在构建或发布阶段,容易被误认为 Jenkins 本身异常。
7.1 pnpm 10:Ignored build scripts
安装依赖时如果看到:
ERR_PNPM_IGNORED_BUILDS
Ignored build scripts: esbuild ...
典型日志类似:
[ERR_PNPM_IGNORED_BUILDS]
Ignored build scripts:
@parcel/watcher
esbuild
vue-demi
Run "pnpm approve-builds" to pick which dependencies
should be allowed to run scripts.
看到这个错误时,说明依赖已经解析完成,但部分依赖的安装脚本被 pnpm 安全策略阻止。
这是新版 pnpm 对依赖生命周期脚本做了限制。
长期维护建议在仓库中显式配置允许执行 build script 的依赖;对于完全可信的内部项目,也可以使用:
pnpm install --dangerously-allow-all-builds
因此示例构建脚本采用该参数。
7.2 vue-tsc 构建 OOM
类型检查阶段如果出现:
FATAL ERROR: Allocation failed
JavaScript heap out of memory
典型日志:
FATAL ERROR:
Ineffective mark-compacts near heap limit
Allocation failed - JavaScript heap out of memory
ELIFECYCLE Command failed with exit code 134
如果错误出现在 vue-tsc、TypeScript 类型检查或 Vite 构建阶段,优先检查 Node 堆内存限制。
通常发生在 vue-tsc 类型检查阶段。
Execute shell 开头加:
export NODE_OPTIONS="--max-old-space-size=2048"
项目更大可以给 4096:
export NODE_OPTIONS="--max-old-space-size=4096"
同时需要保证服务器具有足够的可用内存。
7.3 构建成功但 rsync Permission denied
如果前面已经 build 完成,最后报:
Permission denied
mkdir failed
mkstemp failed
典型日志:
rsync: mkdir ".../static" failed: Permission denied (13)
rsync: mkstemp ".../index.html.xxx" failed: Permission denied (13)
rsync error: some files/attrs were not transferred (code 23)
此时说明构建已经完成,失败点在“发布目录写入权限”,排查重点应转向 Linux 用户与目录权限。
该问题与 Vite 构建无关,通常是 jenkins 用户缺少目标目录写权限。
重新确认:
sudo chown -R jenkins:jenkins /home/html/web_test
sudo chmod 755 /home/html
sudo chmod -R u+rwX,go+rX /home/html/web_test
sudo -u jenkins touch /home/html/web_test/test.txt
touch 测试通过后,再重新执行 Jenkins 构建。
8. 后端 api_test:Koa 构建与 PM2 进程管理
后端示例创建 Job:
api_test
下面使用一个最小 Koa + TypeScript 项目说明完整构建与运行流程。
目录:
api/
├── src/
│ └── index.ts
├── package.json
├── package-lock.json
└── tsconfig.json
8.1 package.json
{
"name": "api-test",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/index.js"
},
"dependencies": {
"koa": "^2.16.0"
},
"devDependencies": {
"@types/koa": "^2.15.0",
"@types/node": "^24.0.0",
"typescript": "^5.9.0"
}
}
项目统一通过以下脚本启动:
npm run start
PM2 同样托管 npm run start,避免在 Jenkins 中写死具体入口文件。
8.2 tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "Node",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*.ts"]
}
8.3 src/index.ts
import Koa from 'koa';
const app = new Koa();
const port = Number(process.env.PORT || 3000);
app.use(async (ctx) => {
if (ctx.path === '/health') {
ctx.body = {
code: 0,
message: 'ok',
service: 'api_test'
};
return;
}
ctx.body = {
code: 0,
message: 'Hello from Koa'
};
});
app.listen(port, '0.0.0.0', () => {
console.log(`api_test started on ${port}`);
});
提交 Jenkins 构建前可先在本地验证:
cd api
npm install
npm run build
npm run start
构建完成后应生成:
api/dist/index.js
访问:
http://127.0.0.1:3000/health
返回:
{
"code": 0,
"message": "ok",
"service": "api_test"
}
9. 配置后端 Jenkins Job api_test
新建:
api_test
→ Freestyle project
Git、Credentials 与 Node 24 配置与 web_test 保持一致。
api_test 的构建、发布与运行关系如下:
Jenkins 拉取代码
│
▼
进入 api 目录
│
▼
npm ci
│
▼
npm run build
│
▼
生成 api/dist
│
▼
同步到 /home/apps/api_test
│
▼
npm ci --omit=dev
│
▼
npm run start
│
▼
PM2 托管进程
│
▼
Nginx 反向代理
不建议直接将 Jenkins Workspace 作为长期运行目录,因为 Workspace 可能在构建或清理过程中被覆盖。
创建独立运行目录:
sudo mkdir -p /home/apps/api_test
sudo chown -R jenkins:jenkins /home/apps/api_test
sudo chmod -R u+rwX,go+rX /home/apps/api_test
9.1 api_test Execute shell
在 Build Steps → Execute shell 中配置:
#!/usr/bin/env bash
set -Eeuo pipefail
readonly APP_NAME="api_test"
readonly DEPLOY_DIR="/home/apps/api_test"
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=2048}"
log() {
printf '
[%s] %s
' "$(date '+%F %T')" "$*"
}
log "检查构建环境"
node -v
npm -v
pm2 -v
log "进入后端目录"
cd "${WORKSPACE}/api"
log "安装完整构建依赖"
npm ci
log "编译 TypeScript"
npm run build
if [[ ! -d "dist" ]]; then
echo "ERROR: dist 目录不存在,终止发布。" >&2
exit 1
fi
log "同步构建产物"
mkdir -p "${DEPLOY_DIR}/dist"
rsync -rv --delete \
dist/ \
"${DEPLOY_DIR}/dist/"
cp package.json "${DEPLOY_DIR}/"
cp package-lock.json "${DEPLOY_DIR}/"
log "安装生产依赖"
cd "${DEPLOY_DIR}"
npm ci --omit=dev
log "启动或重启 PM2 进程"
if pm2 describe "${APP_NAME}" >/dev/null 2>&1; then
pm2 restart "${APP_NAME}" --update-env
else
pm2 start npm \
--name "${APP_NAME}" \
-- run start
fi
pm2 save
pm2 list
log "api_test 发布完成"
这里将“构建目录”和“运行目录”分离:
Jenkins Workspace
↓ npm run build
api/dist
↓ rsync
/home/apps/api_test/dist
↓ npm run start
PM2
这样即使 Jenkins 后续清理 Workspace,也不会直接影响当前正在运行的 Node 服务。
该脚本执行流程如下:
npm ci
↓
npm run build
↓
dist
↓
同步到 /home/apps/api_test/dist
↓
npm ci --omit=dev
↓
pm2 start npm --name api_test -- run start
首次启动:
pm2 start npm --name api_test -- run start
后续部署检测到同名进程后执行:
pm2 restart api_test --update-env
这样项目真正的启动入口一直是:
npm run start
后续即使调整 start 对应的 Node 入口,Jenkins 中的 PM2 命令也无需修改。
9.2 PM2 开机恢复
同机部署场景下,如果 PM2 由 jenkins 用户管理,则 pm2 startup 也应基于同一 Linux 用户完成配置。
生产环境更建议采用后文的远程部署模式:Jenkins 负责构建与制品传输,业务服务器由独立 deploy 用户负责 PM2 运行管理。
10. Nginx 反向代理 api_test
假设 Koa 在本机:
127.0.0.1:3000
Nginx 可以这样写:
server {
listen 80;
server_name api-test.example.com;
location /api/ {
proxy_pass http://127.0.0.1:3000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Rocky Linux 如果 SELinux 阻止 Nginx 连 Node,可以:
sudo setsebool -P httpd_can_network_connect 1
然后:
sudo nginx -t
sudo systemctl reload nginx
测试:
http://api-test.example.com/api/health
不建议将 Node 服务端口 3000 直接暴露到公网。
11. Jenkins 项目级权限隔离
完成构建与部署后,可以进一步配置 Jenkins 的项目级权限。本文使用 Role-based Authorization Strategy 插件,将用户的 Jenkins 基础访问权限与具体 Job 操作权限拆开管理。
权限目标如下:
linhao 管理全部 Jenkins
api 只能访问 api_ 开头的 Job
web 只能访问 web_ 开头的 Job
启用方式:
Manage Jenkins
→ Security
→ Authorization
→ Role-Based Strategy
整体权限模型:
linhao
└── Global Role:admin
└── Overall → Administer
└── Jenkins 全局管理员
api 用户
├── Global Role:loginSys
│ └── Overall → Read
└── Item Role:api
└── Pattern:^api_.*$
└── 只对 api_ 开头的 Job 生效
web 用户
├── Global Role:loginSys
│ └── Overall → Read
└── Item Role:web
└── Pattern:^web_.*$
└── 只对 web_ 开头的 Job 生效
11.1 Global Role 与 Item Role 的区别
Role Strategy 中最容易混淆的是:Global Role 和 Item Role 中会出现很多同名权限,例如 Job → Read、Job → Build、Credentials → View。这些权限名称相同,但作用范围不同。
Global Role
└── 在整个 Jenkins 范围内授予权限
Item Role
└── 只在 Pattern 匹配到的 Item 范围内授予权限
例如:
Global Role:Job → Read
意味着用户拥有全局 Job 读取权限,通常可以读取 Jenkins 中所有 Job。
而:
Item Role:api
Pattern:^api_.*$
Job → Read
只允许读取:
api_test
api_prod
api_user
不会因此获得:
web_test
web_prod
需要特别注意:Jenkins 权限是叠加的,不是互相覆盖或互相否定。 如果用户已经通过 Global Role、用户组或 Authenticated Users 获得了全局 Job → Read,再给他配置 Item Role 并不能把其他 Job 隐藏掉。
因此,要实现项目隔离,普通用户的 Global Role 一般只保留:
Overall → Read
项目相关权限统一放到 Item Role 中。
当前权限名称以本文 Jenkins 2.568.x + Role Strategy 页面实际显示为准。Jenkins 插件可能继续增加新的权限列,例如 Metrics、Lockable Resources 等,新增权限应按照同样的最小权限原则处理。
11.2 Global Role 权限详解
Global Role 可以授予 Jenkins 全局范围权限。当前页面主要包含以下几组:
Overall
Credentials
Agent
Job
Run
View
SCM
11.2.1 Overall
Overall 是 Jenkins 系统级权限,也是 Global Role 中最核心的一组。
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Administer | Jenkins 最高管理权限,可以修改系统配置、安装插件、管理用户、访问 Script Console 等 | 极高权限,只给 Jenkins 管理员 |
Manage | 允许访问和修改一部分管理配置,但不等同于完整管理员,安全关键配置通常仍受限制 | 管理型账号按需开放,普通开发用户不需要 |
Read | Jenkins 基础读取权限,是普通用户进入 Jenkins、访问自己已授权资源的前提 | 普通登录用户需要 |
本文创建:
admin
→ Overall / Administer
loginSys
→ Overall / Read
其中 loginSys 只负责让普通账号具备 Jenkins 基础访问能力,不授予任何全局 Job 权限。
如果 api 或 web 已经配置 Item Role,但登录后仍然提示没有权限,首先检查是否分配了:
Global Role → loginSys → Overall / Read
11.2.2 Credentials
Global Role 中的 Credentials 权限作用于 Jenkins 全局凭据范围。Jenkins 中常见的 Git Token、SSH Private Key、服务器账号、API Token 等都属于敏感数据。
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Create | 创建新的 Jenkins Credentials | 普通开发用户一般不需要 |
Delete | 删除现有 Credentials | 高风险,不建议普通用户开放 |
ManageDomains | 创建、删除或修改 Credentials Domain | 管理员权限,普通用户不需要 |
Update | 修改已有 Credentials | 高风险,不建议普通用户开放 |
View | 查看凭据条目及其相关信息;具体是否展示明文取决于凭据类型和插件实现 | 敏感权限,普通用户一般不开放 |
需要注意:Job 能够使用管理员提前配置好的 Credentials,并不意味着操作 Job 的普通用户必须拥有 Credentials → View。
本文的 api / web 用户不授予任何 Global Credentials 权限。
11.2.3 Agent
Agent 是 Jenkins 的构建节点。单机 Jenkins 可能只使用内置节点;分布式构建时,则可能存在 Linux Agent、Windows Agent、Docker/Kubernetes Cloud Agent 等。
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Build | 允许用户在 Agent 上运行构建;在默认构建以 SYSTEM 身份执行的场景中作用可能不明显 | 使用 Build Authorization 时按需配置 |
Configure | 修改 Agent 配置 | 高风险,可影响运行在节点上的所有 Job |
Connect | 将 Agent 连接或标记为 Online | 节点管理员按需使用 |
Create | 创建新的 Agent | 高风险,普通开发用户不需要 |
Delete | 删除 Agent | 高风险,普通开发用户不需要 |
Disconnect | 断开 Agent 或将节点临时标记为 Offline | 节点管理员按需使用;通常隐含 Connect 能力 |
Provision | 允许 Jenkins 通过已配置的 Cloud 动态申请/创建 Agent,例如云主机、容器、Kubernetes Pod 等 | 云构建环境按需开放,普通用户不建议授予 |
当前示例没有使用分布式 Agent,因此 api / web 用户不需要 Agent 权限。
Role Strategy 还提供独立的 Agent Roles,可按 Agent 名称 Pattern 做节点级权限隔离。本文重点是 Job 权限,因此不展开 Agent Roles。
11.2.4 Job
Job 是日常使用频率最高的一组权限。这里的 Job 不仅包含 Freestyle Project,也包括 Pipeline、Folder 等 Jenkins Item。
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Build | 手动触发一次新的构建,即“立即构建” | 开发用户常用,可按项目授予 |
Cancel | 取消排队中的构建或中止正在运行的构建 | 开发用户通常可以授予 |
Configure | 修改 Job 配置,包括 Git、Credentials 引用、Execute shell、构建参数等 | 高风险,可能修改部署脚本,不建议普通用户开放 |
Create | 创建新的 Job / Pipeline / Folder | 项目管理员按需,普通用户一般不需要 |
Delete | 删除 Job | 高风险,不建议普通用户开放 |
Discover | 知道某个 Job 是否存在,但不一定能读取具体内容;Job → Read 会隐含该能力 | 普通场景很少单独授予 |
Move | 将 Job 从一个 Folder 或 Jenkins 根目录移动到另一个位置 | 项目管理员按需,普通用户不需要 |
Read | 查看 Job 页面、配置摘要、构建历史及相关信息 | 项目用户最基本的 Item 权限 |
Workspace | 浏览 Jenkins Workspace 中检出的源码、构建中间文件及产物 | 按需授予;如果源码或构建文件敏感,可以取消 |
对于项目隔离,Global Role 中一般不要给普通用户任何 Job 权限,否则权限会扩展到整个 Jenkins。
11.2.5 Run
Run 表示某一次具体构建记录,例如:
web_test
├── #18
├── #19
└── #20
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Delete | 删除某一次具体的构建历史记录 | 不建议普通开发用户开放 |
Replay | 对 Pipeline 进行 Replay,可修改 Pipeline 脚本后重新执行一次构建 | 高风险;Freestyle 基本用不到,Pipeline 场景谨慎开放 |
Update | 修改构建记录的描述或其他可更新属性,例如给失败构建添加说明 | 普通用户一般不需要 |
其中 Run → Replay 主要由 Pipeline 相关插件提供,Job → Configure 通常会隐含 Replay 能力。
11.2.6 View
Jenkins View 用于对 Job 做逻辑分组,例如:
All
Frontend
Backend
Test
Production
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Configure | 修改已有 View 的配置和筛选规则 | 项目管理员按需 |
Create | 创建新的 View | 普通用户一般不需要 |
Delete | 删除 View | 普通用户一般不需要 |
Read | 查看 View | 普通项目用户可以授予 |
如果普通用户只需要查看自己有权限的项目,通常保留 View → Read 即可。
11.2.7 SCM
SCM 表示 Source Code Management。
| 权限 | 作用 | 风险/建议 |
|---|---|---|
Tag | 允许 Jenkins 通过 SCM 集成对源码仓库执行 Tag 相关操作 | 普通自动构建通常不需要 |
本文构建流程只是:
Git 拉代码
→ Build
→ Deploy
不需要 Jenkins 自动创建源码 Tag,因此不授予 SCM → Tag。
11.3 Item Role 权限详解
Item Role 只对 Pattern 匹配到的 Jenkins Item 生效。它主要包含:
Credentials
Job
Run
View
SCM
与 Global Role 相比,Item Role 没有 Overall 和 Agent:
Overall → Jenkins 全局权限,只能放 Global Role
Agent → 构建节点权限,应使用 Global/Agent Role 管理
Item → Job / Folder / Pipeline 等项目范围权限
本文定义:
Role:api
Pattern:^api_.*$
Role:web
Pattern:^web_.*$
11.3.1 Item Credentials
Item Role 中的 Credentials 权限名称与 Global Role 相同,但作用范围变成匹配到的 Item/Folder 凭据上下文(前提是该 Item 或 Folder 存在可管理的凭据存储)。
| 权限 | 在 Item Role 中的作用 | 建议 |
|---|---|---|
Create | 在匹配 Item/Folder 的凭据范围内创建 Credentials | 普通项目用户一般不需要 |
Delete | 删除匹配范围内的 Credentials | 不建议普通用户开放 |
ManageDomains | 管理匹配范围内的 Credential Domain | 项目管理员按需 |
Update | 修改匹配范围内已有 Credentials | 高风险,普通用户不建议开放 |
View | 查看匹配范围内可见的 Credentials 信息 | 敏感权限,普通用户一般不需要 |
本文 api / web Role 不开放 Credentials 权限。Git 拉取所需凭据由 Jenkins 管理员预先配置,Job 在执行过程中直接引用即可。
11.3.2 Item Job
这是 Item Role 最关键的一组权限。语义与 Global Job 权限相同,但只对 Pattern 匹配到的 Job 生效。
例如:
Role:api
Pattern:^api_.*$
此时:
| 权限 | 在 Item Role 中的作用 | 本文建议 |
|---|---|---|
Build | 只允许触发匹配到的 Job 构建 | ✅ 开放 |
Cancel | 只允许取消匹配 Job 的构建 | ✅ 开放 |
Configure | 修改匹配 Job 的 Git、构建脚本、凭据引用、构建参数等 | ❌ 普通用户不开放 |
Create | 在允许的父级/命名范围内创建 Item;实际是否能创建还受 Folder、Pattern 等约束 | ❌ 普通用户不开放 |
Delete | 删除匹配到的 Job | ❌ 不开放 |
Discover | 能发现匹配 Item 是否存在,但不一定能读取详情 | 通常不单独授予 |
Move | 移动匹配到的 Job/Folder | ❌ 不开放 |
Read | 查看匹配到的 Job | ✅ 必须开放 |
Workspace | 浏览匹配 Job 的 Workspace | ⚠️ 按需开放 |
因此本文普通开发账号配置:
Job → Read √
Job → Build √
Job → Cancel √
Job → Workspace √(如果不希望查看源码,可取消)
不开放:
Job → Configure
Job → Create
Job → Delete
Job → Move
这意味着开发人员可以查看和构建自己负责的项目,但不能修改 Jenkins 发布脚本,也不能删除 Job。
11.3.3 Item Run
Item Run 权限只作用于 Pattern 匹配 Job 的构建记录。
| 权限 | 在 Item Role 中的作用 | 建议 |
|---|---|---|
Delete | 删除匹配 Job 的某次构建记录 | 普通用户不建议开放 |
Replay | Replay 匹配到的 Pipeline 构建,并允许修改脚本后重新执行 | 高风险,普通用户不开放 |
Update | 修改匹配 Job 构建记录的描述等属性 | 按需开放,通常不需要 |
本文使用 Freestyle Project,因此 Run → Replay 没有实际使用场景,这组权限全部保持关闭。
11.3.4 Item View
Item Role 中的 View 权限用于匹配范围相关的视图访问和管理。Folder 场景下尤其常见;根级 View 的实际可见性还会受到 Global 权限和 Jenkins 版本/插件实现影响。
| 权限 | 在 Item Role 中的作用 | 本文建议 |
|---|---|---|
Configure | 修改匹配作用域内 View 配置 | ❌ 不开放 |
Create | 创建 View | ❌ 不开放 |
Delete | 删除 View | ❌ 不开放 |
Read | 查看相关 View | ✅ 开放 |
本文只授予:
View → Read
11.3.5 Item SCM
| 权限 | 在 Item Role 中的作用 | 建议 |
|---|---|---|
Tag | 允许对匹配 Job 关联的 SCM 执行 Tag 操作 | 普通构建发布不需要 |
因此保持关闭。
11.4 Pattern 的匹配规则
本文按 Job 前缀划分权限:
Role:api
Pattern:^api_.*$
Role:web
Pattern:^web_.*$
^api_.*$ 的含义:
^ 从字符串开头开始匹配
api_ 必须以 api_ 开头
.* 后面允许任意字符
$ 一直匹配到字符串结尾
会匹配:
api_test
api_prod
api_user_center
不会匹配:
api
web_test
test_api
my_api_test
web 角色使用:
^web_.*$
如果以后使用 Folder,Item 的完整名称通常会包含 Folder 路径。例如:
team-a/api_test
team-a/web_test
此时 Pattern 也应按照完整 Item 路径设计,而不是继续只匹配根目录名称。
11.5 本文最终权限配置
Global roles
admin
└── Overall → Administer
loginSys
└── Overall → Read
普通用户的 Global Role 中,不配置:
Job
Credentials
Agent
Run
SCM
避免全局权限破坏 Item Role 的项目隔离效果。
Item roles:api
Pattern:^api_.*$
Job
├── Read √
├── Build √
├── Cancel √
└── Workspace √(按需)
View
└── Read √
Item roles:web
Pattern:^web_.*$
Job
├── Read √
├── Build √
├── Cancel √
└── Workspace √(按需)
View
└── Read √
其他权限保持关闭。
11.6 创建 Jenkins 普通用户
进入:
Manage Jenkins
→ Users
→ Create User
新增:
api
web
管理员 linhao 已在 Jenkins 初始化阶段创建,此处不重复创建。
11.7 Assign Roles
进入:
Manage Jenkins
→ Role Management
→ Assign Roles
Global roles:
linhao → admin
api → loginSys
web → loginSys
Item roles:
api → api
web → web
角色分配关系:
| 用户 | Global Role | Item Role | 可见项目 |
|---|---|---|---|
linhao | admin | — | 全部 Job |
api | loginSys | api | api_ 开头的 Job |
web | loginSys | web | web_ 开头的 Job |
最终效果:
linhao 登录
→ 可以管理 web_test、api_test 以及其他全部 Job
api 登录
→ 只看到 api_test / api_xxx
→ 可以查看、构建、取消构建
→ 无法修改或删除 Job
web 登录
→ 只看到 web_test / web_xxx
→ 可以查看、构建、取消构建
→ 无法修改或删除 Job
如果普通账号登录后直接提示没有权限,优先检查:
Global → loginSys
因为:
Item Role
≠ Jenkins 基础登录权限
普通用户仍然需要:
Overall → Read
另外不要给 Authenticated Users 或普通用户组授予全局 Job → Read、Job → Build,否则这些权限会与 Item Role 叠加,导致用户看到或操作超出 Pattern 范围的 Job。
12. 同机部署流程汇总
目前前端:
Gitee
↓
Jenkins web_test
↓
pnpm install
↓
npm run build
↓
web/dist
↓
rsync
↓
/home/html/web_test
↓
Nginx
后端:
Gitee
↓
Jenkins api_test
↓
npm ci
↓
npm run build
↓
api/dist
↓
/home/apps/api_test
↓
npm run start
↓
PM2
↓
Nginx
权限也已经能做到:
api → api_.*
web → web_.*
该方案适用于 Jenkins 与 Nginx/Node 服务部署在同一台开发或测试服务器的场景。
在生产环境中,Jenkins 通常与业务服务器分离。此时不再使用本地 rsync /home/... 目标路径,而改用 Publish Over SSH。
13. 远程发布方案:Publish Over SSH
假设现在变成:
Jenkins:192.168.31.88
业务服务器:192.168.31.90
推荐将 Jenkins 的职责限定为:
1. 构建
2. 把构建产物传过去
业务服务器上的文件切换、PM2 重启等部署动作由远程脚本负责。
Git
│
▼
Jenkins
│
├─ 安装依赖
├─ 执行 build
▼
生成构建产物
│
▼
Publish Over SSH
│
▼
远程服务器 /tmp/jenkins/...
│
▼
执行 deploy 脚本
│
├─ 前端:替换 Nginx 静态目录
│
└─ 后端:替换应用目录 + npm ci --omit=dev + PM2 restart
流程变成:
Git
↓
Jenkins build
↓
Publish Over SSH
↓
192.168.31.90:/tmp/jenkins/xxx
↓
远程 deploy 脚本
↓
正式目录
备份、健康检查、回滚等逻辑可以集中维护在业务服务器部署脚本中,避免 Jenkins Execute shell 过度膨胀。
14. 目标服务器 deploy 用户与目录
业务服务器 192.168.31.90 创建一个专门发布账号:
sudo useradd -m deploy
准备目录:
sudo mkdir -p /tmp/jenkins
sudo mkdir -p /home/html/web_test
sudo mkdir -p /home/apps/api_test
sudo mkdir -p /home/deploy/scripts
sudo chown -R deploy:deploy /tmp/jenkins
sudo chown -R deploy:deploy /home/html/web_test
sudo chown -R deploy:deploy /home/apps/api_test
sudo chown -R deploy:deploy /home/deploy/scripts
远程服务器如果要运行后端,还要保证 deploy 这个用户能用:
node -v
npm -v
pm2 -v
如未安装 PM2:
npm install -g pm2
SSH 认证建议使用 Key,并避免使用 root 账号直接登录业务服务器。
15. 配置 Publish Over SSH
插件装好后进入:
Manage Jenkins
→ System
→ Publish over SSH
添加服务器:
Name:prod-server
Hostname:192.168.31.90
Username:deploy
Remote Directory:/tmp/jenkins
认证配置 SSH Private Key。
点:
Test Configuration
测试成功后保存配置。
Publish Over SSH 支持在文件传输完成后执行远程命令,因此可以将实际部署逻辑交由目标服务器脚本处理。
16. 远程发布前端 web_test
远程模式下,Jenkins Execute shell 只负责生成构建产物,不直接操作业务服务器目录:
#!/usr/bin/env bash
set -Eeuo pipefail
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=2048}"
cd "${WORKSPACE}/web"
pnpm install \
--frozen-lockfile \
--dangerously-allow-all-builds
npm run build
[[ -d "dist" ]] || {
echo "ERROR: dist 目录不存在。" >&2
exit 1
}
echo "web_test build success"
然后在:
Post-build Actions
→ Send build artifacts over SSH
选择:
prod-server
Transfer Set:
Source files:web/dist/**
Remove prefix:web/dist
Remote directory:web_test
由于全局 Remote Directory 是:
/tmp/jenkins
最终文件会到:
/tmp/jenkins/web_test
目标服务器写:
vim /home/deploy/scripts/deploy-web_test.sh
#!/usr/bin/env bash
set -Eeuo pipefail
readonly SOURCE_DIR="/tmp/jenkins/web_test"
readonly TARGET_DIR="/home/html/web_test"
printf '
[%s] deploy web_test
' "$(date '+%F %T')"
mkdir -p "${TARGET_DIR}"
find "${TARGET_DIR}" \
-mindepth 1 \
-maxdepth 1 \
-exec rm -rf {} +
cp -a "${SOURCE_DIR}/." "${TARGET_DIR}/"
echo "web_test deploy success"
授权:
chmod +x /home/deploy/scripts/deploy-web_test.sh
Publish Over SSH 的 Exec command 填:
bash /home/deploy/scripts/deploy-web_test.sh
前端远程发布流程如下:
Jenkins build
→ SSH 上传 dist
→ 执行 deploy-web_test.sh
→ 更新 Nginx 目录
17. 远程发布后端 api_test
后端远程发布还需要在目标服务器安装生产依赖并重启 PM2。
Jenkins 先完成依赖安装、编译并生成发布包:
#!/usr/bin/env bash
set -Eeuo pipefail
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=2048}"
cd "${WORKSPACE}/api"
npm ci
npm run build
[[ -d "dist" ]] || {
echo "ERROR: dist 目录不存在。" >&2
exit 1
}
rm -f api_test.tar.gz
tar -czf api_test.tar.gz \
dist \
package.json \
package-lock.json
echo "api_test build success"
Publish Over SSH:
Source files:api/api_test.tar.gz
Remove prefix:api
Remote directory:api_test
最终:
/tmp/jenkins/api_test/api_test.tar.gz
远程服务器脚本:
vim /home/deploy/scripts/deploy-api_test.sh
#!/usr/bin/env bash
set -Eeuo pipefail
readonly APP_NAME="api_test"
readonly PACKAGE="/tmp/jenkins/api_test/api_test.tar.gz"
readonly APP_DIR="/home/apps/api_test"
readonly RELEASE_DIR="/tmp/api_test_release"
printf '
[%s] deploy api_test
' "$(date '+%F %T')"
rm -rf "${RELEASE_DIR}"
mkdir -p "${RELEASE_DIR}" "${APP_DIR}"
tar -xzf "${PACKAGE}" -C "${RELEASE_DIR}"
rm -rf "${APP_DIR}/dist"
cp -a "${RELEASE_DIR}/dist" "${APP_DIR}/dist"
cp "${RELEASE_DIR}/package.json" "${APP_DIR}/"
cp "${RELEASE_DIR}/package-lock.json" "${APP_DIR}/"
cd "${APP_DIR}"
npm ci --omit=dev
if pm2 describe "${APP_NAME}" >/dev/null 2>&1; then
pm2 restart "${APP_NAME}" --update-env
else
pm2 start npm \
--name "${APP_NAME}" \
-- run start
fi
pm2 save
pm2 list
echo "api_test deploy success"
授权:
chmod +x /home/deploy/scripts/deploy-api_test.sh
Publish Over SSH 的 Exec command:
bash /home/deploy/scripts/deploy-api_test.sh
该方式下 Jenkins 不直接管理远程服务器上的 PM2:
Jenkins:build + 上传
业务服务器:安装生产依赖 + npm run start + PM2 restart
这种职责划分能够将 CI 构建与业务运行管理解耦。
18. rsync 与 Publish Over SSH 选型
两种方案的边界可以概括为:
Jenkins 构建完成
│
┌─────────────┴─────────────┐
│ │
▼ ▼
同机部署 远程部署
│ │
▼ ▼
rsync --delete Publish Over SSH
│ │
▼ ▼
本机 Nginx / Node 目录 远程临时目录 + deploy 脚本
│ │
▼ ▼
Nginx / PM2 Nginx / PM2
两种部署方式可按以下场景选择:
| 场景 | 推荐方案 |
|---|---|
| Jenkins 和 Nginx 同机 | rsync |
| Jenkins 和 Node 后端同机 | rsync + PM2 |
| Jenkins 和业务服务器分开 | Publish Over SSH |
| 测试/预发/生产多台服务器 | Publish Over SSH |
| 希望部署逻辑留在业务服务器 | Publish Over SSH + deploy 脚本 |
rsync 适合同机发布,配置简单,并可通过 --delete 清理旧资源。
Publish Over SSH 更适合 Jenkins 与业务服务器分离、多环境部署以及需要远程执行部署脚本的场景。
19. 完整流程汇总
前端同机:
Gitee
↓
Jenkins web_test
↓
Node 24 / pnpm
↓
npm run build
↓
web/dist
↓
rsync --delete
↓
/home/html/web_test
↓
Nginx
后端同机:
Gitee
↓
Jenkins api_test
↓
npm ci
↓
npm run build
↓
dist
↓
/home/apps/api_test
↓
npm ci --omit=dev
↓
npm run start
↓
PM2
↓
Nginx
Jenkins 权限:
linhao
└── Global:admin
api
├── Global:loginSys
└── Item:api
└── ^api_.*$
web
├── Global:loginSys
└── Item:web
└── ^web_.*$
远程发布:
Git
↓
Jenkins build
↓
Publish Over SSH
↓
192.168.31.90:/tmp/jenkins
↓
deploy-xxx.sh
↓
前端:Nginx 静态目录
后端:应用目录 + PM2
至此,基础 Jenkins CI/CD 已覆盖代码拉取、构建、部署、运行与权限隔离。
Webhook、Pipeline/Jenkinsfile、Docker、蓝绿发布等能力可在此基础上继续扩展。首次搭建时,建议优先保证以下主链路稳定:
拉代码 → 构建 → 发布 → 运行 → 权限隔离
在基础链路稳定后,可以接入 Gitee Webhook 实现自动触发,或进一步将 Freestyle Project 迁移为 Jenkinsfile/Pipeline。
20. 验收检查
建议按以下顺序进行验收:
Jenkins
[ ] systemctl status jenkins 正常
[ ] NodeJS Plugin 的 node24 能用
[ ] Git Credentials 可以拉私有仓库
web_test
[ ] pnpm install 正常
[ ] npm run build 正常
[ ] dist 存在
[ ] rsync 发布成功
[ ] 页面可以通过 Nginx 打开
api_test
[ ] npm ci 正常
[ ] npm run build 生成 dist
[ ] npm run start 能运行 dist
[ ] pm2 list 显示 api_test online
[ ] /health 可以访问
权限
[ ] linhao 能看到全部 Job
[ ] api 只能看到 api_ 开头 Job
[ ] web 只能看到 web_ 开头 Job
[ ] api/web 都有 loginSys
远程发布
[ ] Publish Over SSH Test Configuration 成功
[ ] deploy 使用 SSH Key
[ ] 上传以后远程 deploy 脚本可以执行