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): 开发人员频繁地将代码合并到主干(通常是 mainmaster 分支)。每次合并后,系统会自动执行构建和单元测试,以便尽早发现集成错误。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(社区版)也是免费的。

二、 准备工作:你需要什么?

在开始之前,请确保你具备以下条件:

  1. 一个 GitLab 账号: 你可以在 GitLab.com 注册免费账号,或者使用公司或组织自托管的 GitLab 实例。
  2. 一个 GitLab 项目: 你需要一个 Git 仓库来存放你的代码和 .gitlab-ci.yml 文件。可以创建一个新项目或使用现有项目。
  3. 基础 Git 操作知识: 了解 git clone, git add, git commit, git push 等基本命令。
  4. (可选)一个简单的示例项目: 为了演示,我们将使用一个非常基础的 Node.js 应用(或任何你熟悉的语言编写的简单应用,甚至可以是静态 HTML 文件),但核心概念适用于任何类型的项目。
  5. (可选)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 # 即使暂时不做实际部署,也可以先定义阶段
```

这里定义了三个阶段:buildtestdeploy。流水线将按此顺序执行:先执行 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

  1. .gitlab-ci.yml 文件添加到 Git 暂存区:git add .gitlab-ci.yml
  2. 提交更改:git commit -m "Add initial .gitlab-ci.yml for build and test"
  3. 推送到 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_jobtest_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 变量。

  1. 在 GitLab UI 中添加变量:

    • 前往项目 -> Settings -> CI/CD -> Variables。
    • 点击 "Expand",然后点击 "Add variable"。
    • Key: 输入变量名,例如 DEPLOY_API_KEY
    • Value: 输入你的 API 密钥。
    • Flags:
      • Protect variable: 如果勾选,该变量只在受保护的分支(通常是 main)或标签上运行的 Job 中可用。非常适合生产环境密钥。
      • Mask variable: 如果勾选,GitLab 会尝试在 Job 日志中隐藏该变量的值,防止意外泄露。强烈建议对敏感信息启用。
    • 点击 "Add variable"。
  2. .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_jobmain 分支运行时,$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.jsonyarn.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 之旅吧!


THE END