GitLab CI/CD 实战教程:一步步教你搭建
GitLab CI/CD 实战教程:从零开始,一步步教你搭建自动化流程
在现代软件开发实践中,持续集成(Continuous Integration, CI)和持续交付/部署(Continuous Delivery/Deployment, CD)是提升开发效率、保证代码质量、加速产品迭代的关键环节。GitLab 作为一款一体化的 DevOps 平台,内置了强大且易用的 CI/CD 功能。本教程将通过一个实战项目,一步步指导你如何搭建和配置 GitLab CI/CD,实现从代码提交到自动化构建、测试和部署的完整流程。
一、 什么是 CI/CD?为什么选择 GitLab CI/CD?
1. CI/CD 核心概念
- 持续集成 (CI): 开发人员频繁地将代码合并到主干(通常是
main
或master
分支)。每次合并后,系统会自动执行构建和单元测试,以便尽早发现集成错误。CI 的核心在于 自动化构建 和 自动化测试。 - 持续交付 (CD - Delivery): 在 CI 的基础上,将通过所有测试的代码自动部署到“类生产环境”(如 Staging 环境)。部署到生产环境前的最后一步通常需要手动确认。CD 的核心在于 自动化部署到测试环境,确保随时有一个可部署的版本。
- 持续部署 (CD - Deployment): 这是 CD 的延伸,将通过所有测试的代码 自动部署到生产环境,无需人工干预。这要求有极高的自动化测试覆盖率和对流程的信心。
2. GitLab CI/CD 的优势
- 一体化平台: GitLab 将代码仓库、项目管理、CI/CD、容器注册表、安全扫描等功能整合在一起,无需切换工具,体验流畅。
- 易于上手: 只需要在项目根目录下创建一个
.gitlab-ci.yml
文件来定义流水线(Pipeline),即可启用 CI/CD。语法简洁,基于 YAML。 - 强大的 Runner 系统: GitLab Runner 是执行 CI/CD 任务(Job)的代理程序。它可以安装在各种操作系统(Linux, macOS, Windows)和环境中(物理机、虚拟机、Docker、Kubernetes),支持多种执行器(Shell, Docker, Kubernetes等),扩展性强。
- 灵活性与可定制性: 支持复杂的流水线逻辑(如并行、串行、条件执行、手动触发),可定义环境变量、缓存、产物(Artifacts)等,满足不同项目的需求。
- 内置安全功能: 集成了静态应用安全测试 (SAST)、动态应用安全测试 (DAST)、依赖项扫描、容器扫描等安全能力,帮助在开发早期发现和修复漏洞。
- 可视化: 提供直观的流水线、任务、环境等视图,方便监控和调试。
- 免费层可用: GitLab.com 的免费计划已经包含了强大的 CI/CD 功能(有一定额度限制),对于个人项目和小型团队非常友好。自托管的 GitLab CE(社区版)也是免费的。
二、 准备工作:你需要什么?
在开始之前,请确保你具备以下条件:
- 一个 GitLab 账号: 你可以在 GitLab.com 注册免费账号,或者使用公司或组织自托管的 GitLab 实例。
- 一个 GitLab 项目: 你需要一个 Git 仓库来存放你的代码和
.gitlab-ci.yml
文件。可以创建一个新项目或使用现有项目。 - 基础 Git 操作知识: 了解
git clone
,git add
,git commit
,git push
等基本命令。 - (可选)一个简单的示例项目: 为了演示,我们将使用一个非常基础的 Node.js 应用(或任何你熟悉的语言编写的简单应用,甚至可以是静态 HTML 文件),但核心概念适用于任何类型的项目。
- (可选)Docker 基础知识: GitLab CI/CD 经常与 Docker 结合使用,以提供一致的构建和测试环境。了解 Docker 镜像和容器的基本概念会很有帮助。
三、 GitLab CI/CD 核心概念解析
在深入实战之前,理解以下几个核心概念至关重要:
- Pipeline (流水线): CI/CD 的最高层级抽象,代表了一次完整的构建、测试、部署流程。通常由一次代码提交(
git push
)或合并请求(Merge Request)触发。一个 Pipeline 包含多个 Stages。 - Stage (阶段): Pipeline 中的逻辑分组,用于定义任务的执行顺序。同一 Stage 中的 Jobs 会并行执行(如果 Runner 资源允许),只有当前 Stage 的所有 Jobs 成功完成后,下一个 Stage 才会开始执行。常见的 Stages 包括
build
(构建)、test
(测试)、deploy
(部署)等。 - Job (任务): Pipeline 中的基本执行单元,负责执行具体的脚本命令。例如,一个 Job 可能负责编译代码,另一个 Job 负责运行单元测试。每个 Job 都在一个独立的 Runner 环境中执行。
- Runner (执行器): 实际执行 Job 中定义脚本的代理程序。Runner 可以是共享的(由 GitLab.com 或你的 GitLab 管理员提供),也可以是你自己或团队注册的特定 Runner(Specific Runner)或组 Runner(Group Runner)。Runner 从 GitLab Coordinator 拉取 Job,执行完毕后将结果(日志、产物)发送回 GitLab。
- .gitlab-ci.yml: 存放在项目根目录下的 YAML 文件,用于定义 Pipeline 的结构(Stages、Jobs)、每个 Job 的执行脚本、执行条件、使用的 Docker 镜像、环境变量、缓存、产物等。这是配置 GitLab CI/CD 的核心文件。
- Artifacts (产物): Job 执行过程中产生的文件或目录(如编译后的二进制文件、测试报告、打包的应用)。可以定义将这些产物传递给后续 Stage 的 Job 使用,或者提供下载。
- Variables (变量): 用于存储配置信息、密钥等。可以是 GitLab 预定义的变量(如
CI_COMMIT_REF_NAME
表示当前分支名),也可以在.gitlab-ci.yml
或 GitLab UI 中自定义。敏感信息(如密码、API Key)应设置为“受保护”或“被遮盖”。 - Cache (缓存): 用于在不同的 Pipeline 或同一 Pipeline 的不同 Job 之间共享文件(如依赖库、编译缓存),以加速执行过程。
四、 实战演练:一步步搭建 CI/CD 流水线
假设我们有一个简单的 Node.js 项目,结构如下:
my-node-app/
├── app.js
├── package.json
├── test/
│ └── test.js
└── .gitlab-ci.yml <-- 我们将创建这个文件
package.json
可能包含类似内容:
json
{
"name": "my-node-app",
"version": "1.0.0",
"description": "A simple Node.js app for GitLab CI/CD demo",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "echo 'Running tests...' && exit 0" // 简化测试命令
},
"dependencies": {
// 可能的依赖
},
"devDependencies": {
// 可能的测试依赖
}
}
第 1 步:创建 .gitlab-ci.yml
文件
在项目根目录下创建 .gitlab-ci.yml
文件。这是定义 CI/CD 流水线的入口。
第 2 步:定义 Stages
首先,定义流水线包含哪些阶段以及它们的执行顺序。
```yaml
.gitlab-ci.yml
stages:
- build
- test
- deploy # 即使暂时不做实际部署,也可以先定义阶段
```
这里定义了三个阶段:build
、test
、deploy
。流水线将按此顺序执行:先执行 build
阶段的所有 Job,成功后再执行 test
阶段的所有 Job,最后是 deploy
阶段。
第 3 步:创建第一个 Job - 构建
现在,我们来创建一个属于 build
阶段的 Job。这个 Job 将模拟构建过程,比如安装依赖。
```yaml
.gitlab-ci.yml
stages:
- build
- test
- deploy
build_job:
stage: build # 指定该 Job 属于 build 阶段
image: node:18 # 指定执行该 Job 使用的 Docker 镜像
script:
- echo "Starting build process..."
- npm install # 安装 Node.js 依赖
- echo "Build finished."
artifacts: # 定义产物
paths:
- node_modules/ # 将安装好的依赖作为产物,供后续阶段使用
expire_in: 1 hour # 产物保留1小时(可选)
```
build_job:
: 这是 Job 的名称,可以自定义,但必须唯一。stage: build
: 明确此 Job 属于build
阶段。image: node:18
: 指定使用官方的 Node.js 18 Docker 镜像来运行这个 Job。Runner 会拉取这个镜像并在容器内执行script
中的命令。这确保了构建环境的一致性。script:
: 定义了 Job 要执行的 Shell 命令列表。这里我们打印信息并运行npm install
。artifacts:
: 定义需要保存并传递给后续阶段的文件或目录。我们将node_modules
目录作为产物。
第 4 步:创建第二个 Job - 测试
接下来,创建一个属于 test
阶段的 Job,用于运行测试。
```yaml
.gitlab-ci.yml
stages:
- build
- test
- deploy
build_job:
stage: build
image: node:18
script:
- echo "Starting build process..."
- npm install
- echo "Build finished."
artifacts:
paths:
- node_modules/
expire_in: 1 hour
test_job:
stage: test # 指定该 Job 属于 test 阶段
image: node:18 # 同样使用 Node.js 镜像
dependencies: # 声明依赖于 build_job 的产物
- build_job
script:
- echo "Starting test process..."
- npm test # 运行 package.json 中定义的测试命令
- echo "Testing finished."
```
test_job:
: Job 名称。stage: test
: 属于test
阶段。image: node:18
: 使用相同的环境。dependencies: [build_job]
: 这是一个重要的关键字。它表示test_job
需要build_job
产生的artifacts
。Runner 会在执行test_job
前自动下载build_job
定义的artifacts
(这里是node_modules/
目录)。这样就不需要在test_job
里重新npm install
了。注意:dependencies
默认会下载所有先前阶段的 Job 的 artifacts。指定 Job 名称可以更精确。如果不使用dependencies
而依赖cache
,则需要配置cache
。script:
: 执行测试命令。
第 5 步:检查 Runner
在你提交 .gitlab-ci.yml
文件之前,需要确保你的项目有可用的 Runner 来执行这些 Job。
- 对于 GitLab.com:
- 前往你的项目 -> Settings -> CI/CD -> Runners。
- 展开 "Shared runners" 部分。如果它们是启用的(通常默认启用),你就可以使用 GitLab 提供的共享 Runner。这些 Runner 支持 Docker 执行器,能够拉取我们指定的
node:18
镜像。
- 对于自托管 GitLab:
- 同样路径下检查 Shared Runners 是否启用。
- 检查 "Specific runners" 或 "Group runners" 是否有已注册并处于活动状态(绿色图标)的 Runner。
- 如果没有可用的 Runner,你需要根据 GitLab 文档 安装和注册一个 Runner。对于本教程,推荐安装 Docker 执行器的 Runner。
第 6 步:提交代码并观察 Pipeline
- 将
.gitlab-ci.yml
文件添加到 Git 暂存区:git add .gitlab-ci.yml
- 提交更改:
git commit -m "Add initial .gitlab-ci.yml for build and test"
- 推送到 GitLab 仓库:
git push origin <your-branch-name>
推送成功后,GitLab 会自动检测到 .gitlab-ci.yml
文件并触发一个新的 Pipeline。
- 查看 Pipeline:
- 在 GitLab 项目页面,导航到 "CI/CD" -> "Pipelines"。
- 你会看到一个正在运行(或已完成)的 Pipeline,对应你刚才的提交。
- 点击 Pipeline ID 可以查看详情,包括各个 Stages 和 Jobs 的状态。
- 点击 Job 名称可以查看该 Job 的详细日志输出,包括
script
中命令的执行情况、错误信息(如果有)等。
如果一切顺利,build_job
和 test_job
应该会依次成功执行(显示绿色对勾)。
第 7 步:添加一个简单的部署 Job (示例)
让我们添加一个 deploy
阶段的 Job。这里我们仅作演示,只打印一条消息。在实际场景中,这里会包含部署到服务器、云平台或 GitLab Pages 的命令。
```yaml
.gitlab-ci.yml
stages:
- build
- test
- deploy
build_job:
# ... (保持不变) ...
artifacts:
paths:
- node_modules/
- app.js # 假设部署需要 app.js
- package.json # 和 package.json
expire_in: 1 hour
test_job:
# ... (保持不变) ...
deploy_job:
stage: deploy # 属于 deploy 阶段
image: alpine:latest # 可以用一个轻量级镜像执行简单命令
script:
- echo "Starting deployment process..."
# 实际部署命令会放在这里,例如:
# - scp -r . user@your-server:/path/to/deploy
# - docker push your-registry/my-app:$CI_COMMIT_TAG (如果是构建 Docker 镜像)
# - ./deploy-script.sh
- echo "Simulating deployment to production..."
- echo "Deployment finished."
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # 仅在 main 分支上运行时执行此 Job
```
deploy_job:
: Job 名称。stage: deploy
: 属于deploy
阶段。image: alpine:latest
: 使用了一个非常小的 Linux 镜像,如果只是执行 shell 命令足够了。script:
: 包含模拟的部署消息。rules:
(或旧版的only
/except
): 这是控制 Job 何时运行的关键。if: '$CI_COMMIT_BRANCH == "main"'
: 这条规则表示,只有当代码提交到main
分支时,deploy_job
才会被包含在 Pipeline 中并执行。这样可以防止开发分支的每次提交都触发生产部署。你可以设置更复杂的规则,例如基于标签($CI_COMMIT_TAG
)、合并请求等。
第 8 步:使用 CI/CD 变量
假设部署脚本需要一个 API 密钥。我们不应该将其硬编码在 .gitlab-ci.yml
文件中。可以使用 GitLab CI/CD 变量。
-
在 GitLab UI 中添加变量:
- 前往项目 -> Settings -> CI/CD -> Variables。
- 点击 "Expand",然后点击 "Add variable"。
- Key: 输入变量名,例如
DEPLOY_API_KEY
。 - Value: 输入你的 API 密钥。
- Flags:
- Protect variable: 如果勾选,该变量只在受保护的分支(通常是
main
)或标签上运行的 Job 中可用。非常适合生产环境密钥。 - Mask variable: 如果勾选,GitLab 会尝试在 Job 日志中隐藏该变量的值,防止意外泄露。强烈建议对敏感信息启用。
- Protect variable: 如果勾选,该变量只在受保护的分支(通常是
- 点击 "Add variable"。
-
在
.gitlab-ci.yml
中使用变量:
```yaml
... (deploy_job 部分) ...
deploy_job:
stage: deploy
image: alpine:latest
script:
- echo "Starting deployment process..."
- echo "Using API Key: $DEPLOY_API_KEY" # 在脚本中通过 $VARIABLE_NAME 引用
# - deploy-script.sh --api-key $DEPLOY_API_KEY # 实际使用示例
- echo "Simulating deployment..."
- echo "Deployment finished."
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
```
现在,当 deploy_job
在 main
分支运行时,$DEPLOY_API_KEY
会被替换为你在 GitLab UI 中设置的值。
第 9 步:利用缓存加速
npm install
可能会下载很多依赖,每次运行 Pipeline 都重新下载会很慢。可以使用 cache
来缓存 node_modules
目录。
```yaml
.gitlab-ci.yml
在顶层定义默认缓存,所有 Job 都会继承
cache:
key:
files:
- package-lock.json # 缓存的 key 基于 package-lock.json 的内容
paths:
- node_modules/ # 缓存 node_modules 目录
policy: pull-push # 默认策略:尝试拉取缓存,Job 成功后推送更新缓存
stages:
- build
- test
- deploy
build_job:
stage: build
image: node:18
script:
- echo "Checking cache..."
- npm install # 如果缓存命中且有效,npm 会很快完成
- echo "Build finished."
# 不再需要将 node_modules 作为 artifact,因为 test_job 也会使用缓存
# 但如果需要传递其他构建产物,artifacts 仍然有用
test_job:
stage: test
image: node:18
# dependencies 不再是必须的,因为会使用缓存
# 但如果 build_job 产生了除 node_modules 外的其他 artifacts 且 test_job 需要,仍需 dependencies
script:
- echo "Starting test process..."
- npm test
- echo "Testing finished."
deploy_job 不变...
```
cache:
: 定义缓存策略。key:
: 定义缓存的唯一标识符。files: [package-lock.json]
: 使用package-lock.json
(或yarn.lock
) 文件的内容哈希作为 key 的一部分。这意味着只有当依赖项发生变化时,缓存才会失效并重新生成。这是一个常用的最佳实践。
paths: [node_modules/]
: 指定要缓存的目录。policy: pull-push
: Job 开始时尝试拉取(pull)缓存,Job 成功结束后将paths
中指定的内容推送(push)更新缓存。还有pull
(只拉不推)和push
(只推不拉)策略。
第 10 步:探索更多功能
我们已经搭建了一个基础的 CI/CD 流水线。GitLab CI/CD 还提供了许多高级功能值得探索:
- Manual Jobs (手动任务): 定义需要手动点击才能触发的 Job,常用于生产部署前的最后确认。使用
when: manual
关键字。 - Environments (环境): 定义部署目标环境(如
staging
,production
),并在 GitLab UI 中跟踪每个环境的部署历史和状态。 - Review Apps (评审应用): 自动为每个合并请求创建一个临时环境和部署,方便代码评审者直接预览更改。
- Security Scanning: 集成 SAST, DAST, Dependency Scanning 等,将安全检查融入流水线。
- Docker-in-Docker: 在 CI Job 内部构建 Docker 镜像。
- Matrix Builds: 使用不同的变量组合并行运行同一个 Job(例如,针对不同版本的 Node.js 进行测试)。
- Parent/Child Pipelines: 将复杂的流水线拆分成多个独立的子流水线。
五、 最佳实践与建议
- 保持 Job 简短和专注: 每个 Job 应只做一件事(编译、测试、部署等),便于调试和并行化。
- 使用明确的 Stage 顺序: 合理规划 Stages,反映真实的交付流程。
- 利用缓存: 对依赖项、编译结果等使用缓存,显著提高流水线速度。
- 管理好 Secrets: 使用 GitLab CI/CD Variables 存储敏感信息,并启用 "Protected" 和 "Masked" 标志。切勿将密钥硬编码在
.gitlab-ci.yml
或代码中。 - 使用固定的依赖版本: 使用
package-lock.json
或yarn.lock
等锁定文件,确保 CI 环境与本地开发环境依赖一致。在cache:key:files
中使用它们。 - 优化 Docker 镜像: 使用官方或精简的基础镜像,多阶段构建 (Multi-stage builds) 来减小最终部署镜像的大小。
- 编写有效的测试: CI 的价值很大程度上取决于自动化测试的质量和覆盖率。
- 控制 Job 执行条件: 使用
rules
(推荐) 或only/except
精确控制哪些 Job 在什么条件下(分支、标签、变量等)运行。 - 监控和日志: 定期检查 Pipeline 和 Job 日志,及时发现和解决问题。
- 迭代优化: CI/CD 不是一蹴而就的,根据项目需求和团队反馈持续改进你的
.gitlab-ci.yml
配置。
六、 故障排查
遇到问题时,可以从以下几个方面入手:
- YAML 语法错误:
.gitlab-ci.yml
对缩进非常敏感。提交前可以使用 GitLab UI 内置的 "CI Lint" 工具(在 CI/CD -> Editor 或 CI/CD -> Pipelines 页面找到)检查语法。 - Runner 问题: 检查 Runner 是否可用、是否在线、是否有适合 Job 的标签 (tags)、配置是否正确(如 Docker 是否能正常工作)。
- Job 日志: 最重要的调试信息来源。仔细阅读失败 Job 的日志,查找错误消息。GitLab UI 会高亮显示错误行。
- 权限问题: 脚本是否需要特定权限?访问外部服务(如服务器、云提供商)的凭证是否正确且有效?
- 依赖或环境问题: CI 环境与本地不一致?检查 Docker 镜像版本、依赖版本、环境变量。
- 缓存/产物问题: 缓存是否失效?产物是否正确生成和传递?
七、 总结
通过本教程,我们从零开始,一步步学习了 GitLab CI/CD 的核心概念,并动手实践了如何创建 .gitlab-ci.yml
文件,定义 Stages 和 Jobs,配置 Docker 镜像,使用 Artifacts、Variables 和 Cache,最终搭建起一个包含构建、测试和(模拟)部署的基础自动化流水线。
GitLab CI/CD 是一个功能极其丰富的工具,能够极大地提升软件开发和交付的效率与质量。掌握它,意味着你的团队可以更快地响应变化,更频繁地交付价值,更自信地发布软件。希望本教程能为你打开 GitLab CI/CD 的大门,鼓励你继续探索其更多高级功能,并将其应用到你的实际项目中,享受自动化带来的便利。现在,就开始在你的项目里添加 .gitlab-ci.yml
,开启你的 DevOps 之旅吧!