手把手教你配置 .gitlab-ci.yml:从入门到精通的终极指南
在现代软件开发生命周期中,持续集成(Continuous Integration)、持续交付(Continuous Delivery)和持续部署(Continuous Deployment)——统称为 CI/CD ——已经从一个“高大上”的概念转变为不可或缺的核心实践。它能够自动化软件的构建、测试和部署流程,极大地提高了开发效率、代码质量和发布速度。而 GitLab CI/CD 正是这个领域中的佼佼者,它与 GitLab 代码仓库无缝集成,仅需一个名为 .gitlab-ci.yml
的文件,便能开启强大的自动化之旅。
本文将是一篇极其详尽的指南,旨在带你从零开始,手把手地学习如何编写和配置 .gitlab-ci.yml
文件。无论你是初次接触 CI/CD 的新手,还是希望深化理解的开发者,都能在这里找到所需的知识。我们将覆盖从最基础的概念、核心关键字,到高级技巧、最佳实践,并最终通过一个完整的实战项目来巩固所学。
第一部分:CI/CD 与 GitLab CI/CD 的核心概念
在深入 .gitlab-ci.yml
的语法之前,我们必须理解其背后的几个核心概念。
1. 什么是 GitLab CI/CD?
GitLab CI/CD 是深度集成在 GitLab 中的一套工具,用于实现软件开发流程的自动化。当你将代码推送到 GitLab 仓库时,GitLab 会自动查找项目根目录下的 .gitlab-ci.yml
文件,并根据该文件的定义,触发一系列自动化的任务。
2. 关键组件:Pipeline, Stage, Job, Runner
理解这四个概念是掌握 GitLab CI/CD 的基石。
-
Pipeline(流水线): 这是 CI/CD 的最高层级概念。一个 Pipeline 代表了一次完整的构建、测试、部署流程,通常在一次代码提交(commit)或合并请求(merge request)时被触发。它由一个或多个 Stage 组成。
-
Stage(阶段): Stage 是对 Job 的逻辑分组,它定义了 Job 的执行顺序。同一 Stage 内的 Job 会并行执行(如果资源允许),而所有的 Stage 则会按顺序执行。例如,一个典型的 Pipeline 可能包含
build
、test
、deploy
三个 Stage。只有当build
Stage 中的所有 Job 都成功完成后,test
Stage 才会开始。 -
Job(作业): Job 是 GitLab CI/CD 中最小的可执行单元,它定义了具体要“做什么”。例如,“编译代码”、“运行单元测试”、“构建 Docker 镜像”等都是 Job。每个 Job 都在一个独立的、隔离的环境中运行。
-
Runner(执行器): 如果说
.gitlab-ci.yml
是自动化流程的“蓝图”,那么 Runner 就是执行这张蓝图的“工人”。Runner 是一个独立的应用程序,它会从 GitLab 服务器获取待处理的 Job,并在其所在的环境中执行 Job 中定义的脚本。Runner 可以安装在任何机器上(物理机、虚拟机或容器内),并可以配置为项目专用(Specific)、团队共享(Group)或全局共享(Shared)。
现在,我们带着这些概念,正式进入 .gitlab-ci.yml
的世界。
第二部分:.gitlab-ci.yml
语法核心关键字详解
.gitlab-ci.yml
文件使用 YAML 格式编写,语法简洁清晰。下面我们将逐一解析最常用和最重要的关键字。
1. image
:定义执行环境
几乎所有的 Job 都需要在特定的环境中运行,比如一个包含 Node.js 的环境或一个包含 Java JDK 的环境。image
关键字允许你指定一个 Docker 镜像作为 Job 的基础运行环境。
-
全局定义:
“`yaml
# 所有 Job 默认都会使用这个 Node.js 18 的镜像
image: node:18-alpine… jobs
“`
-
Job 内部定义(覆盖全局):
“`yaml
image: node:18-alpinebuild-job:
stage: build
script:
– npm installdeploy-job:
stage: deploy
# 这个 Job 使用不同的镜像,比如一个包含 AWS CLI 的镜像
image: amazon/aws-cli
script:
– aws s3 sync ./build s3://my-bucket
“`
2. stages
:定义流水线阶段
这个关键字在文件的顶层定义了所有可用的 Stage 及其执行顺序。如果你不定义 stages
,GitLab 会默认使用 build
, test
, deploy
这三个阶段。
yaml
stages:
- install_dependencies
- quality_check
- build
- test
- deploy
上面的定义意味着,所有属于 install_dependencies
阶段的 Job 会首先执行,成功后,quality_check
阶段的 Job 开始执行,以此类推。
3. job_name
和 script
:定义作业和执行脚本
任何不属于预定义关键字的顶层键,都会被 GitLab 视为一个 Job 的名称。每个 Job 至少需要包含一个 script
关键字。
“`yaml
“build-app” 就是 Job 的名称
build-app:
stage: build # 指定该 Job 属于哪个 Stage
script:
– echo “开始编译应用…”
– mkdir build
– echo “构建产物” > build/info.txt
– echo “编译完成。”
``
script` 块中可以包含一个或多个 shell 命令,它们会由 Runner 依次执行。
4. before_script
和 after_script
:通用脚本钩子
before_script
:在每个 Job 的script
执行之前运行的命令。通常用于准备工作,如安装依赖、配置环境等。after_script
:在每个 Job 的script
执行之后运行的命令,无论 Job 成功还是失败都会执行。通常用于清理工作,如删除临时文件、注销登录等。
它们可以被定义在全局,也可以在 Job 内部定义以覆盖全局设置。
“`yaml
default:
before_script:
– echo “全局 before_script:正在准备环境…”
test-job:
stage: test
before_script:
– echo “Job 内部 before_script:正在安装测试依赖…” # 这个会覆盖全局的
script:
– echo “正在运行测试…”
after_script:
– echo “测试完成,正在清理…”
“`
5. variables
:定义变量
变量是 CI/CD 中非常重要的一部分,可以用来存储配置信息、密钥等。
“`yaml
variables:
# 全局变量
NODE_ENV: “production”
DATABASE_URL: “postgres://user:password@postgres:5432/db”
build-job:
stage: build
variables:
# Job 局部变量,会覆盖全局同名变量
NODE_ENV: “development”
script:
# 在脚本中可以直接使用 $VARIABLE_NAME 来引用变量
– echo “当前环境是: $NODE_ENV”
``
CI_COMMIT_REF_NAME
除了自定义变量,GitLab 还提供了大量预定义变量,如(当前分支或 tag 名称),
CI_PROJECT_DIR` (项目在 Runner 中的路径) 等,它们非常有用。
6. cache
:缓存依赖项
每次 Job 运行时,Runner 都会创建一个全新的环境。这意味着像 node_modules
或 maven
仓库这样的依赖项每次都需要重新下载,非常耗时。cache
关键字就是为了解决这个问题。
“`yaml
cache:
# key 用来标识缓存的唯一性。这里使用 commit SHA,表示不同分支可以使用同一份缓存
# 如果想让每个分支有独立的缓存,可以使用 $CI_COMMIT_REF_SLUG
key: “$CI_COMMIT_REF_SLUG”
# paths 定义了需要缓存的目录或文件
paths:
– node_modules/
– .npm/
install-deps:
stage: install_dependencies
script:
– npm install –cache .npm –prefer-offline
``
cache` 的目标是加速后续 Job 的执行,它不保证在不同 Pipeline 之间传递。
7. artifacts
:传递产物
当一个 Job 需要将它生成的文件(如编译后的二进制文件、测试报告、构建的网站文件)传递给后续 Stage 的 Job 时,就需要使用 artifacts
。
“`yaml
build-job:
stage: build
script:
– npm run build # 假设这个命令会在 ./dist 目录下生成静态文件
artifacts:
# paths 定义了要保存为产物的文件或目录
paths:
– dist/
# expire_in 设置产物的过期时间
expire_in: 1 week
deploy-job:
stage: deploy
script:
# 在这个 Job 中,dist/ 目录会自动被恢复,可以直接使用
– echo “正在部署 dist 目录下的文件…”
– ls -l dist/
``
cache
**vs
artifacts的区别**:
cache
* **目的**:用于加速 Job(在同一 Pipeline 或不同 Pipeline 的 Job 之间共享),而
artifacts用于在同一 Pipeline 的不同 Stage 的 Job 之间传递数据。
cache
* **上传时机**:在 Job 成功后上传,
artifacts也是。
cache
* **下载时机**:在 Job 开始时下载,
artifacts也是。
artifacts
* **可见性**:可以在 GitLab UI 上直接下载,
cache` 则不能。
8. rules
:控制 Job 的执行条件
这是控制 Job 何时运行的最强大、最灵活的方式。rules
是一个规则列表,GitLab 会从上到下匹配,一旦匹配成功,就会根据该规则决定是否执行 Job,并停止后续匹配。
“`yaml
deploy-to-prod:
stage: deploy
script:
– echo “部署到生产环境…”
rules:
# 规则一:当提交到 main 分支时,执行此 Job
– if: ‘$CI_COMMIT_BRANCH == “main”‘
when: on_success # on_success 是默认值,可以省略
# 规则二:当有新的 tag 被推送时,手动触发此 Job
- if: '$CI_COMMIT_TAG'
when: manual # 需要在 GitLab UI 上手动点击才能运行
# 规则三:当源分支是 "feature/" 开头,且目标分支是 "main" 的合并请求时,执行此 Job
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main" && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^feature\//'
# 规则四:只在 src/api/ 目录下的文件发生变化时,才运行
- if: '$CI_COMMIT_BRANCH == "develop"'
changes:
- src/api/**/*
- .gitlab-ci.yml
“`
rules
基本上取代了旧的 only/except
语法,因为它更加强大和直观。
9. needs
:创建有向无环图(DAG)流水线
传统上,流水线严格按照 stages
的顺序执行。但有时,一个在 deploy
阶段的 Job 可能只依赖 build
阶段的一个 Job,而不需要等待 test
阶段的所有 Job 完成。needs
关键字可以打破 stages
的限制,让 Job 在其依赖的 Job 完成后立即开始,从而大大缩短 Pipeline 的总时间。
“`yaml
stages:
– build
– test
– deploy
build-a:
stage: build
script: build a
build-b:
stage: build
script: build b
test-a:
stage: test
script: test a
needs: [“build-a”] # test-a 只依赖 build-a
test-b:
stage: test
script: test b
needs: [“build-b”] # test-b 只依赖 build-b
deploy-all:
stage: deploy
script: deploy all
needs: [“test-a”, “test-b”] # deploy-all 依赖 test-a 和 test-b
``
test-a
在这个例子中,和
test-b可以并行执行,一旦各自的
buildJob 完成,它们就会立即开始,而不需要等待整个
build` 阶段完成。
第三部分:实战演练:为一个 Node.js 项目配置完整的 CI/CD
现在,让我们把所有学到的知识融会贯通,为一个真实(虽然简单)的 Node.js Web 应用配置一个完整的 .gitlab-ci.yml
文件。
项目背景:
* 一个简单的 Express.js 应用。
* 使用 npm
进行包管理。
* 有代码风格检查 (lint
)。
* 有单元测试 (test
)。
* 需要构建成一个 Docker 镜像并推送到 GitLab Container Registry。
* 最终部署到预发布环境(仅在 develop
分支)和生产环境(仅在 main
分支,且需要手动触发)。
.gitlab-ci.yml
文件内容:
“`yaml
1. 全局配置
———————————————-
使用最新的 Node.js长期支持版作为默认镜像
default:
image: node:18-lts-alpine
定义流水线的各个阶段和顺序
stages:
– install
– validate
– build
– push
– deploy
全局缓存配置,缓存 node_modules 目录以加速流程
使用分支名作为 key,确保每个分支有独立的缓存
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
– node_modules/
2. 作业定义
———————————————-
— INSTALL STAGE —
install_dependencies:
stage: install
script:
– echo “Installing dependencies…”
– npm install
# 定义这个作业的产物是 node_modules,后续作业可以直接使用
# 注意:这里用 artifacts 而不是仅用 cache,是因为我们希望确保后续作业拿到的是
# 本次 install 确切安装的版本,而不是可能过时的缓存。
artifacts:
paths:
– node_modules/
— VALIDATE STAGE —
lint_code:
stage: validate
# 依赖 install_dependencies 作业,它完成后此作业才能开始
needs: [“install_dependencies”]
script:
– echo “Linting code…”
– npm run lint
run_tests:
stage: validate
needs: [“install_dependencies”]
script:
– echo “Running tests…”
– npm run test
# 定义测试覆盖率报告,GitLab 可以解析并展示
coverage: /All files\s|\s([\d.]+)/
— BUILD STAGE —
build_docker_image:
stage: build
# 构建 Docker 镜像需要 Docker 环境,所以这里覆盖了默认 image
# “dind” (Docker-in-Docker) 模式
image: docker:20.10.16
services:
– docker:20.10.16-dind
# 需要依赖前面的所有验证作业
needs: [“lint_code”, “run_tests”]
variables:
# 定义镜像名称和标签
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
# 关闭 TLS 验证,因为 dind service 是在内部网络
DOCKER_TLS_CERTDIR: “”
before_script:
# 登录到 GitLab 自带的容器镜像仓库
# CI_REGISTRY_USER 和 CI_JOB_TOKEN 都是 GitLab 提供的预定义变量
– docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY
script:
– echo “Building Docker image: $IMAGE_TAG”
– docker build -t $IMAGE_TAG .
– echo “Image built successfully.”
# 我们不在这里推送,将构建和推送分离是好习惯
# 但我们需要将镜像保存为产物,传递给 push 作业
artifacts:
paths:
– app.tar # 假设 docker build 后用 docker save 保存了镜像
# 这里的脚本需要调整为:
# – docker build -t $IMAGE_TAG .
# – docker save -o app.tar $IMAGE_TAG
— PUSH STAGE —
由于 Docker 镜像通常很大,直接通过 artifacts 传递并不高效。
一个更常见的做法是在 build 阶段直接 push 一个带 commit SHA 的标签,
然后在 deploy 阶段引用这个标签。这里为了演示,我们简化一下,
将 build 和 push 合并。
build_and_push_image:
stage: build # 我们将它放在 build 阶段
image: docker:20.10.16
services:
– docker:20.10.16-dind
needs: [“lint_code”, “run_tests”]
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA # 使用 commit SHA 作为唯一标签
DOCKER_TLS_CERTDIR: “”
before_script:
– docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY
script:
– echo “Building and pushing Docker image…”
– docker build -t $IMAGE_TAG .
– docker push $IMAGE_TAG
rules:
# 只有在 develop 或 main 分支才构建镜像
– if: ‘$CI_COMMIT_BRANCH == “develop” || $CI_COMMIT_BRANCH == “main”‘
— DEPLOY STAGE —
deploy_staging:
stage: deploy
image: curlimages/curl:7.83.1 # 假设我们用一个简单的 curl 来模拟部署
needs: [“build_and_push_image”]
script:
– echo “Deploying to Staging environment…”
– echo “Image to deploy: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA”
# 实际部署脚本,例如:
# – ssh user@staging-server “docker pull $IMAGE_TAG && docker run …”
environment:
name: staging
url: http://staging.example.com
rules:
# 仅在 develop 分支的提交上自动部署到 staging
– if: ‘$CI_COMMIT_BRANCH == “develop”‘
when: on_success
deploy_production:
stage: deploy
image: curlimages/curl:7.83.1
needs: [“build_and_push_image”]
script:
– echo “Deploying to Production environment…”
– echo “Image to deploy: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA”
# 实际部署脚本
environment:
name: production
url: http://www.example.com
rules:
# 仅在 main 分支的提交上,并且需要手动触发
– if: ‘$CI_COMMIT_BRANCH == “main”‘
when: manual
“`
第四部分:最佳实践与高级技巧
-
保持 DRY (Don’t Repeat Yourself): 使用
!reference
标签或extends
关键字来重用配置,避免大段重复的 YAML。
“`yaml
.base_job_template:
image: node:18
tags:
– docker-runnertest_job:
extends: .base_job_template
script:
– npm test
“` -
使用 GitLab UI 的 CI Linter: 在提交
.gitlab-ci.yml
之前,总是使用 GitLab 自带的 CI Lint 工具(在CI/CD -> Editor
或CI/CD -> Pipelines
页面)来检查语法错误。 -
保护你的密钥: 绝对不要将密码、API Key 等敏感信息硬编码在
.gitlab-ci.yml
文件中。应该使用 GitLab 项目设置中的Settings -> CI/CD -> Variables
来存储它们。将变量标记为 “Protected” 可以使其只在受保护的分支和标签上可用,标记为 “Masked” 可以防止它在 Job 日志中被明文打印。 -
优化流水线性能:
- 并行化: 将耗时且无依赖关系的任务放在同一 Stage,让它们并行执行。
- 使用 DAG (
needs
): 精准定义依赖关系,打破 Stage 的僵硬顺序。 - 高效缓存: 精心设计
cache:key
,最大化缓存命中率。 - 使用小型 Docker 镜像:
alpine
版本的镜像通常比默认的要小得多,可以减少镜像拉取时间。
-
将复杂脚本外部化: 如果
script
部分变得非常复杂,最好将其写成一个独立的 shell 脚本文件(如scripts/deploy.sh
),然后在.gitlab-ci.yml
中调用它。这让 CI 配置更清晰,也让脚本更易于本地测试和维护。
总结
.gitlab-ci.yml
是开启自动化大门的钥匙。它以声明式的方式定义了从代码提交到最终部署的每一个环节。虽然初看起来关键字繁多,但只要你理解了 Pipeline、Stage、Job 和 Runner 这几个核心概念,并掌握了 image
, script
, stages
, cache
, artifacts
, rules
等关键指令,你就已经具备了构建强大、高效 CI/CD 流水线的能力。
本文从基础概念讲起,深入剖析了核心语法,并通过一个详尽的实战案例展示了如何将理论应用于实践。更重要的是,我们探讨了如何优化流水线并遵循最佳实践。
现在,你已经准备好了。打开你的 GitLab 项目,创建那个神奇的 .gitlab-ci.yml
文件,开始你的自动化之旅吧。记住,持续学习和不断尝试是精通任何技术的唯一途径。 GitLab 的官方文档永远是你最好的朋友。祝你编码愉快,流水线畅通无阻!