整体流程概览
-
开发人员 在本地完成功能开发和测试。
-
将代码
git push
到 GitLab 仓库的特定分支(例如main
或develop
)。 -
GitLab 检测到代码推送,根据项目根目录下的
.gitlab-ci.yml
文件触发 CI/CD Pipeline。 -
GitLab CI/CD 将 Pipeline 中的 Jobs 分配给已注册并处于活动状态的 GitLab Runner。
-
GitLab Runner 根据
.gitlab-ci.yml
中定义的stages
和jobs
执行任务:-
拉取代码: Runner 克隆最新的代码。
-
安装依赖 : 为前端应用和 Node.js 中间件安装
npm
或yarn
依赖。 -
运行测试: 执行单元测试、集成测试等(可选但推荐)。
-
构建应用:
- 构建 Qiankun 主应用。
- 构建 Qiankun 子应用。
- 准备 Node.js 中间件(可能涉及编译 TypeScript、打包等)。
-
部署: 将构建好的静态文件(前端)和应用文件(Node.js)安全地传输到目标服务器。
-
重启服务: 在目标服务器上,可能需要重启 Node.js 服务(例如使用 PM2)并重新加载 Nginx 配置。
-
-
Nginx 服务器根据配置:
- 提供主应用的
index.html
和静态资源。 - 根据路径或其他规则,正确路由到或提供子应用的静态资源(Qiankun 加载机制)。
- 将 API 请求反向代理到运行中的 Node.js 中间件服务。
- 提供主应用的
-
用户 通过浏览器访问 Nginx 提供的地址,体验应用。
一、 项目结构(示例)
假设你的项目结构如下:
lua
.
├── .gitlab-ci.yml # GitLab CI/CD 配置文件 (核心!)
├── frontend/ # 所有前端代码
│ ├── main-app/ # Qiankun 主应用
│ │ ├── public/
│ │ ├── src/
│ │ ├── package.json
│ │ └── vue.config.js or vite.config.js or other build config
│ ├── sub-app1/ # 第一个 Qiankun 子应用
│ │ ├── public/
│ │ ├── src/
│ │ ├── package.json
│ │ └── vue.config.js or vite.config.js or other build config
│ └── sub-app2/ # 第二个 Qiankun 子应用 (以此类推)
│ ├── public/
│ ├── src/
│ ├── package.json
│ └── vue.config.js or vite.config.js or other build config
├── middleware/ # Node.js 中间件
│ ├── src/
│ ├── package.json
│ ├── tsconfig.json # 如果使用 TypeScript
│ └── ecosystem.config.js # PM2 配置文件 (推荐)
└── deploy/ # (可选) 存放部署脚本或 Nginx 配置模板
└── nginx.conf.template
二、 GitLab 设置
-
创建 GitLab 项目: 在你的 GitLab 实例上创建一个新的项目。
-
推送代码: 将你的项目代码(包括上述结构)推送到这个 GitLab 仓库。
-
CI/CD 设置:
-
Variables (变量) : 在 GitLab 项目的
Settings
->CI/CD
->Variables
中设置敏感信息,例如:DEPLOY_SERVER_IP
: 目标部署服务器的 IP 地址。DEPLOY_SERVER_USER
: 用于 SSH 登录部署服务器的用户名。DEPLOY_SSH_PRIVATE_KEY
: 用于 SSH 免密登录的私钥。重要 : 将此变量类型设置为File
,并将Protect variable
和Mask variable
勾选上(如果适用)。DEPLOY_BASE_PATH
: 部署到服务器上的基础路径,例如/var/www/my-app
。NODE_ENV
: 设置为production
用于构建和部署。- (可选)
API_BASE_URL
: 前端应用中 API 请求的基础 URL。
-
Runner: 确保有可用的、已注册到该项目或 GitLab 实例的 Runner。我们将在下一节讨论 Runner 的设置。
-
三、 GitLab Runner 设置
GitLab Runner 是执行 .gitlab-ci.yml
中定义任务的代理。你可以在任何机器上安装它(你的本地机器、专门的构建服务器、部署目标服务器,或使用 Kubernetes)。
-
安装 : 根据你的操作系统,按照 GitLab Runner 官方文档 进行安装。
-
注册 : 使用
gitlab-runner register
命令将 Runner 连接到你的 GitLab 实例。你需要提供:-
GitLab 实例 URL (e.g.,
https://gitlab.com/
或你的私有实例 URL)。 -
注册 Token (在 GitLab 项目的
Settings
->CI/CD
->Runners
页面找到)。 -
Runner 描述 (e.g.,
my-project-runner
)。 -
Tags (标签) : 这是关键!为 Runner 添加标签,例如
docker
,linux
,my-project
。你将在.gitlab-ci.yml
中使用这些标签来指定哪些 Runner 可以执行特定的 Job。 -
Executor (执行器) : 选择 Runner 执行任务的方式。常用选项:
shell
: 直接在 Runner 所在的机器上执行命令。简单,但可能污染 Runner 环境。docker
: 在隔离的 Docker 容器中执行命令。推荐!干净、可重复的环境。需要 Runner 机器上安装 Docker。kubernetes
: 在 Kubernetes 集群中动态创建 Pod 来执行任务。适用于大规模场景。
我们这里假设使用
docker
executor 。注册时会询问默认的 Docker 镜像,你可以指定一个常用的 Node.js 镜像,如node:18
。 -
-
启动 Runner :
gitlab-runner start
(或根据系统服务管理方式启动)。 -
验证: 在 GitLab 项目的 Runner 设置页面,你应该能看到新注册的 Runner 显示为绿色在线状态。
四、 前端 (Qiankun) CI/CD 配置
我们需要在 package.json
中定义好构建脚本,并在 .gitlab-ci.yml
中调用它们。
1. 主应用 (frontend/main-app/package.json
)
json
{
"name": "main-app",
"version": "1.0.0",
"scripts": {
"serve": "vue-cli-service serve", // 或 vite
"build": "vue-cli-service build", // 或 vite build
"test:unit": "vue-cli-service test:unit" // 示例测试命令
// ... 其他脚本
},
// ... dependencies, devDependencies
}
2. 子应用 (frontend/sub-app1/package.json
)
子应用的构建配置(如 vue.config.js
或 vite.config.js
)需要确保:
- 输出 UMD (Universal Module Definition) 格式。
- 开启 CORS(开发时需要,生产环境由 Nginx 处理)。
- 设置正确的
publicPath
,以便主应用能找到子应用的资源。这个publicPath
通常需要与 Nginx 配置的路径相对应,或者是一个绝对 URL。在 CI/CD 中,可能需要根据部署环境动态设置。
json
{
"name": "sub-app1",
"version": "1.0.0",
"scripts": {
"serve": "vue-cli-service serve --port 8081", // 不同端口启动
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit"
// ...
},
// ...
}
确保子应用的 vue.config.js
或 vite.config.js
等构建工具配置中设置了正确的 output.libraryTarget: 'umd'
和 output.library: 'subApp1-[name]'
(或类似配置),以及正确的 publicPath
。
五、 Node.js 中间件 CI/CD 配置
1. middleware/package.json
perl
{
"name": "my-middleware",
"version": "1.0.0",
"main": "dist/server.js", // 指向构建后的入口文件
"scripts": {
"start": "node dist/server.js",
"dev": "nodemon src/server.ts", // 开发时
"build": "tsc", // 如果使用 TypeScript
// 或者: "build": "babel src -d dist --copy-files", // 如果使用 Babel
"test": "jest" // 示例测试命令
// ...
},
// ... dependencies, devDependencies (typescript, @types/node, ts-node, etc.)
}
2. middleware/ecosystem.config.js
(PM2 配置文件 - 推荐用于生产环境)
PM2 是一个流行的 Node.js 进程管理器。
java
// ecosystem.config.js
module.exports = {
apps : [{
name : "my-middleware-app", // 应用名称
script : "./dist/server.js", // 应用入口
cwd : "/var/www/my-app/middleware", // 应用根目录 (部署后的路径)
watch : false, // 不建议在生产环境启用 watch
env_production: { // 生产环境变量
NODE_ENV: "production",
PORT: 3000 // Node.js 服务监听的端口
// 添加其他需要的环境变量, 如数据库连接字符串等
},
// 可以添加日志配置、实例数量等
// log_date_format: "YYYY-MM-DD HH:mm Z",
// error_file: "/var/log/pm2/my-middleware-err.log",
// out_file: "/var/log/pm2/my-middleware-out.log",
// instances: "max", // 或指定数量
// exec_mode: "cluster"
}]
}
注意 : cwd
应该设置为部署后 Node.js 代码所在的服务器绝对路径。这个路径需要与部署脚本和 .gitlab-ci.yml
中的路径一致。
六、 .gitlab-ci.yml
详解 (核心文件)
这是定义 CI/CD 流水线的核心文件,放在项目根目录下。
bash
# .gitlab-ci.yml
# 定义默认使用的 Docker 镜像 (如果 job 没有指定 image)
# 选择一个包含 Node.js 和 npm/yarn 的镜像
default:
image: node:18 # 可以选择你项目使用的 Node.js 版本
# 定义流水线的各个阶段 (按顺序执行)
stages:
- install_deps # 安装依赖
- test # 运行测试 (可选)
- build # 构建应用
- deploy # 部署应用
# --- 缓存配置 ---
# 缓存 node_modules 以加速后续 Job
cache:
key:
files:
- frontend/main-app/package-lock.json # 或 yarn.lock
- frontend/sub-app1/package-lock.json
# - frontend/sub-app2/package-lock.json # 如果有更多子应用
- middleware/package-lock.json
paths:
- frontend/main-app/node_modules/
- frontend/sub-app1/node_modules/
# - frontend/sub-app2/node_modules/
- middleware/node_modules/
policy: pull-push # 默认策略,先拉取缓存,Job 成功后推送更新
# --- 变量定义 ---
variables:
# 定义前端构建输出的基础目录,方便后续引用
FRONTEND_BUILD_BASE: $CI_PROJECT_DIR/frontend_dist
# 定义 Node.js 构建输出目录
MIDDLEWARE_BUILD_PATH: $CI_PROJECT_DIR/middleware/dist
# 定义部署到服务器上的基础路径 (也可以从 GitLab CI/CD Variables 读取)
# DEPLOY_BASE_PATH: "/var/www/my-app" # 在 GitLab Variables 中设置更安全
# --- 安装依赖 Job (并行执行) ---
install_deps_main_app:
stage: install_deps
tags:
- docker # 指定使用带有 'docker' 标签的 Runner
script:
- echo "Installing dependencies for Main App..."
- cd frontend/main-app
- npm install # 或者 yarn install
artifacts:
paths:
- frontend/main-app/node_modules/ # 将 node_modules 作为 artifact 传递给后续阶段 (如果不用 cache)
expire_in: 1 hour # 设置 artifact 过期时间 (如果使用 cache,此 artifact 可能非必需)
cache: # 继承全局缓存策略,或定义 Job 特定缓存
key:
files:
- frontend/main-app/package-lock.json
paths:
- frontend/main-app/node_modules/
policy: pull-push
install_deps_sub_app1:
stage: install_deps
tags:
- docker
script:
- echo "Installing dependencies for Sub App 1..."
- cd frontend/sub-app1
- npm install
artifacts:
paths:
- frontend/sub-app1/node_modules/
expire_in: 1 hour
cache:
key:
files:
- frontend/sub-app1/package-lock.json
paths:
- frontend/sub-app1/node_modules/
policy: pull-push
install_deps_middleware:
stage: install_deps
tags:
- docker
script:
- echo "Installing dependencies for Middleware..."
- cd middleware
- npm install
artifacts:
paths:
- middleware/node_modules/
expire_in: 1 hour
cache:
key:
files:
- middleware/package-lock.json
paths:
- middleware/node_modules/
policy: pull-push
# --- 测试 Job (可选, 依赖于 install_deps 完成) ---
# test_main_app:
# stage: test
# tags:
# - docker
# needs: ["install_deps_main_app"] # 明确依赖关系
# script:
# - echo "Running tests for Main App..."
# - cd frontend/main-app
# - npm run test:unit # 执行测试命令
# allow_failure: false # 如果测试失败,则 Pipeline 失败
# test_sub_app1:
# stage: test
# tags:
# - docker
# needs: ["install_deps_sub_app1"]
# script:
# - echo "Running tests for Sub App 1..."
# - cd frontend/sub-app1
# - npm run test:unit
# allow_failure: false
# test_middleware:
# stage: test
# tags:
# - docker
# needs: ["install_deps_middleware"]
# script:
# - echo "Running tests for Middleware..."
# - cd middleware
# - npm run test
# allow_failure: false
# --- 构建 Job (并行执行, 依赖于 install_deps 或 test 完成) ---
build_main_app:
stage: build
tags:
- docker
needs: ["install_deps_main_app"] # 如果有 test 阶段,则依赖 test_main_app
script:
- echo "Building Main App..."
- cd frontend/main-app
# 设置环境变量,例如 API 地址 (可以从 GitLab CI/CD Variables 获取)
# export VUE_APP_API_BASE_URL=$API_BASE_URL
- npm run build
# 将构建结果移动到统一的输出目录
- mkdir -p $FRONTEND_BUILD_BASE/main
- mv dist/* $FRONTEND_BUILD_BASE/main/
artifacts:
paths:
- $FRONTEND_BUILD_BASE/main/ # 将主应用构建结果作为 artifact 传递给 deploy 阶段
expire_in: 1 day
build_sub_app1:
stage: build
tags:
- docker
needs: ["install_deps_sub_app1"] # 或 test_sub_app1
script:
- echo "Building Sub App 1..."
- cd frontend/sub-app1
# 同样可以设置环境变量
# export VUE_APP_API_BASE_URL=$API_BASE_URL
# export VUE_APP_PUBLIC_PATH=/sub-app1/ # 确保 publicPath 正确
- npm run build
# 将构建结果移动到统一的输出目录,注意路径区分
- mkdir -p $FRONTEND_BUILD_BASE/sub-app1
- mv dist/* $FRONTEND_BUILD_BASE/sub-app1/
artifacts:
paths:
- $FRONTEND_BUILD_BASE/sub-app1/ # 将子应用构建结果作为 artifact
expire_in: 1 day
build_middleware:
stage: build
tags:
- docker
needs: ["install_deps_middleware"] # 或 test_middleware
script:
- echo "Building Middleware..."
- cd middleware
- npm run build # 执行构建命令 (e.g., tsc)
# 注意:这里只构建代码,node_modules 不包含在构建产物中
# 部署时需要在服务器上重新安装生产依赖
artifacts:
paths:
- $MIDDLEWARE_BUILD_PATH/ # 将编译后的 JS 文件作为 artifact
- middleware/package.json # 需要 package.json 来安装生产依赖
- middleware/package-lock.json # 锁定依赖版本
- middleware/ecosystem.config.js # PM2 配置文件也需要部署
expire_in: 1 day
# --- 部署 Job (依赖于所有 build Job 完成) ---
deploy_to_production:
stage: deploy
tags:
- docker # 部署 Job 也可以在 Docker Runner 中执行 SSH 命令
needs: # 依赖所有构建任务
- job: build_main_app
artifacts: true # 需要下载构建产物
- job: build_sub_app1
artifacts: true
- job: build_middleware
artifacts: true
environment: # 定义部署环境 (可选,用于 GitLab 环境管理)
name: production
url: http://your-app.com # 部署后可访问的 URL
script:
# 准备 SSH 环境
- echo "Starting deployment to production server $DEPLOY_SERVER_IP..."
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' # 确保 ssh-agent 可用 (Alpine 镜像是 openssh)
- eval $(ssh-agent -s)
# 添加 SSH 私钥 (从 GitLab CI/CD Variable 读取)
# $DEPLOY_SSH_PRIVATE_KEY 是之前设置的 File类型的变量
- echo "$DEPLOY_SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
# 创建 SSH 目录并设置权限 (如果 Runner 里没有)
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# (可选) 禁用严格的主机密钥检查,首次连接时避免交互
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
# 或者,更好的是提前将服务器的公钥添加到 Runner 的 known_hosts
# - ssh-keyscan $DEPLOY_SERVER_IP >> ~/.ssh/known_hosts
# - chmod 644 ~/.ssh/known_hosts
# --- 部署前端文件 ---
# $DEPLOY_BASE_PATH 是在 GitLab Variables 设置的服务器基础路径, e.g., /var/www/my-app
# $FRONTEND_BUILD_BASE 是 Job 内的构建产物目录
- echo "Deploying frontend files..."
# 使用 rsync (推荐,比 scp 高效)
# 需要目标服务器安装 rsync: ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "sudo apt-get update && sudo apt-get install -y rsync"
- rsync -avz --delete $FRONTEND_BUILD_BASE/ $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP:$DEPLOY_BASE_PATH/frontend/
# 或者使用 scp (如果 rsync 不可用)
# - scp -r $FRONTEND_BUILD_BASE/* $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP:$DEPLOY_BASE_PATH/frontend/
# --- 部署 Node.js 中间件 ---
- echo "Deploying middleware files..."
# 目标服务器上的中间件目录
- export TARGET_MIDDLEWARE_PATH="$DEPLOY_BASE_PATH/middleware"
# 在服务器上创建目标目录 (如果不存在)
- ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP "mkdir -p $TARGET_MIDDLEWARE_PATH"
# 复制构建后的代码、配置文件和依赖描述文件
- scp -r $MIDDLEWARE_BUILD_PATH/* $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP:$TARGET_MIDDLEWARE_PATH/dist/
- scp middleware/package.json middleware/package-lock.json middleware/ecosystem.config.js $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP:$TARGET_MIDDLEWARE_PATH/
# - rsync -avz middleware/ $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP:$TARGET_MIDDLEWARE_PATH/ # 也可以用 rsync
# --- 在服务器上执行操作 ---
- echo "Running post-deployment commands on server..."
- | # 使用多行脚本块在远程服务器执行命令
ssh $DEPLOY_SERVER_USER@$DEPLOY_SERVER_IP << EOF
echo "Changing directory to middleware path: $TARGET_MIDDLEWARE_PATH"
cd $TARGET_MIDDLEWARE_PATH
echo "Installing/updating production dependencies for middleware..."
# 只安装生产依赖,--omit=dev 适用于 npm v7+, 老版本用 --production
npm install --omit=dev --no-package-lock # --no-package-lock 避免更新 lock 文件
echo "Checking PM2 status..."
# 确保服务器上安装了 PM2: npm install pm2 -g
pm2 list
echo "Reloading/Restarting middleware application with PM2..."
# 尝试启动或重启应用,如果配置文件变化会应用新的配置
# 'pm2 startOrRestart' 会根据应用是否存在来决定是启动还是重启
# 'pm2 reload' 可以实现 0 秒停机重载 (如果应用支持)
pm2 startOrRestart ecosystem.config.js --env production
# (可选) 保存 PM2 进程列表,以便服务器重启后自动恢复
pm2 save
echo "Checking Nginx configuration..."
sudo nginx -t # 测试 Nginx 配置语法
echo "Reloading Nginx configuration..."
sudo systemctl reload nginx # 重新加载 Nginx 配置使其生效
echo "Deployment finished successfully!"
EOF
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # 只在 main 分支的提交上运行此 Job
when: on_success # 只有前面的阶段都成功时才运行
# (可选) 添加一个清理 Job,用于删除不再需要的 artifacts 或执行其他清理任务
# cleanup:
# stage: .post # 特殊阶段,总是在 Pipeline 最后执行
# script:
# - echo "Cleaning up..."
# # 清理 Runner 上的临时文件等
代码解释:
-
default.image
: 为所有未指定image
的 Job 设置默认的 Docker 镜像。 -
stages
: 定义了 Pipeline 的执行阶段顺序。同一阶段的 Job 可以并行执行。 -
cache
: 定义缓存策略,主要用于缓存node_modules
,避免每次都重新下载。key
基于 lock 文件,当 lock 文件改变时缓存失效。policy: pull-push
表示先尝试拉取缓存,Job 成功后再把更新后的缓存推上去。 -
variables
: 定义全局或 Job 内部可用的变量。$CI_PROJECT_DIR
是 GitLab CI 预定义变量,表示 Runner 中项目代码的根目录。 -
install_deps_*
Jobs:- 属于
install_deps
阶段。 tags
: 指定只有带有docker
标签的 Runner 才能执行此 Job。script
: 包含实际执行的 shell 命令,如cd
进入目录,npm install
安装依赖。artifacts
: 定义 Job 成功后需要保留并传递给后续阶段的文件或目录。如果使用了高效的缓存,node_modules
可能不需要作为 artifact 传递。cache
: 可以覆盖全局缓存策略,为特定 Job 定义缓存。
- 属于
-
test_*
Jobs (注释掉的可选部分) :- 属于
test
阶段。 needs
: 定义 Job 依赖关系。确保在安装依赖之后才运行测试。script
: 执行测试命令,如npm run test:unit
。allow_failure: false
: 表示如果此 Job 失败,整个 Pipeline 将标记为失败。
- 属于
-
build_*
Jobs:- 属于
build
阶段。 needs
: 依赖于安装(或测试)阶段。script
: 执行构建命令npm run build
。关键 : 将构建产物(通常在dist
目录)移动到一个统一的、按应用区分的目录 ($FRONTEND_BUILD_BASE/main
,$FRONTEND_BUILD_BASE/sub-app1
,$MIDDLEWARE_BUILD_PATH
),以便deploy
阶段能方便地找到它们。artifacts
: 将构建产物定义为 artifact,供deploy
阶段下载使用。对于 Node.js,除了编译后的代码,还需要package.json
,package-lock.json
和ecosystem.config.js
。
- 属于
-
deploy_to_production
Job:-
属于
deploy
阶段。 -
needs
: 依赖所有build
Job,并设置artifacts: true
来下载它们的产物。 -
environment
: (可选) 关联到 GitLab 的环境功能,方便追踪部署。 -
script
:-
SSH 设置 : 安装
openssh-client
,启动ssh-agent
,使用ssh-add
添加从 GitLab Variable ($DEPLOY_SSH_PRIVATE_KEY
) 读取的私钥。配置StrictHostKeyChecking no
或添加known_hosts
以避免交互。 -
部署前端 : 使用
rsync
或scp
将$FRONTEND_BUILD_BASE
目录下的所有内容(包含 main 和 sub-app 的构建结果)复制到服务器的目标路径$DEPLOY_BASE_PATH/frontend/
。rsync -avz --delete
是常用的选项,它会同步文件,并删除目标目录中源目录不存在的文件。 -
部署后端 : 创建后端目标目录,复制编译后的代码 (
dist
)、package.json
、package-lock.json
和ecosystem.config.js
到服务器的$TARGET_MIDDLEWARE_PATH
。 -
服务器端操作 : 使用
ssh user@host 'commands'
或ssh user@host << EOF ... EOF
在远程服务器上执行命令:cd
到中间件目录。npm install --omit=dev
: 在服务器上安装生产依赖。pm2 startOrRestart ecosystem.config.js --env production
: 使用 PM2 启动或重启 Node.js 应用,加载ecosystem.config.js
中定义的配置和生产环境变量。pm2 save
: (可选) 保存当前 PM2 进程列表,以便服务器重启后能自动恢复。sudo nginx -t
: 检查 Nginx 配置语法是否正确。sudo systemctl reload nginx
: 平滑地重新加载 Nginx 配置,应用更改。
-
-
rules
: 定义 Job 何时运行。if: '$CI_COMMIT_BRANCH == "main"'
表示只有当代码提交到main
分支时,这个部署 Job 才会执行。when: on_success
确保只有在 Pipeline 前面的所有必需阶段都成功完成后才执行部署。
-
七、 Nginx 配置
Nginx 在这里扮演两个主要角色:
- 静态文件服务器: 提供构建好的前端文件(主应用和子应用)。
- 反向代理 : 将特定的 URL 请求(如
/api/
)转发给在后端运行的 Node.js 中间件服务。
假设你的部署路径是 /var/www/my-app
,其中包含:
/var/www/my-app/frontend/main/
: 主应用构建文件 (index.html
,js/
,css/
)/var/www/my-app/frontend/sub-app1/
: 子应用1构建文件 (js/
,css/
, 可能还有index.html
但通常不用)/var/www/my-app/middleware/
: Node.js 应用代码,监听在localhost:3000
(由 PM2 启动)
以下是一个 Nginx 配置文件示例 (/etc/nginx/sites-available/my-app.conf
,然后软链接到 sites-enabled
):
bash
# /etc/nginx/sites-available/my-app.conf
server {
listen 80; # 监听 HTTP 端口
# listen 443 ssl http2; # 如果配置 HTTPS
server_name your-app.com www.your-app.com; # 替换为你的域名
# (如果配置 HTTPS)
# ssl_certificate /etc/letsencrypt/live/your-app.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your-app.com/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# 定义部署的基础路径变量 (可选,使配置更清晰)
set $base_path /var/www/my-app/frontend;
# 日志文件 (可选)
# access_log /var/log/nginx/my-app.access.log;
# error_log /var/log/nginx/my-app.error.log;
# --- 主应用配置 ---
location / {
root $base_path/main; # 主应用的 index.html 和静态资源根目录
try_files $uri $uri/ /index.html; # 对于 SPA (Single Page Application) 很重要
# 尝试直接匹配文件 ($uri)
# 尝试匹配目录 ($uri/)
# 如果都找不到,则返回 /index.html,让前端路由处理
index index.html index.htm;
}
# --- 子应用静态资源 ---
# Qiankun 通常由主应用加载子应用的 JS/CSS。
# 确保主应用构建时使用的子应用 publicPath 与这里的 Nginx 配置匹配。
# 例如,如果子应用1的资源 URL 是 /sub-app1/js/app.js
# 并且构建文件放在 $base_path/sub-app1/ 目录下
location /sub-app1/ {
alias $base_path/sub-app1/; # 使用 alias 指向子应用构建文件的实际路径
# 注意: alias 路径末尾需要有 /
try_files $uri $uri/ =404; # 子应用通常没有自己的 index.html 入口,找不到就 404
# 如果子应用有自己的路由并且需要 fallback 到某个文件,可以调整 try_files
}
# 如果有更多子应用,添加类似的 location 块
# location /sub-app2/ {
# alias $base_path/sub-app2/;
# try_files $uri $uri/ =404;
# }
# --- Node.js 中间件反向代理 ---
location /api/ { # 所有以 /api/ 开头的请求都代理到 Node.js 服务
proxy_pass http://localhost:3000/; # Node.js 服务监听的地址和端口
# --- 重要: 设置代理头部信息 ---
proxy_http_version 1.1; # 推荐使用 HTTP/1.1
proxy_set_header Upgrade $http_upgrade; # 用于 WebSocket 支持 (如果需要)
proxy_set_header Connection 'upgrade'; # 用于 WebSocket 支持 (如果需要)
proxy_set_header Host $host; # 将原始请求的 Host 头部传递给后端
proxy_cache_bypass $http_upgrade;
# 传递客户端真实 IP 地址给后端 (Node.js 中可能需要读取这些头部)
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; # 传递协议 (http or https)
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# (可选) 增加代理超时时间
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout 60s;
# (可选) 如果 Node.js API 需要处理 /api/ 前缀,则不需要重写
# 如果 Node.js API 内部路由不包含 /api/ 前缀,可能需要重写 URL
# rewrite ^/api/(.*)$ /$1 break; # 从请求路径中移除 /api/
}
# (可选) 阻止访问隐藏文件,如 .git, .env
location ~ /. {
deny all;
}
# (可选) 压缩静态资源以提高加载速度
# gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
}
Nginx 配置解释:
-
listen
: 指定 Nginx 监听的端口(HTTP 80, HTTPS 443)。 -
server_name
: 匹配请求的域名。 -
set $base_path
: 定义一个变量,方便引用前端文件的根目录。 -
location /
: 匹配根路径/
和所有未被其他location
块匹配的请求。root
: 指定提供文件的根目录,这里是主应用的构建目录。try_files
: 这是处理 SPA 路由的关键。它会按顺序查找文件,如果找不到,最后会返回/index.html
,让 Vue Router 或 React Router 处理路由。
-
location /sub-app1/
: 匹配以/sub-app1/
开头的 URL。alias
: 将请求映射到服务器上的另一个路径。这对于提供不在root
目录下的文件很有用。确保这里的路径与子应用构建时的publicPath
和部署脚本中的目标路径一致。try_files $uri $uri/ =404;
: 尝试提供子应用的具体静态文件。如果找不到,返回 404。
-
location /api/
: 匹配所有以/api/
开头的请求。proxy_pass http://localhost:3000/;
: 将这些请求转发给在本机 3000 端口运行的 Node.js 服务。注意末尾的/
,它会影响请求 URI 如何传递给后端。proxy_set_header
: 设置传递给后端服务的 HTTP 头部。这对于后端获取客户端真实 IP、Host、协议等信息非常重要。Upgrade
和Connection
用于支持 WebSocket。rewrite
(注释掉的可选部分): 如果你的 Node.js API 路由是从根路径开始的(例如/users
而不是/api/users
),你可能需要用rewrite
指令去掉请求中的/api/
前缀。
-
其他: 配置了日志、禁止访问隐藏文件、HTTPS (注释掉的部分,需要配合证书) 和 Gzip 压缩 (注释掉的可选部分)。
部署后检查:
- 确保 Nginx 服务已启动并运行 (
sudo systemctl status nginx
)。 - 确保 Node.js 应用已通过 PM2 启动并运行 (
pm2 list
)。 - 检查 Nginx 和 PM2 的日志文件是否有错误。
- 尝试访问你的应用域名,看主应用是否加载。
- 导航到需要加载子应用的部分,检查浏览器控制台是否有资源加载错误或 CORS 错误。
- 测试 API 请求是否能成功到达 Node.js 后端并返回正确结果。
八、 总结与后续改进
这个流程提供了一个相当完整的 CI/CD 自动化方案。从代码提交到应用部署和服务的自动更新,大大提高了效率和一致性。
后续可考虑的改进点:
-
环境分离 : 为不同的环境(如
development
,staging
,production
)创建不同的 GitLab CI/CD 变量、部署 Job 和 Nginx 配置。可以使用不同的分支触发不同环境的部署。 -
更精细的缓存策略: 针对不同类型的依赖(前端 vs 后端)或构建步骤使用不同的缓存 key。
-
并行与依赖优化 : 优化
.gitlab-ci.yml
中的needs
关系,让尽可能多的 Job 并行执行以缩短 Pipeline 时间。 -
安全性:
- 使用 GitLab CI/CD Variables (Protected & Masked) 存储所有敏感信息。
- 定期审计 SSH 密钥和访问权限。
- 在 CI 流程中加入代码扫描、依赖项漏洞扫描工具(如 Snyk, OWASP Dependency-Check)。
-
数据库迁移 : 如果 Node.js 应用涉及数据库,需要在部署流程中加入数据库迁移步骤(例如使用
knex migrate:latest
)。 -
回滚策略: 定义如何在部署失败或部署后发现严重问题时快速回滚到上一个稳定版本。这可能涉及保留旧版本的构建产物和代码,并提供一个手动或自动触发的回滚 Job。
-
监控与告警: 集成监控工具(如 Prometheus, Grafana, Sentry)来监控应用性能、错误和服务器状态,并设置告警。
-
Docker 化部署: 将 Node.js 应用和前端静态文件打包到 Docker 镜像中,在 CI 中构建镜像并推送到 Docker Registry(如 GitLab Container Registry),部署时拉取镜像并运行容器。这可以进一步提高环境一致性。Nginx 也可以作为另一个容器运行,并通过 Docker 网络连接到应用容器。
-
使用 Monorepo 工具: 如果你的前后端代码在一个仓库里,可以考虑使用 Lerna 或 Nx 等 Monorepo 管理工具,它们可以帮助优化依赖安装、构建和测试流程,只重新构建和部署有变化的部分。