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

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 与业务服务器分离时仍能保持清晰的构建与发布边界。

示例环境如下:

text 复制代码
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

示例仓库采用前后端同仓结构:

text 复制代码
side-blog/
├── web/
│   ├── src/
│   ├── package.json
│   ├── pnpm-lock.yaml
│   └── vite.config.ts
│
└── api/
    ├── src/
    ├── package.json
    ├── package-lock.json
    └── tsconfig.json

如果前端与后端分别位于独立仓库,只需去掉后续构建脚本中的 cd webcd api

整体流程如下:

text 复制代码
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:

bash 复制代码
sudo dnf install -y fontconfig java-21-openjdk
java -version

添加 Jenkins LTS 仓库:

bash 复制代码
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:

bash 复制代码
sudo dnf install -y jenkins
sudo systemctl daemon-reload
sudo systemctl enable --now jenkins

确认是否已经开机启动:

bash 复制代码
systemctl is-enabled jenkins
systemctl status jenkins

看到:

text 复制代码
enabled
Active: active (running)

返回 enabled 且服务状态为 active (running),即表示 Jenkins 已正常启动并设置为开机自启。

内网或测试环境可开放 Jenkins 默认端口 8080:

bash 复制代码
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

浏览器打开:

text 复制代码
http://192.168.31.88:8080

首次访问 Jenkins 时需要读取初始化密码:

bash 复制代码
sudo cat /var/lib/jenkins/secrets/initialAdminPassword

初始化阶段可选择 Install suggested plugins 安装 Jenkins 推荐插件。

初始化向导中创建管理员账号:

text 复制代码
linhao

该账号作为 Jenkins 全局管理员。后续配置项目级权限时,仅新增 apiweb 两个普通账号。

初始化完成后进入 Jenkins 首页:

初始化完成后,Jenkins 首页应能够看到以下核心入口:

text 复制代码
新建 Item
构建历史
Manage Jenkins
构建队列
构建执行状态

后续所有 Job、插件、工具链和权限配置都从这些入口完成。


2. 安装 Jenkins 所需插件

进入:

text 复制代码
Manage Jenkins
→ Plugins

Manage Jenkins 中,本篇主要会用到以下配置入口:

text 复制代码
Plugins        → 安装 NodeJS Plugin、Role-based Authorization Strategy、Publish Over SSH
Tools          → 配置 Node.js 24
System         → 配置 Publish Over SSH 远程服务器
Security       → 启用 Role-Based Strategy
Users          → 创建 Jenkins 普通用户

本文涉及以下三个插件:

text 复制代码
NodeJS Plugin
Role-based Authorization Strategy
Publish Over SSH

Publish Over SSH 仅用于远程发布;同机部署阶段无需配置。

NodeJS Plugin

搜索 NodeJS,安装 NodeJS Plugin

插件安装完成后,在 Installed plugins 中应能看到:

text 复制代码
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

进入:

text 复制代码
Manage Jenkins
→ Tools
→ NodeJS installations

新增一套:

text 复制代码
Name:node24
Install automatically:勾选
NodeJS Version:24.x
Global npm packages to install:pnpm pm2

前端构建使用 pnpm,后端进程管理使用 PM2,因此可在该工具配置中统一安装。

如果希望 CI 环境更稳定,也可以固定主版本:

text 复制代码
pnpm@10 pm2@6

NodeJS 工具建议配置为:

text 复制代码
Name:node24
Install automatically:√
NodeJS Version:24.x
Global npm packages to install:pnpm pm2

如果希望 CI 环境更稳定,可以固定主版本:

text 复制代码
pnpm@10 pm2@6

保存以后,每个 Job 在"构建环境"里勾上:

text 复制代码
Provide Node & npm bin/ folder to PATH

然后选择:

text 复制代码
node24

完成配置后,Execute shell 中可直接使用:

bash 复制代码
node -v
npm -v
pnpm -v
pm2 -v

4. 配置 Git 私有仓库凭据

新建 Job 后,在:

text 复制代码
源码管理
→ Git

仓库填:

text 复制代码
https://gitee.com/linhao-dev/side-blog.git

如果是私有仓库,Credentials 不能留空。

HTTPS 仓库我一般添加:

text 复制代码
Kind:Username with password
Username:linhao-dev
Password:Gitee 私人令牌

Password 建议填写 Personal Access Token,不建议直接保存 Git 平台登录密码。

分支根据项目实际情况填:

text 复制代码
*/main

如果仓库还是 master

text 复制代码
*/master

配置时如果出现:

text 复制代码
Incorrect username or password (access token)

该错误通常由 Credentials 配置错误导致,应优先检查用户名、Token 与仓库访问权限。


5. Jenkins Job 命名规范

为便于后续通过 Role Pattern 进行权限隔离,Job 统一采用前缀命名,不再沿用临时测试名称 vite-web

text 复制代码
web_xxx  前端
api_xxx  后端

示例创建两个 Freestyle Project:

text 复制代码
web_test
api_test

对应的权限正则可统一定义为:

text 复制代码
^web_.*$
^api_.*$

这样新增同类 Job 时,无需为每个项目单独维护一套角色。


6. 前端 web_test:Vite 构建与同机发布

创建一个 Freestyle Project:

text 复制代码
新建 Item
→ web_test
→ Freestyle project

Git、Credentials 与 Node 24 按前述方式配置。

同机部署场景如下:

text 复制代码
Jenkins 和 Nginx 在同一台服务器

最终就是:

text 复制代码
Git
 ↓
Jenkins
 ↓
cd web
 ↓
pnpm install
 ↓
npm run build
 ↓
web/dist
 ↓
rsync
 ↓
/home/html/web_test
 ↓
Nginx
text 复制代码
Jenkins 拉取代码
      │
      ▼
进入 web 目录
      │
      ▼
pnpm install
      │
      ▼
npm run build
      │
      ▼
生成 web/dist
      │
      ▼
rsync --delete
      │
      ▼
/home/html/web_test
      │
      ▼
Nginx

6.1 配置发布目录权限

服务器执行一次:

bash 复制代码
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 用户执行一次写入测试:

bash 复制代码
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 中配置以下脚本:

bash 复制代码
#!/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 的工作空间目录

发布阶段采用:

bash 复制代码
rsync -rv --delete

相比简单的 cprsync 更适合前端静态资源发布,因为构建文件通常包含 hash:

text 复制代码
index-a83d.js
index-27fd.js

新版本已经没有的旧文件,--delete 会一起清掉。

如果使用:

bash 复制代码
rsync -av --delete

在部分权限配置下可能出现:

text 复制代码
chgrp failed: Operation not permitted

静态资源发布通常无需保留源文件的 owner/group,因此使用 -rv 可以避免不必要的权限继承。

6.3 配置 Nginx 静态目录

Nginx root 指向发布目录:

nginx 复制代码
server {
    listen 80;
    server_name web-test.example.com;

    root /home/html/web_test;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

检查:

bash 复制代码
sudo nginx -t
sudo systemctl reload nginx

后续 Jenkins 仅替换静态文件,无需在每次构建后 reload Nginx。

如果使用 Vue Router history 模式,需要保留以下配置:

nginx 复制代码
try_files $uri $uri/ /index.html;

7. 前端构建与发布常见问题

以下问题均发生在构建或发布阶段,容易被误认为 Jenkins 本身异常。

7.1 pnpm 10:Ignored build scripts

安装依赖时如果看到:

text 复制代码
ERR_PNPM_IGNORED_BUILDS
Ignored build scripts: esbuild ...

典型日志类似:

text 复制代码
[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 的依赖;对于完全可信的内部项目,也可以使用:

bash 复制代码
pnpm install --dangerously-allow-all-builds

因此示例构建脚本采用该参数。

7.2 vue-tsc 构建 OOM

类型检查阶段如果出现:

text 复制代码
FATAL ERROR: Allocation failed
JavaScript heap out of memory

典型日志:

text 复制代码
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 开头加:

bash 复制代码
export NODE_OPTIONS="--max-old-space-size=2048"

项目更大可以给 4096:

bash 复制代码
export NODE_OPTIONS="--max-old-space-size=4096"

同时需要保证服务器具有足够的可用内存。

7.3 构建成功但 rsync Permission denied

如果前面已经 build 完成,最后报:

text 复制代码
Permission denied
mkdir failed
mkstemp failed

典型日志:

text 复制代码
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 用户缺少目标目录写权限。

重新确认:

bash 复制代码
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:

text 复制代码
api_test

下面使用一个最小 Koa + TypeScript 项目说明完整构建与运行流程。

目录:

text 复制代码
api/
├── src/
│   └── index.ts
├── package.json
├── package-lock.json
└── tsconfig.json

8.1 package.json

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"
  }
}

项目统一通过以下脚本启动:

bash 复制代码
npm run start

PM2 同样托管 npm run start,避免在 Jenkins 中写死具体入口文件。

8.2 tsconfig.json

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

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 构建前可先在本地验证:

bash 复制代码
cd api
npm install
npm run build
npm run start

构建完成后应生成:

text 复制代码
api/dist/index.js

访问:

text 复制代码
http://127.0.0.1:3000/health

返回:

json 复制代码
{
  "code": 0,
  "message": "ok",
  "service": "api_test"
}

9. 配置后端 Jenkins Job api_test

新建:

text 复制代码
api_test
→ Freestyle project

Git、Credentials 与 Node 24 配置与 web_test 保持一致。

api_test 的构建、发布与运行关系如下:

text 复制代码
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 可能在构建或清理过程中被覆盖。

创建独立运行目录:

bash 复制代码
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 中配置:

bash 复制代码
#!/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 发布完成"

这里将"构建目录"和"运行目录"分离:

text 复制代码
Jenkins Workspace
    ↓ npm run build
api/dist
    ↓ rsync
/home/apps/api_test/dist
    ↓ npm run start
PM2

这样即使 Jenkins 后续清理 Workspace,也不会直接影响当前正在运行的 Node 服务。

该脚本执行流程如下:

text 复制代码
npm ci
 ↓
npm run build
 ↓
dist
 ↓
同步到 /home/apps/api_test/dist
 ↓
npm ci --omit=dev
 ↓
pm2 start npm --name api_test -- run start

首次启动:

bash 复制代码
pm2 start npm --name api_test -- run start

后续部署检测到同名进程后执行:

bash 复制代码
pm2 restart api_test --update-env

这样项目真正的启动入口一直是:

bash 复制代码
npm run start

后续即使调整 start 对应的 Node 入口,Jenkins 中的 PM2 命令也无需修改。

9.2 PM2 开机恢复

同机部署场景下,如果 PM2 由 jenkins 用户管理,则 pm2 startup 也应基于同一 Linux 用户完成配置。

生产环境更建议采用后文的远程部署模式:Jenkins 负责构建与制品传输,业务服务器由独立 deploy 用户负责 PM2 运行管理。


10. Nginx 反向代理 api_test

假设 Koa 在本机:

text 复制代码
127.0.0.1:3000

Nginx 可以这样写:

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,可以:

bash 复制代码
sudo setsebool -P httpd_can_network_connect 1

然后:

bash 复制代码
sudo nginx -t
sudo systemctl reload nginx

测试:

text 复制代码
http://api-test.example.com/api/health

不建议将 Node 服务端口 3000 直接暴露到公网。


11. Jenkins 项目级权限隔离

完成构建与部署后,可以进一步配置 Jenkins 项目级权限。

权限目标如下:

text 复制代码
linhao  管理全部 Jenkins
api     只能看到 api_ 开头的 Job
web     只能看到 web_ 开头的 Job

发布阶段采用:

text 复制代码
Role-based Authorization Strategy

插件安装以后,到:

text 复制代码
Manage Jenkins
→ Security
→ Authorization

选择:

text 复制代码
Role-Based Strategy

权限模型如下:

text 复制代码
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

Role-Based Strategy 中需要区分两类权限:

text 复制代码
Global roles:整个 Jenkins 范围的权限
Item roles:只对 Pattern 匹配到的 Job 生效

进行项目隔离时,普通用户不应在 Global Role 中获得 Job ReadJob Build 等全局 Job 权限。

11.1 Global roles

Global roles 定义两个角色:

text 复制代码
admin
→ Overall / Administer

loginSys
→ Overall / Read

loginSys 仅授予 Overall → Read,用于提供 Jenkins 基础访问权限;具体 Job 权限由 Item roles 控制。

初始化管理员 linhao 分配:

text 复制代码
admin

11.2 Item roles

Item roles 定义两个角色:

text 复制代码
Role:api
Pattern:^api_.*$

和:

text 复制代码
Role:web
Pattern:^web_.*$

普通开发账号建议授予:

text 复制代码
Job → Read
Job → Build
Job → Cancel
Job → Workspace
View → Read

不授予 ConfigureDelete 与 Credentials 相关权限,以遵循最小权限原则。

Item Role 建议按前缀设计:

text 复制代码
Role:api
Pattern:^api_.*$

Role:web
Pattern:^web_.*$

权限建议仅授予:

text 复制代码
Job → Read
Job → Build
Job → Cancel
Job → Workspace
View → Read

不授予 ConfigureDelete 和 Credentials 相关权限。

Pattern ^api_.*$ 的匹配结果如下:

text 复制代码
^api_.*$

会匹配:

text 复制代码
api_test
api_prod
api_user

不会匹配:

text 复制代码
web_test
test_api
api

web 角色使用 ^web_.*$,规则一致。

11.3 创建普通 Jenkins 用户

进入:

text 复制代码
Manage Jenkins
→ Users

新增:

text 复制代码
api
web

管理员 linhao 已在初始化阶段创建,此处仅新增普通账号。

11.4 Assign Roles

Global roles:

text 复制代码
linhao → admin
api    → loginSys
web    → loginSys

Item roles:

text 复制代码
api → api
web → web

角色分配关系如下:

用户 Global Role Item Role 可见项目
linhao admin --- 全部 Job
api loginSys api api_ 开头的 Job
web loginSys web web_ 开头的 Job

最后效果:

text 复制代码
linhao 登录
→ 能看到 web_test、api_test 和其他全部 Job

api 登录
→ 只看到 api_test / api_xxx

web 登录
→ 只看到 web_test / web_xxx

如果普通账号登录后提示"没有权限",首先检查是否已经分配:

text 复制代码
Global → loginSys

该角色对应 Overall → Read

Item Role 不能替代 Jenkins 的基础访问权限。


12. 同机部署流程汇总

目前前端:

text 复制代码
Gitee
 ↓
Jenkins web_test
 ↓
pnpm install
 ↓
npm run build
 ↓
web/dist
 ↓
rsync
 ↓
/home/html/web_test
 ↓
Nginx

后端:

text 复制代码
Gitee
 ↓
Jenkins api_test
 ↓
npm ci
 ↓
npm run build
 ↓
api/dist
 ↓
/home/apps/api_test
 ↓
npm run start
 ↓
PM2
 ↓
Nginx

权限也已经能做到:

text 复制代码
api → api_.*
web → web_.*

该方案适用于 Jenkins 与 Nginx/Node 服务部署在同一台开发或测试服务器的场景。

在生产环境中,Jenkins 通常与业务服务器分离。此时不再使用本地 rsync /home/... 目标路径,而改用 Publish Over SSH。


13. 远程发布方案:Publish Over SSH

假设现在变成:

text 复制代码
Jenkins:192.168.31.88
业务服务器:192.168.31.90

推荐将 Jenkins 的职责限定为:

text 复制代码
1. 构建
2. 把构建产物传过去

业务服务器上的文件切换、PM2 重启等部署动作由远程脚本负责。

text 复制代码
Git
 │
 ▼
Jenkins
 │
 ├─ 安装依赖
 ├─ 执行 build
 ▼
生成构建产物
 │
 ▼
Publish Over SSH
 │
 ▼
远程服务器 /tmp/jenkins/...
 │
 ▼
执行 deploy 脚本
 │
 ├─ 前端:替换 Nginx 静态目录
 │
 └─ 后端:替换应用目录 + npm ci --omit=dev + PM2 restart

流程变成:

text 复制代码
Git
 ↓
Jenkins build
 ↓
Publish Over SSH
 ↓
192.168.31.90:/tmp/jenkins/xxx
 ↓
远程 deploy 脚本
 ↓
正式目录

备份、健康检查、回滚等逻辑可以集中维护在业务服务器部署脚本中,避免 Jenkins Execute shell 过度膨胀。


14. 目标服务器 deploy 用户与目录

业务服务器 192.168.31.90 创建一个专门发布账号:

bash 复制代码
sudo useradd -m deploy

准备目录:

bash 复制代码
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 这个用户能用:

bash 复制代码
node -v
npm -v
pm2 -v

如未安装 PM2:

bash 复制代码
npm install -g pm2

SSH 认证建议使用 Key,并避免使用 root 账号直接登录业务服务器。


15. 配置 Publish Over SSH

插件装好后进入:

text 复制代码
Manage Jenkins
→ System
→ Publish over SSH

添加服务器:

text 复制代码
Name:prod-server
Hostname:192.168.31.90
Username:deploy
Remote Directory:/tmp/jenkins

认证配置 SSH Private Key。

点:

text 复制代码
Test Configuration

测试成功后保存配置。

Publish Over SSH 支持在文件传输完成后执行远程命令,因此可以将实际部署逻辑交由目标服务器脚本处理。


16. 远程发布前端 web_test

远程模式下,Jenkins Execute shell 只负责生成构建产物,不直接操作业务服务器目录:

bash 复制代码
#!/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"

然后在:

text 复制代码
Post-build Actions
→ Send build artifacts over SSH

选择:

text 复制代码
prod-server

Transfer Set:

text 复制代码
Source files:web/dist/**
Remove prefix:web/dist
Remote directory:web_test

由于全局 Remote Directory 是:

text 复制代码
/tmp/jenkins

最终文件会到:

text 复制代码
/tmp/jenkins/web_test

目标服务器写:

bash 复制代码
vim /home/deploy/scripts/deploy-web_test.sh
bash 复制代码
#!/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"

授权:

bash 复制代码
chmod +x /home/deploy/scripts/deploy-web_test.sh

Publish Over SSH 的 Exec command 填:

bash 复制代码
bash /home/deploy/scripts/deploy-web_test.sh

前端远程发布流程如下:

text 复制代码
Jenkins build
→ SSH 上传 dist
→ 执行 deploy-web_test.sh
→ 更新 Nginx 目录

17. 远程发布后端 api_test

后端远程发布还需要在目标服务器安装生产依赖并重启 PM2。

Jenkins 先完成依赖安装、编译并生成发布包:

bash 复制代码
#!/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:

text 复制代码
Source files:api/api_test.tar.gz
Remove prefix:api
Remote directory:api_test

最终:

text 复制代码
/tmp/jenkins/api_test/api_test.tar.gz

远程服务器脚本:

bash 复制代码
vim /home/deploy/scripts/deploy-api_test.sh
bash 复制代码
#!/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"

授权:

bash 复制代码
chmod +x /home/deploy/scripts/deploy-api_test.sh

Publish Over SSH 的 Exec command

bash 复制代码
bash /home/deploy/scripts/deploy-api_test.sh

该方式下 Jenkins 不直接管理远程服务器上的 PM2:

text 复制代码
Jenkins:build + 上传
业务服务器:安装生产依赖 + npm run start + PM2 restart

这种职责划分能够将 CI 构建与业务运行管理解耦。


18. rsync 与 Publish Over SSH 选型

两种方案的边界可以概括为:

text 复制代码
                         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. 完整流程汇总

前端同机:

text 复制代码
Gitee
 ↓
Jenkins web_test
 ↓
Node 24 / pnpm
 ↓
npm run build
 ↓
web/dist
 ↓
rsync --delete
 ↓
/home/html/web_test
 ↓
Nginx

后端同机:

text 复制代码
Gitee
 ↓
Jenkins api_test
 ↓
npm ci
 ↓
npm run build
 ↓
dist
 ↓
/home/apps/api_test
 ↓
npm ci --omit=dev
 ↓
npm run start
 ↓
PM2
 ↓
Nginx

Jenkins 权限:

text 复制代码
linhao
└── Global:admin

api
├── Global:loginSys
└── Item:api
    └── ^api_.*$

web
├── Global:loginSys
└── Item:web
    └── ^web_.*$

远程发布:

text 复制代码
Git
 ↓
Jenkins build
 ↓
Publish Over SSH
 ↓
192.168.31.90:/tmp/jenkins
 ↓
deploy-xxx.sh
 ↓
前端:Nginx 静态目录
后端:应用目录 + PM2

至此,基础 Jenkins CI/CD 已覆盖代码拉取、构建、部署、运行与权限隔离。

Webhook、Pipeline/Jenkinsfile、Docker、蓝绿发布等能力可在此基础上继续扩展。首次搭建时,建议优先保证以下主链路稳定:

text 复制代码
拉代码 → 构建 → 发布 → 运行 → 权限隔离

在基础链路稳定后,可以接入 Gitee Webhook 实现自动触发,或进一步将 Freestyle Project 迁移为 Jenkinsfile/Pipeline。


20. 验收检查

建议按以下顺序进行验收:

text 复制代码
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 脚本可以执行
相关推荐
葡萄城技术团队2 小时前
ERP 内置 BI(上):为什么业务部门还在用 Excel 做分析?
前端
deli0070072 小时前
技术案例:使用华为云码道(CodeArts)智能体开发鸿蒙徒步行程记录App
前端
计算机魔术师2 小时前
Anthropic 详解 7·30 安全事件:配置错误致 Claude 访问真实系统,已加强沙箱隔离与实时监控
前端
zlwool2 小时前
图纸管理选型:从变更频率到齐套率
java·前端·python·erp·设备erp·非标机械
阿图灵2 小时前
MakerHub 开发报告:v1.0.0 → v1.1.0(单日 26 提交,图片渲染、目录跟随与数据真实化)
前端·vue·个人网站·deepseek·开发报告
程序员小八7773 小时前
Go Web 工程化:日志、配置与错误处理中间件,让服务「能上线」
前端·中间件·golang
SquabbyZhu3 小时前
从 4 个 URL 到 1 个入口:Peaks-Loop 驱动的微前端聚合实践
前端
Hilaku3 小时前
技术好就能升职是前端圈最大的谎言!
前端·javascript·程序员
lhldsg3 小时前
树洞倾诉的核心需求与产品定位误区
java·前端·小程序