Jenkins CI/CD 实战:Vite 前端发布、Koa + PM2 后端部署、权限隔离与远程发布

218 阅读22分钟

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 webcd 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 全局管理员。后续配置项目级权限时,仅新增 apiweb 两个普通账号。

初始化完成后进入 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-lockfileCI 中严格按照 lock 文件安装依赖
--delete删除目标目录中已经不属于新版本的旧静态资源
${WORKSPACE}Jenkins 当前 Job 的工作空间目录

发布阶段采用:

rsync -rv --delete

相比简单的 cprsync 更适合前端静态资源发布,因为构建文件通常包含 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 → ReadJob → BuildCredentials → View。这些权限名称相同,但作用范围不同。

Global Role
└── 在整个 Jenkins 范围内授予权限

Item Role
└── 只在 Pattern 匹配到的 Item 范围内授予权限

例如:

Global Role:Job → Read

意味着用户拥有全局 Job 读取权限,通常可以读取 Jenkins 中所有 Job。

而:

Item Role:api
Pattern:^api_.*$
JobRead

只允许读取:

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 中最核心的一组。

权限作用风险/建议
AdministerJenkins 最高管理权限,可以修改系统配置、安装插件、管理用户、访问 Script Console 等极高权限,只给 Jenkins 管理员
Manage允许访问和修改一部分管理配置,但不等同于完整管理员,安全关键配置通常仍受限制管理型账号按需开放,普通开发用户不需要
ReadJenkins 基础读取权限,是普通用户进入 Jenkins、访问自己已授权资源的前提普通登录用户需要

本文创建:

admin
→ Overall / Administer

loginSys
→ Overall / Read

其中 loginSys 只负责让普通账号具备 Jenkins 基础访问能力,不授予任何全局 Job 权限。

如果 apiweb 已经配置 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 没有 OverallAgent

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 的某次构建记录普通用户不建议开放
ReplayReplay 匹配到的 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 RoleItem Role可见项目
linhaoadmin全部 Job
apiloginSysapiapi_ 开头的 Job
webloginSyswebweb_ 开头的 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 → ReadJob → 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

流程变成:

GitJenkins build
 ↓
Publish Over SSH192.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_.*$

远程发布:

GitJenkins build
 ↓
Publish Over SSH192.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 脚本可以执行