GitLab CI/CD 完全指南
一份涵盖从入门、配置到问题排查的完整 CI/CD 实践文档,实现快速上手并高效利用自动化流水线。
参考文档
核心流程
项目的流水线被划分为多个阶段 (Stages),确保任务按预定顺序执行,完整流程如下:
notify_start -> lint -> sonar -> build -> deploy -> notify_end
- lint & sonar: 在合并请求 (Merge Request) 场景下运行,进行代码规范检查和静态质量分析,保障代码质量。
- build & deploy: 在推送到
dev或master分支,或手动触发时执行,完成应用的构建和部署。



::
配置详解
CI/CD 文件结构
.
├── .gitlab-ci.yml # 主配置文件,定义 stages, workflow, variables, include 规则
├── .gitlab-dev.yml # dev 环境的作业 (build, deploy, notify)
├── .gitlab-prod.yml # prod 环境的作业 (build, notify)
└── scripts/
└── ci/
├── package-zip.sh # 打包构建产物为 .zip 并生成环境变量
├── deploy-zip.sh # 部署 .zip 包到目标服务器
└── wechat-notify.js # 发送企业微信通知
.gitlab-ci.yml
# 全局规则:只在指定分支或合并到指定分支的请求中运行
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- if: $CI_PIPELINE_SOURCE == "push" && ($CI_COMMIT_BRANCH == "dev" || $CI_COMMIT_BRANCH == "master")
when: always
- if: $CI_PIPELINE_SOURCE == "web"
when: always
- when: never
# 定义流水线的阶段
stages:
- notify_start
- lint
- sonar
- build
- deploy
- notify_end
# 定义变量
variables:
NODE_VERSION: lts
PNPM_VERSION: latest
GIT_DEPTH: 0
GIT_STRATEGY: clone
BUILD_ENV:
value: dev
options:
- dev
- prod
description: 选择构建环境(dev/prod)
SONAR_HOST_URL: # SonarQube 主机地址(自定义)
value: 'http://10.0.0.100:9000'
description: SonarQube主机地址
WECHAT_WEBHOOK_URL: # 企业微信webhook地址,用于通知(自定义)
value: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your-webhook-key-here'
description: 企业微信webhook地址
BUILD_DIR: # 构建目录(自定义)
value: app-gen/dist/web/your-app-name
description: 构建目录
DEPLOY_HOST: # 测试部署主机(一般固定,无需修改)
value: 10.0.0.100
description: 测试部署主机
DEPLOY_DIR: # 测试部署目录(自定义)
value: /home/user/nginx/html/
description: 测试部署目录
default:
image: node:${NODE_VERSION}
tags:
- sonarqube
include:
- local: .gitlab-dev.yml
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"
- if: $CI_PIPELINE_SOURCE == "web" && $BUILD_ENV == "dev"
- local: .gitlab-prod.yml
rules:
- if: $CI_PIPELINE_SOURCE == "web" && $BUILD_ENV == "prod"
# 代码检查作业
lint:
stage: lint
script:
- npm install -g pnpm@${PNPM_VERSION}
- pnpm install --frozen-lockfile
- pnpm eslint 'app/**/*.{ts,tsx,vue}'
artifacts:
when: always
reports:
junit: .eslintcache
paths:
- .eslintcache
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
allow_failure: true
# SonarQube 代码分析
sonarqube-check:
stage: sonar
image: openjdk:11-jre-slim
variables:
SONAR_PROJECT_KEY: ${CI_PROJECT_ID}
SONAR_PROJECT_NAME: ${CI_PROJECT_TITLE}
SONAR_PROJECT_VERSION: ${CI_COMMIT_SHORT_SHA}
SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar'
SONAR_SCANNER_VERSION: 4.6.2.2472
cache:
key: 'sonarqube-${SONAR_SCANNER_VERSION}'
paths:
- .sonar/cache
- sonar-scanner/
before_script:
- echo "准备 SonarScanner CLI ${SONAR_SCANNER_VERSION}..."
- |
if [ ! -d "sonar-scanner" ]; then
echo "下载并安装 SonarScanner CLI..."
apt-get update && apt-get install -y wget unzip
wget -O sonar-scanner.zip "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux.zip"
unzip sonar-scanner.zip
mv sonar-scanner-${SONAR_SCANNER_VERSION}-linux sonar-scanner
else
echo "使用缓存的 SonarScanner CLI..."
fi
- export PATH="$PWD/sonar-scanner/bin:$PATH"
script:
- echo "运行测试并生成覆盖率报告..."
- export PATH="$PWD/sonar-scanner/bin:$PATH"
- |
sonar-scanner \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.projectName="${SONAR_PROJECT_NAME}" \
-Dsonar.projectVersion=${SONAR_PROJECT_VERSION} \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN}
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 流水线开始通知
notify-start:
stage: notify_start
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- node scripts/ci/wechat-notify.js --label "${CI_MERGE_REQUEST_TITLE}" --type CI-start
when: on_success
# 流水线结束通知成功
notify-end-success:
stage: notify_end
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- node scripts/ci/wechat-notify.js --label "${CI_MERGE_REQUEST_TITLE}" --type CI-end --success
when: on_success
# 流水线结束通知失败
notify-end-failed:
stage: notify_end
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- node scripts/ci/wechat-notify.js --label "${CI_MERGE_REQUEST_TITLE}" --type CI-end --failed
when: on_failure
为了隔离不同环境的构建逻辑,采用了多脚本和动态配置的策略。
- 构建脚本分离:
package.json中定义了两个构建脚本:build: 用于prod环境,执行生产环境的完整构建。build:dev: 用于dev环境,执行测试环境的特定构建。
- 动态加载配置:
.gitlab-ci.yml会根据$BUILD_ENV变量的值,通过include规则动态加载.gitlab-dev.yml或.gitlab-prod.yml,从而执行对应环境的作业。
全局环境变量
| 变量名 | 用途 |
|---|---|
BUILD_ENV | 控制构建环境 (dev/prod),流水线自动或手动选择 |
BUILD_DIR | 指定构建产物的输出目录,根据项目调整 |
SONAR_HOST_URL | SonarQube 服务器地址 |
WECHAT_WEBHOOK_URL | 企业微信 WebHook地址,用于通知 |
DEPLOY_HOST | 部署目标服务器 IP 地址 |
DEPLOY_DIR | 部署到服务器上的目标目录 |
BUILD_DIR 等变量需要根据不同项目进行修改,请确保其指向正确的构建产物目录。企业微信通知
流水线的关键节点会通过企业微信机器人发送实时通知。
- 通知脚本:
scripts/ci/wechat-notify.js负责组装消息内容并发送。 - @ 成员: 在消息中可以使用
<@userid>的语法来提及指定成员,请确保填入的是成员的账号 (userid),而不是手机号或姓名。


手动执行流水线
除了自动化触发,你也可以手动运行流水线,并指定构建环境或者其他定义的全局变量。
- 进入项目的
构建->流水线页面。 - 点击
运行流水线按钮。 - 选择对应分支,在
变量区域,BUILD_ENV变量会提供一个下拉框,你可以选择dev或prod环境。

常见问题 (FAQ)
这是因为 .gitlab-ci.yml 中的 BUILD_DIR 变量被配置为了绝对路径 (例如 /app/dist/...)。
CI Runner 的工作目录是
$CI_PROJECT_DIR,构建产物路径应该是相对于该目录的相对路径。解决方案:
修改 .gitlab-ci.yml 中的 BUILD_DIR 变量,将其改为相对路径 (例如 app/dist/...)。
因为该通知作业的
needs 依赖中只包含了 build 作业,而没有包含 deploy 作业,导致它在 build 完成后就立即执行。因为
.gitlab-dev.yml 的 include 规则依赖于 $BUILD_ENV 变量,但在自动触发的 MR 流水线中,定义在 .gitlab-ci.yml 文件内部的 variables 在 include 解析阶段是不可用的。这是因为部署脚本中的
scp 命令源路径使用了 .../. 结尾,这表示只复制目录的内容,而不是目录本身。因为失败通知作业 (
notify-deploy-end-failed) 的 needs 依赖了上游作业。一旦上游作业失败,该通知作业自身会被 GitLab 跳过 (skipped)。企业微信的 Markdown 消息中,
@ 成员有严格的语法要求。