GitLab CI/CD 配置解析
这个配置文件定义了一个完整的 Node.js 项目部署流程,包含预安装、安装和部署三个阶段。下面是对每一部分的详细解析:
变量定义部分
yaml
| variables: | |
| BD: dist # 构建输出目录(build directory) | |
| PD: dev.hanlan.site # 生产目录(production directory) | |
| BUR: dev.hanlan.site_backup # 备份目录(backup directory) | |
| SD: “${ng_sd}” # 源目录(source directory),从变量ng_sd获取 | |
| ORTHER_SOURCEDIR: /www/wwwroot # 其他源目录路径 | |
| DSI: “${DEFAULT}” # 部署服务器IP(deploy server IP) | |
| TEST_SERVER_IP: “10.10.17.18” # 测试服务器IP | |
| MASTER_SERVER_IP: “暂时无生产IP” # 生产服务器IP(暂未设置) | |
| D_HOST: “10.10.17.16:5000” # Docker镜像仓库地址 | |
| NPM_REGISTRY: “https://registry.npmmirror.com” # npm镜像源 |
阶段定义
yaml
| stages: | |
| – preinstall # 预安装阶段(仅当package.json/package-lock.json变化时运行) | |
| – install # 安装阶段(每次构建都运行) | |
| – deploy # 部署阶段 |
缓存配置
yaml
| cache: | |
| key: | |
| files: | |
| – package-lock.json # 基于package-lock.json的hash作为缓存键 | |
| paths: | |
| – node_modules/ # 缓存node_modules目录 | |
| – package-lock.json # 缓存package-lock.json文件 |
预安装任务
yaml
| preinstall-job: | |
| image: $D_HOST/node:v20 # 使用自定义Node.js 20镜像 | |
| stage: preinstall # 属于preinstall阶段 | |
| only: # 仅在以下分支触发 | |
| refs: | |
| – master | |
| – test | |
| – dev | |
| changes: # 仅当以下文件变化时触发 | |
| – package.json | |
| – package-lock.json | |
| script: | |
| – echo “配置 NPM 镜像” | |
| – npm config set registry $NPM_REGISTRY # 设置npm镜像源 | |
| – echo “依赖发生变化,开始 install” | |
| – npm install # 安装依赖 | |
| – echo “install 完成” |
安装任务
yaml
| install-job: | |
| image: $D_HOST/node:v20 # 使用自定义Node.js 20镜像 | |
| stage: install # 属于install阶段 | |
| only: # 仅在以下分支触发 | |
| refs: | |
| – master | |
| – test | |
| – dev | |
| script: | |
| – echo “配置 NPM 镜像” | |
| – npm config set registry $NPM_REGISTRY # 设置npm镜像源 | |
| – echo “start install” | |
| – npm install # 安装依赖 | |
| – echo “build package” | |
| – npm run build:${CI_COMMIT_BRANCH} # 根据分支构建项目 | |
| – echo “完成打包” | |
| artifacts: # 生成物配置 | |
| paths: | |
| – $BD/ # 保存dist目录内容 | |
| expire_in: 1 days # 生成物保存1天 |
部署任务
yaml
| deploy-job: | |
| image: $D_HOST/sshpass:latest # 使用包含sshpass的自定义镜像 | |
| stage: deploy # 属于deploy阶段 | |
| only: # 仅在以下分支触发 | |
| refs: | |
| – master | |
| – test | |
| – dev | |
| script: | # 多行脚本(使用|保持格式) | |
| echo “部署到 Nginx” | |
| echo “当前分支: $CI_COMMIT_BRANCH” | |
| echo “默认路径: SD=${SD}, PD=${PD}, BUR=${BUR}, DSI=${DSI}” | |
| # 根据分支设置变量 | |
| if [[ “$CI_COMMIT_BRANCH” == “test” ]]; then # 测试分支配置 | |
| PD=”test.emit.team” | |
| BUR=”test.emit.team_backup” | |
| DSI=”${TEST_SERVER_IP:-10.10.17.18}” # 使用测试服务器IP | |
| SD=”${ORTHER_SOURCEDIR:-/www/wwwroot}” | |
| elif [[ “$CI_COMMIT_BRANCH” == “master” ]]; then # 主分支配置 | |
| DSI=”${MASTER_SERVER_IP:-10.10.17.18}” # 使用生产服务器IP(暂未设置) | |
| SD=”${ORTHER_SOURCEDIR:-/www/wwwroot}” | |
| fi | |
| echo “更新后路径: SD=${SD}, PD=${PD}, BUR=${BUR}, DSI=${DSI}” | |
| # 一行SSH备份命令(变量在本地先展开) | |
| sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no root@$DSI “\ | |
| mkdir -p \”$SD/$BUR\” && \ # 创建备份目录 | |
| if [ -d \”$SD/$PD\” ] && [ \”\$(ls -A \”$SD/$PD\”)\” ]; then \ # 检查生产目录是否存在且非空 | |
| BACKUP_FILE=\”$SD/$BUR/backup_$(date +%Y%m%d%H%M%S).tar.gz\” && \ # 备份文件名 | |
| echo \”开始备份: \$BACKUP_FILE\” && \ | |
| tar czf \”\$BACKUP_FILE\” -C \”$SD\” \”$PD\” && \ # 执行备份 | |
| echo \”备份成功:\$BACKUP_FILE\”; \ | |
| else \ | |
| echo \”源目录不存在或为空,跳过备份\”; \ # 目录不存在或为空时的处理 | |
| fi” | |
| # 清空旧目录 | |
| sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no root@$DSI “rm -rf \”$SD/$PD\”/*” | |
| # 本地打包 | |
| if [ -d “$BD” ] && [ “$(ls -A “$BD”)” ]; then # 检查构建目录是否存在且非空 | |
| echo “打包构建产物” | |
| tar czf release.tar.gz -C “$BD” . # 打包构建产物 | |
| else | |
| echo “错误: 构建目录 $BD 不存在或为空!” # 错误处理 | |
| exit 1 | |
| fi | |
| # 传输并解压 | |
| sshpass -p $PASSWORD scp -o StrictHostKeyChecking=no release.tar.gz root@$DSI:”$SD/$PD/” # 上传打包文件 | |
| sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no root@$DSI “\ # 远程解压 | |
| cd \”$SD/$PD\” && \ | |
| echo \”解压到: $SD/$PD\” && \ | |
| tar xzf release.tar.gz && \ # 解压文件 | |
| rm -f release.tar.gz && \ # 删除临时打包文件 | |
| echo \”解压后文件列表:\” && \ | |
| ls -l” # 列出解压后的文件 | |
| echo “部署完成” | |
| dependencies: # 依赖的任务 | |
| – install-job # 依赖install-job的生成物 | |
| after_script: # 部署后清理 | |
| – echo “清理临时文件” | |
| – rm -f release.tar.gz # 删除本地临时打包文件 | |
| – echo “部署流程结束” |
流程总结
- 预安装阶段:仅在依赖文件变化时运行,安装项目依赖
- 安装阶段:每次构建都运行,安装依赖并构建项目,生成构建产物
- 部署阶段:将构建产物部署到目标服务器,包括:
- 根据分支设置不同的部署配置
- 备份现有生产环境
- 清空生产目录
- 上传并解压新版本
- 清理临时文件
整个流程实现了自动化构建和部署,支持多环境(开发/测试/生产)部署,并包含备份机制确保部署安全。
//////.gitlab-ci.yml////////////////////////////////////////////////////////////////////////////////////////////////
variables:
BD: dist
PD: dev.hanlan.site
BUR: dev.hanlan.site_backup
SD: “${ng_sd}”
ORTHER_SOURCEDIR: /www/wwwroot
DSI: “${DEFAULT}”
TEST_SERVER_IP: “10.10.17.18”
MASTER_SERVER_IP: “pro.emit.team”
D_HOST: “10.10.17.16:5000”
NPM_REGISTRY: “https://registry.npmmirror.com”
stages:
– preinstall
– install
– deploy
# 缓存配置,基于 package-lock.json 的 hash
cache:
key:
files:
– package-lock.json
paths:
– node_modules/
– package-lock.json
preinstall-job:
image: $D_HOST/node:v20
stage: preinstall
only:
refs:
– master
– test
– dev
changes:
– package.json
– package-lock.json
script:
– echo “配置 NPM 镜像”
– npm config set registry $NPM_REGISTRY
– echo “依赖发生变化,开始 install”
– npm install
– echo “install 完成”
install-job:
image: $D_HOST/node:v20
stage: install
only:
refs:
– master
– test
– dev
script:
– echo “配置 NPM 镜像”
– npm config set registry $NPM_REGISTRY
– echo “start install”
– npm install
– echo “Format all the files under the “src” directory”
– npm run format
– echo “build package”
– npm run build:${CI_COMMIT_BRANCH}
– echo “完成打包”
artifacts:
paths:
– $BD/
expire_in: 1 days
deploy-job:
image: $D_HOST/sshpass:latest
stage: deploy
only:
refs:
– master
– test
– dev
script: |
echo “部署到 Nginx”
echo “当前分支: $CI_COMMIT_BRANCH”
echo “默认路径: SD=${SD}, PD=${PD}, BUR=${BUR}, DSI=${DSI}”
# 根据分支设置变量
if [[ “$CI_COMMIT_BRANCH” == “test” ]]; then
PD=”test.emit.team”
BUR=”test.emit.team_backup”
DSI=”${TEST_SERVER_IP:-10.10.17.18}”
SD=”${ORTHER_SOURCEDIR:-/www/wwwroot}”
elif [[ “$CI_COMMIT_BRANCH” == “master” ]]; then
PD=”pro.emit.team”
BUR=”pro.emit.team_backup”
DSI=”${MASTER_SERVER_IP:-pro.emit.team}”
SD=”${ORTHER_SOURCEDIR:-/www/wwwroot}”
PASSWORD=$PASSWORD_PROD_WEB
fi
echo “更新后路径: SD=${SD}, PD=${PD}, BUR=${BUR}, DSI=${DSI}”
# 一行 SSH 备份(变量在本地先展开)
sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no root@$DSI “\
mkdir -p \”$SD/$BUR\” && \
if [ -d \”$SD/$PD\” ] && [ \”\$(ls -A \”$SD/$PD\”)\” ]; then \
BACKUP_FILE=\”$SD/$BUR/backup_$(date +%Y%m%d%H%M%S).tar.gz\” && \
echo \”开始备份: \$BACKUP_FILE\” && \
tar czf \”\$BACKUP_FILE\” -C \”$SD\” \”$PD\” && \
echo \”备份成功:\$BACKUP_FILE\”; \
else \
echo \”源目录不存在或为空,跳过备份\”; \
fi”
# 清空旧目录
sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no root@$DSI “rm -rf \”$SD/$PD\”/*”
# 本地打包
if [ -d “$BD” ] && [ “$(ls -A “$BD”)” ]; then
echo “打包构建产物”
tar czf release.tar.gz -C “$BD” .
else
echo “错误: 构建目录 $BD 不存在或为空!”
exit 1
fi
# 传输并解压
sshpass -p $PASSWORD scp -o StrictHostKeyChecking=no release.tar.gz root@$DSI:”$SD/$PD/”
sshpass -p $PASSWORD ssh -o StrictHostKeyChecking=no root@$DSI “\
cd \”$SD/$PD\” && \
echo \”解压到: $SD/$PD\” && \
tar xzf release.tar.gz && \
rm -f release.tar.gz && \
echo \”解压后文件列表:\” && \
ls -l”
echo “部署完成”
dependencies:
– install-job
after_script:
– echo “清理临时文件”
– rm -f release.tar.gz
– echo “部署流程结束”