手把手教你配置 .gitlab-ci.yml 文件 – wiki基地


手把手教你配置 .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 可能包含 buildtestdeploy 三个 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-alpine

    build-job:
    stage: build
    script:
    – npm install

    deploy-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_namescript:定义作业和执行脚本

任何不属于预定义关键字的顶层键,都会被 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_scriptafter_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”
``
除了自定义变量,GitLab 还提供了大量预定义变量,如
CI_COMMIT_REF_NAME(当前分支或 tag 名称),CI_PROJECT_DIR` (项目在 Runner 中的路径) 等,它们非常有用。

6. cache:缓存依赖项

每次 Job 运行时,Runner 都会创建一个全新的环境。这意味着像 node_modulesmaven 仓库这样的依赖项每次都需要重新下载,非常耗时。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/
``
**
cachevsartifacts的区别**:
* **目的**:
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-atest-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
“`

第四部分:最佳实践与高级技巧

  1. 保持 DRY (Don’t Repeat Yourself): 使用 !reference 标签或 extends 关键字来重用配置,避免大段重复的 YAML。
    “`yaml
    .base_job_template:
    image: node:18
    tags:
    – docker-runner

    test_job:
    extends: .base_job_template
    script:
    – npm test
    “`

  2. 使用 GitLab UI 的 CI Linter: 在提交 .gitlab-ci.yml 之前,总是使用 GitLab 自带的 CI Lint 工具(在 CI/CD -> EditorCI/CD -> Pipelines 页面)来检查语法错误。

  3. 保护你的密钥: 绝对不要将密码、API Key 等敏感信息硬编码在 .gitlab-ci.yml 文件中。应该使用 GitLab 项目设置中的 Settings -> CI/CD -> Variables 来存储它们。将变量标记为 “Protected” 可以使其只在受保护的分支和标签上可用,标记为 “Masked” 可以防止它在 Job 日志中被明文打印。

  4. 优化流水线性能:

    • 并行化: 将耗时且无依赖关系的任务放在同一 Stage,让它们并行执行。
    • 使用 DAG (needs): 精准定义依赖关系,打破 Stage 的僵硬顺序。
    • 高效缓存: 精心设计 cache:key,最大化缓存命中率。
    • 使用小型 Docker 镜像: alpine 版本的镜像通常比默认的要小得多,可以减少镜像拉取时间。
  5. 将复杂脚本外部化: 如果 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 的官方文档永远是你最好的朋友。祝你编码愉快,流水线畅通无阻!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部