GitLab CI:通过示例解释缓存和 Artifacts

2025-06-08

GitLab CI:通过示例解释缓存和 Artifacts

大家好,DEV 社区!我从事软件测试领域已经八年多了。除了 Web 服务测试,我还负责维护我们团队 GitLab 中的 CI/CD 流水线。

让我们讨论一下 GitLab 缓存和工件之间的区别。我将展示如何以实用的方式为 Node.js 应用配置流水线,以实现良好的性能和资源利用率。

有三件事你可以一直关注:火在燃烧,水在下降,以及下次提交后构建正在通过。没有人愿意等待 CI 完成太久,最好设置好所有调整,以避免在提交和构建状态之间长时间等待。缓存和工件可以帮你解决!它们有助于大幅减少运行流水线所需的时间。

当人们必须在缓存和工件之间做出选择时,他们会感到困惑。GitLab 的文档很清晰,但Node.js 应用的缓存示例和Node.js 的Pipeline模板却互相矛盾。

让我们看看 GitLab 术语中“管道”的含义。管道一系列阶段组成,每个阶段可以包含一个或多个作业。作业在分布式运行器集群中运行。启动管道时,会有一个随机的、拥有可用资源的运行器执行所需的作业。GitLab-runner 是可以运行作业的代理。为了简单起见,我们假设 Docker 是所有运行器的执行器。

每个作业都从零开始,并且不知道前一个作业的结果。如果不使用缓存和工件,运行器在安装项目依赖项时必须访问互联网或本地注册表并下载必要的软件包。

什么是缓存?

它是一组文件,作业可以在运行前下载并在执行后上传。默认情况下,缓存存储在 GitLab Runner 的安装位置。如果配置了分布式缓存,则使用 S3 作为存储。 假设您第一次使用本地缓存运行 Pipeline。该作业将找不到缓存,但会在执行后将缓存上传到 runner01。第二个作业将在 runner02 上执行,它也找不到缓存,因此即使没有缓存也能正常工作。结果将保存到 runner02。第三个作业 Lint 将在 runner01 上找到缓存并使用它(拉取)。执行完成后,它会将缓存上传回(推送)。
GitLab 缓存

什么是文物?

工件是作业执行后存储在 GitLab 服务器上的文件。后续作业将在脚本执行之前下载工件。 构建作业会创建一个 DEF 工件并将其保存在服务器上。第二个作业 Test 会在运行命令之前从服务器下载工件。第三个作业 Lint 也会类似地从服务器下载工件。
GitLab 工件

为了进行比较,在第一个作业中创建了工件,并在后续作业中使用。缓存在每个作业中创建。

考虑 GitLab 推荐的 Node.js CI 模板示例:



image: node:latest # (1)

# This folder is cached between builds
cache:
  paths:
    - node_modules/ # (2)

test_async:
  script:
    - npm install # (3)
    - node ./specs/start.js ./specs/async.spec.js

test_db:
  script:
    - npm install # (4)
    - node ./specs/start.js ./specs/db-postgres.spec.js


Enter fullscreen mode Exit fullscreen mode

第 1 行指定了 Docker 镜像,该镜像将在所有作业中使用。第一个问题是latest标签。该标签会破坏构建的可重复性。它始终指向 Node.js 的最新版本。如果 GitLab 运行器缓存了 Docker 镜像,则第一次运行将下载该镜像,并且所有后续运行都将使用本地可用的镜像。因此,即使节点从版本 XX 升级到 YY,我们的流水线也对此一无所知。因此,我建议指定镜像的版本。不仅仅是发布分支 ( node:14),还要指定完整的版本标签 ( node:14.2.5)。

第 2 行与第 3 行和第 4 行相关。node_modules指定了用于缓存的目录,每个作业都会执行软件包的安装(npm install)。由于软件包在目录下可用,因此安装速度应该更快node_modules。由于没有为缓存指定键,因此default将使用 一词作为键。这意味着缓存将是永久的,并在所有 git 分支之间共享。

提醒一下,主要目标是保持管道的可重复性今天启动的管道一年后应该也能以同样的方式运行

NPM 将依赖项存储在两个文件中—— package.jsonpackage-lock.json。如果使用package.json,构建将无法重现。运行时,npm install包管理器会将依赖关系不严格的版本放入最新的次要版本中。为了修复依赖关系树,我们使用package-lock.json文件。所有包的版本都在此处严格指定。

但还有一个问题,npm install重写了 package-lock.json,这不是我们期望的。因此,我们使用了特殊的命令npm ci

  • 删除 node_modules 目录;
  • 从 package-lock.json 安装包。

node_modules如果每次都会被删除怎么办?我们可以使用环境变量指定NPM缓存npm_config_cache

最后,配置没有明确指定作业的执行阶段。默认情况下,作业在测试阶段内运行。结果发现两个作业将并行运行。太棒了!让我们添加作业阶段并修复我们发现的所有问题。

第一次迭代后我们得到了:



image: node: 16.3.0 # (1)

stages:
  - test

variables:
  npm_config_cache: "$CI_PROJECT_DIR/.npm" (5)

# This folder is cached between builds
cache:
  key:
    files:
      - package-lock.json (6)
  paths:
    - .npm # (2)

test_async:
  stage: test
  script:
    - npm ci # (3)
    - node ./specs/start.js ./specs/async.spec.js

test_db:
  stage: test
  script:
    - npm ci # (4)
    - node ./specs/start.js ./specs/db-postgres.spec.js


Enter fullscreen mode Exit fullscreen mode

我们改进了 Pipeline,使其可复现。但仍有两个缺点。首先,缓存是共享的。每个作业都会拉取缓存,并在执行完后推送新版本。在 Pipeline 内部,最好只更新一次缓存。其次,每个作业都会安装依赖包,浪费时间。

为了解决第一个问题,我们明确描述了缓存管理。让我们添加一个“隐藏”作业,并仅启用拉取策略(下载缓存但不更新):



# Define a hidden job to be used with extends
# Better than default to avoid activating cache for all jobs
.dependencies_cache:
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - .npm
    policy: pull


Enter fullscreen mode Exit fullscreen mode

要连接缓存,您需要通过extends关键字继承作业。



...
extends: .dependencies_cache
...


Enter fullscreen mode Exit fullscreen mode

为了解决第二个问题,我们使用了 artifacts。让我们创建一个作业,用于归档包依赖项并将 artifactsnode_modules进一步传递。后续作业将从现场运行测试。



setup:
  stage: setup
  script:
    - npm ci
  extends: .dependencies_cache
  cache:
    policy: pull-push
  artifacts:
    expire_in: 1h
    paths:
      - node_modules


Enter fullscreen mode Exit fullscreen mode

我们安装 npm 依赖项并使用隐藏的 dependency_cache 作业中描述的缓存。然后,我们指定如何通过拉取-推送策略更新缓存。较短的生命周期(1 小时)有助于节省工件的空间。无需node_modules在 GitLab 服务器上长期保存工件。

更改后的完整配置:



image: node: 16.3.0 # (1)

stages:
  - setup
  - test

variables:
  npm_config_cache: "$CI_PROJECT_DIR/.npm" (5)

# Define a hidden job to be used with extends
# Better than default to avoid activating cache for all jobs
.dependencies_cache:
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - .npm
    policy: pull

setup:
  stage: setup
  script:
    - npm ci
  extends: .dependencies_cache
  cache:
    policy: pull-push
  artifacts:
    expire_in: 1h
    paths:
      - node_modules

test_async:
  stage: test
  script:
    - node ./specs/start.js ./specs/async.spec.js

test_db:
  stage: test
  script:
    - node ./specs/start.js ./specs/db-postgres.spec.js


Enter fullscreen mode Exit fullscreen mode

我们了解了缓存和工件之间的区别。我们构建了一个可复现的流水线,它工作方式可预测,并能高效利用资源。本文介绍了在 GitLab 中设置持续集成 (CI) 时的一些常见错误以及如何避免这些错误。
祝您构建顺利,流水线运行快速。欢迎您在评论区留言!

链接

鏂囩珷鏉ユ簮锛�https://dev.to/drakulavich/gitlab-ci-cache-and-artifacts-explained-by-example-2opi
PREV
亲爱的未来程序员
NEXT
2024 年技术面试必胜指南 🚀 1. 申请工作 2.1 筛选电话(首次面试) 2.2. 可选:人力资源面试 3. 技术面试 4. 可选:招聘经理面试 5. 文化契合度面试 6. 录用通知谈判