Gitlab CI:npm 不喜欢缓存的 node_modules

Tim*_*Sch 9 npm gitlab gitlab-ci npm-install

互联网上充斥着关于 Gitlab 不缓存的抱怨,但在我看来,Gitlab CI 确实缓存正确。问题是,无论如何,npm 似乎都会再次安装所有内容。

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - vendor/
    - bootstrap/
    - node_modules/

build-dependencies:
  image: ...
  stage: build
  script:
  - cp .env.gitlab-testing .env
  - composer install --no-progress --no-interaction
  - php artisan key:generate
  - npm install
  - npm run prod
  - npm run prod
  artifacts:
    paths:
    - vendor/
    - bootstrap/
    - node_modules/
    - .env
    - public/mix-manifest.json
  tags:
  - docker
Run Code Online (Sandbox Code Playgroud)

这是我的 gitlab-ci.yml 文件(好吧..相关部分)。虽然使用了缓存的 Composer 依赖项,但没有使用 node_modules。出于绝望,我什至将所有内容都添加到缓存工件中。

cod*_*365 85

更新答案(2023 年 12 月 2 日GitLab@^15.12 & >13

就像收到的评论一样,在原始答案中使用工件并不理想,但却是最干净、可靠的方法。现在,GitLab 的文档已经围绕使用进行了更新cache,并且还进行了扩展以支持每个作业的多个缓存键(不幸的是最多 4 个),有一种更好的方法来处理node_modules跨管道。

实施的基本原理是基于了解 GitLab 的怪癖及其npm工作原理。这些是基本原理:

  1. NPM 建议在 CI/CD 环境中运行时使用 代替npm cinpm install仅供参考,这需要存在package-lock.json,它用于确保在 CI 环境中运行时自动更新 0 个包(npm i默认情况下不会每次都创建相同的确定性构建,例如在作业重新运行时)。

  2. npm cinode_modules在重新安装 中列出的所有软件包之前,故意删除整个第一个package-lock.json。因此,最好只将 GitLab 配置为运行npm ci一次,并确保将结果node_modules传递给其他作业。

  3. NPM 有自己的缓存,用于存储~/.npm/离线构建和整体速度。您可以使用该选项指定不同的缓存位置--cache <dir>(您将需要这个)。(@Amityo 答案的变体)

  4. GitLab无法缓存存储库之外的任何目录!这意味着默认缓存目录~/.npm无法被缓存。

  5. GitLab 的全局缓存配置默认应用于每个作业。如果作业不需要全局缓存文件,则需要显式覆盖缓存配置。使用 YAML 锚点,可以复制和修改全局缓存配置。

  6. 要运行额外的安装npxnpm run <script>不重新运行安装,您应该node_modules/跨管道缓存文件夹。

  7. GitLab 的期望是用户使用该cache功能来处理依赖项,并且仅用于artifacts动态生成的构建结果。这个答案现在比以前更好地支持了这个愿望。GitLab.com 上有一个限制,即工件应小于最大工件大小或 1GB(压缩后)。并且工件使用您的存储使用配额

  8. needs或指令的使用dependencies将影响上一个作业中的工件是否会在下一个作业中自动下载(或删除)。

  9. GitLab 缓存可以监视文件的哈希值并将其用作密钥,因此缓存可能仅在更新时才会更新package-lock.json。您可以使用package.json,但会使确定性构建无效,因为当次要版本或补丁可用时它不会更新。

  10. 如果您有一个单一存储库并且有超过 2 个单独的包,则在作业期间您将达到 4 个缓存条目限制install。您不会有理想的设置,但您可以将一些缓存定义组合在一起。还值得注意的是,GitLabcache.key.files最多支持 2 个文件用于密钥哈希,因此您可能需要使用另一种方法来确定有用的密钥。一种可能的解决方案是使用非文件相关的密钥并缓存node_modules/该密钥下的所有文件夹。这样,该作业只有 2 个缓存条目install,每个后续作业只有 1 个缓存条目。

  11. 不幸的是,GitLab 的缓存列表将索引值添加到它使用的缓存键之前(这非常不直观!),这可能会影响您的缓存在作业之间是否匹配。感谢@deanharber 提出这个问题。截至 2023 年 12 月 3 日,此示例已更新以反映作业中的新订单install。全局缓存条目必须位于安装作业的第一个位置,因为全局缓存键实际上是0_[CACHE_KEY_FOR_NODE_MODULES],而 npm 缓存位于第二个位置,因为它的键为1_[CACHE_KEY_FOR_NPM_TARBALLS]

解决方案

  • .pre使用整个存储库中缓存的下载包(tar.gz)运行单个安装作业作为阶段。
  • 将所有node_modules/文件夹缓存到以下作业以执行该管道。不允许除安装之外的任何作业上传缓存(减少管道运行时间并防止意外后果)
  • 仅在需要时通过工件将目录传递build/给其他作业
# .gitlab-ci.yml

stages:
  - build
  - test
  - deploy


# global cache settings for all jobs
# Ensure compatibility with the install job
# goal: the install job loads the cache and
# all other jobs can only use it
cache:
    # most npm libraries will only have 1 entry for the base project deps
    - &global_cache_node_mods
      key:
          files:
              - package-lock.json
      paths:
          - node_modules/
      policy: pull  # prevent subsequent jobs from modifying cache

#   # ATTN mono-repo users: with only additional node_modules, 
#   # add up to 2 additional cache entries. 
#   # See limitations in #10. 
#   - key:
#         files:
#             - core/pkg1/package-lock.json
#     paths:
#         - core/pkg1/node_modules/
#     policy: pull # prevent jobs from modifying cache


install:
  image: ...
  stage: .pre   # always first, no matter if it is listed in stages
  cache:
    # Mimic &global_cache_node_mods config but override policy
    # to allow this job to update the cache at the end of the job
    # and only update if it was a successful job (#5)
    - <<: *global_cache_node_mods
       when: on_success
       policy: pull-push

#   # ATTN Monorepo Users: add additional key entries from 
#   # the global cache and override the policy as above but
#   # realize the limitations (read #10).
#   - key:
#      files:
#        - core/pkg1/package-lock.json
#      paths:
#        - core/client/node_modules/
#      when: on_success
#      policy: pull-push

    # store npm cache for all branches (stores download pkg.tar.gz's)
    # will not be necessary for any other job
    - key: ${CI_JOB_NAME}
       # must be inside $CI_PROJECT_DIR for gitlab-runner caching (#3)
       paths:
         - .npm/
       when: on_success
       policy: pull-push

# before_script:
#   - ...
  script:
    # define cache dir & use it npm!
    - npm ci --cache .npm --prefer-offline
#   # monorepo users: run secondary install actions
#   - npx lerna bootstrap -- --cache .npm/ --prefer-offline


build:
  stage: build
  # global cache settings are inherited to grab `node_modules`
  script:
    - npm run build
  artifacts:
    paths:
      - dist/           # where ever your build results are stored


test:
  stage: test
  # global cache settings are inherited to grab `node_modules`
  needs:
    # install job is not "needed" unless it creates artifacts
    # install job also occurs in the previous stage `.pre` so it
    # is implicitly required since `when: on_success` is the default
    # for subsequent jobs in subsequent stages
    - job: build
      artifacts: true      # grabs built files
  # dependencies: could also be used instead of needs
  script:
    - npm test


deploy:
  stage: deploy
  when: on_success # only if previous stages' jobs all succeeded
  # override inherited cache settings since node_modules is not needed
  cache: {}
  needs:
    - job: build
      artifacts: true      # grabs dist/
  script:
    - npm publish
Run Code Online (Sandbox Code Playgroud)

GitLab 的建议可以在GitLab 文档npm中找到。


[已弃用] 原始答案(2021 年 10 月 27 日,GitLab<13.12

到目前为止我看到的所有答案都只给出了一半答案,但实际上并没有完全完成缓存 IMO 的任务。

为了使用 npm 和 GitLab 进行完全缓存,您必须注意以下事项:

  1. 参见上面#1

  2. npm cinode_modules在重新安装 中列出的所有软件包之前,故意删除整个第一个package-lock.json。因此,配置 GitLab 来缓存node_modules构建作业之间的目录是没有用的。重点是确保没有准备挂钩或node_modules之前运行的任何其他内容被修改。IMO,这对于 CI 环境来说并不是真正有效,但您无法更改它并维护完全确定性的构建。

  3. 参见上面#3-#4

  4. 如果您有多个阶段,则每个作业都会下载全局缓存。这可能不是您想要的!

  5. 要运行其他npx命令而不重新运行安装,您应该将该node_modules/文件夹作为工件传递给其他作业。

[已弃用] 解决方案

  • .pre使用整个存储库中缓存的下载包(tar.gz)运行单个安装作业作为阶段。
  • 仅在需要时将 node_modules 和构建目录传递给其他作业

stages:
  - build
  - test
  - deploy

install:
  image: ...
  stage: .pre         # always first, no matter if it is listed in stages
  cache:
    key: NPM_DOWNLOAD_CACHE  # a single-key-4-all-branches for install jobs
    paths:
      - .npm/
  before_script:
    - cp .env.gitlab-testing .env
    - composer install --no-progress --no-interaction
    - php artisan key:generate
  script:
    # define cache dir & use it npm!
    - npm ci --cache .npm --prefer-offline
  artifacts:
    paths:
    - vendor/
    - bootstrap/
    - node_modules/
    - .env
    - public/mix-manifest.json

build:
  stage: build
  needs:
    - job: install         
      artifacts: true       # true by default, grabs `node_modules`
  script:
    - npm run build
  artifacts:
    paths:
      - dist/               # whereever your build results are stored

test:
  stage: test
  needs:
    - job: install
      artifacts: true      # grabs node_modules
    - job: build
      artifacts: true      # grabs built files
  script:
    - npm test

deploy:
  stage: deploy
  needs:
      # does not need node_modules so don't state install as a need
    - job: build
      artifacts: true      # grabs dist/
    - job: test            # must succeed
      artifacts: false     # not needed
  script:
    - npm publish
Run Code Online (Sandbox Code Playgroud)

  • 在我的设置中,安装作业中定义的缓存键的顺序必须翻转。后续作业尝试从安装作业创建“1_[CACHE_KEY_FOR_NODE_MODULES]”的“0_[CACHE_KEY_FOR_NODE_MODULES]”检索缓存 (3认同)
  • @Sagivb.g &amp; \@boy,答案现已更新为使用缓存!谢谢你的意见。 (2认同)

dim*_*sus 10

实际上它应该可以工作,您的缓存是全局设置的,您的键是指当前分支${CI_COMMIT_REF_SLUG}...

这是我的构建,它似乎在阶段之间缓存 node_modules。

image: node:latest

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
  - node_modules/
  - .next/

stages:
  - install
  - test
  - build
  - deploy

install_dependencies:
  stage: install
  script:
    - npm install

test:
  stage: test
  script:
    - npm run test

build:
  stage: build
  script:
    - npm run build

Run Code Online (Sandbox Code Playgroud)


Fra*_*ard 5

我遇到了同样的问题,对我来说问题出在缓存设置上,默认情况下缓存不保留未版本控制的 git 文件,并且由于我们不在 git 中存储 node_modules,因此根本没有缓存 npm 文件。所以我所要做的就是插入一行“untracked: true”,如下所示

  cache:
  untracked: true
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - vendor/
    - bootstrap/
    - node_modules/
Run Code Online (Sandbox Code Playgroud)

现在 npm 更快了,虽然它仍然需要检查事情是否发生了变化,对我来说这仍然需要几分钟,所以我考虑让一个特定的工作来执行 npm 安装,但它已经加快了很多。


Ami*_*tyo 2

默认缓存路径是~/.npm

设置 npm 缓存目录:

npm config set cache <path> --global
Run Code Online (Sandbox Code Playgroud)

浏览此处获取更多信息

  • 虽然这完全不是OP问题的答案,但它仍然对我有帮助。对于npm-noobs:`node_modules`是`npm install`的成品。也许像 python `virtualenv` ?我发现我的解决方案应该将 npm-cache-dir 放入 CI 缓存中。不是“node_modules”。 (5认同)