【问题标题】:How do I cache steps in GitHub actions?如何在 GitHub 操作中缓存步骤?
【发布时间】:2019-08-02 06:58:46
【问题描述】:

假设我有一个包含 2 个步骤的 GitHub 操作工作流。

  1. 下载并编译我的应用程序的依赖项。
  2. 编译并测试我的应用程序

我的依赖项很少更改,编译的依赖项可以安全地缓存,直到我下次更改指定其版本的锁定文件。

是否有一种方法可以保存第一步的结果,以便将来的工作流程可以跳过该步骤?

【问题讨论】:

  • 虽然 bitoiu 的回答是正确的,即今天的 GitHub Actions 中没有显式缓存功能,但在给定的工作流运行中,您确实会在工作流的各个步骤中获得隐式缓存。发生这种情况是因为 GitHub 卷在每个步骤中都会将您的存储库安装到 Docker 中。您在一个步骤中所做的任何更改都会保留在磁盘上,以供该工作流运行的后续步骤使用。当然,这不会像您所要求的那样缓存跨运行的依赖项构建,但其他人可能会发现 some 缓存的能力很有用。我认为没有记录此功能。
  • 您可以做的另一件事是将构建依赖项缓存(例如,作为 tar)推送到 S3 / Minio / 等,就像 GitLab's distributed caching system 的工作方式一样。在 GitHub Actions 产品添加这样的功能之前,您现在必须手动执行与 S3 或类似的请求。这可以为您节省多少时间(如果有的话)当然取决于您的依赖项的大小以及 GitHub Actions 从 S3 提取的速度。我自己还没有测试过。
  • "GitHub 将删除超过 7 天未访问的所有缓存条目。"引用自 GitHub 的文档:docs.github.com/en/actions/guides/…

标签: github github-actions


【解决方案1】:

现在通过cache action 原生支持缓存。它适用于存储库中的作业和工作流。另见:https://help.github.com/en/actions/automating-your-workflow-with-github-actions/caching-dependencies-to-speed-up-workflows

考虑following example

name: GitHub Actions Workflow with NPM cache

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: Cache NPM dependencies
      uses: actions/cache@v1
      with:
        path: ~/.npm
        key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.OS }}-npm-cache-

    - name: Install NPM dependencies
      run: npm install

其中cache操作的pathkey参数用于标识缓存。

可选的restore-keys 用于可能回退到部分匹配(即,如果package-lock.json 更改将使用以前的缓存)。

当使用 restore-keys 回退并且有多个不同的缓存(例如,用于 JS 包和系统包)时,为键添加一些 id 前缀(在此示例中为 npm-cache)很有用。否则,一个缓存可能会退回到另一个不相关的缓存。同样,在使用矩阵构建时,操作系统前缀很有用,因此不同系统的缓存不会混淆。

您还可以使用@actions/cache 构建自己的可重用缓存逻辑,例如:


旧答案:

目前无法进行本机缓存,expected to be implemented by mid-November 2019

您可以按照GH Community board 的建议,使用工件(12)在作业(在 1 个工作流程内)之间移动目录。但是,doesn't work across workflows.

【讨论】:

  • 除非您在缓存步骤中包含 if: steps.cache-deps.outputs.cache-hit != 'true'id: cache-deps 之类的内容,否则此示例是否仍会在每次推送时运行 npm install 步骤?
  • 是的,它会,但它会从缓存中加载依赖项。
  • @thisismydesign 如果我仍然需要等待npm install 步骤,从缓存加载依赖项有什么好处?
  • 仅供那些不知道的人参考,这将通过 GitHub 远程发送和接收缓存。因此,如果您使用的是本地管理的 GitHub 运行器(顺便说一句,这很棒),那么您将承担上传和下载这些缓存的时间和成本。
  • @DannyFeliz 你可以选择缓存node_modules 但不推荐:github.com/actions/cache/blob/… 缓存~/.npm 至少可以节省你下载包的时间(也可以是其他的东西,我'不确定)。
【解决方案2】:

cache 操作只能缓存文件夹的内容。所以如果有这样一个文件夹,你可能会通过缓存它来赢得一些时间。

例如,如果您使用一些虚构的 package-installer(如 Python 的 pipvirtualenv,或 NodeJS 的 npm,或其他任何将其文件放入文件夹的东西),您可以通过以下方式赢得一些时间这样做:

    - uses: actions/cache@v2
      id: cache-packages  # give it a name for checking the cache hit-or-not
      with:
        path: ./packages/  # what we cache: the folder
        key: ${{ runner.os }}-packages-${{ hashFiles('**/packages*.txt') }}
        restore-keys: |
          ${{ runner.os }}-packages-
    - run: package-installer packages.txt
      if: steps.cache-packages.outputs.cache-hit != 'true'

那么这里最重要的是:

  1. 我们为这一步命名,cache-packages
  2. 后来,我们用这个名字进行条件执行:if,steps.cache-packages.outputs.cache-hit != 'true'
  3. 为缓存操作指定要缓存的文件夹的路径:./packages/
  4. 缓存键:取决于输入文件的哈希值。也就是说,如果任何packages.txt 文件发生更改,缓存将被重建。
  5. 第二步,包安装程序,只有在没有缓存的情况下才会运行

virtualenv的用户:如果你需要激活一些shell环境,你必须在每一步都做。像这样:

- run: . ./environment/activate && command

【讨论】:

    【解决方案3】:

    我的依赖项很少更改,编译的依赖项可以安全地缓存,直到我下次更改指定其版本的锁定文件。是否有一种方法可以保存第一步的结果,以便将来的工作流程可以跳过该步骤?

    第一步是:

    下载并编译我的应用程序的依赖项。

    GitHub Actions 本身不会为您执行此操作。我能给您的唯一建议是您遵守 Docker 最佳实践,以确保如果 Actions 确实使用 docker 缓存,您的图像可以重复使用而不是重建。见:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache

    在构建映像时,Docker 会逐步执行 Dockerfile 中的指令,并按照指定的顺序执行每个指令。在检查每条指令时,Docker 会在其缓存中查找可以重用的现有图像,而不是创建新的(重复的)图像。

    这也意味着 GitHub Actions 的底层系统可以/将利用 Docker 缓存。

    但是编译之类的东西,Docker 将无法使用缓存机制,所以如果这是你迫切需要的东西,我建议你好好考虑一下。另一种方法是从工件存储(Nexus、NPM、MavenCentral)下载编译/处理的文件以跳过该步骤。您确实必须权衡好处与您在此基础上添加到构建中的复杂性。

    【讨论】:

    • 感谢您的帮助。在 GitHub 获得此功能之前,我将继续使用 Circle 和 Travis。 :)
    • 你能指出 Travis 执行此操作的文档吗?
    • 这是一个 Dockerfile 的example,它利用 docker 缓存来处理依赖项。值得一提的是,只有在每次构建映像时,它都在同一台机器上运行(因此也是相同的 docker 缓存),才会利用缓存。我不确定所有操作是否共享相同的“docker 上下文”,但我认为这是一个主要因素。
    • 我刚刚通过运行我创建了 5 次的基于 Docker 的操作进行了尝试。每次,泊坞窗都是从头开始重新生成的。因此,它看起来不像使用相同的 docker 上下文(至少到目前为止)。我没有办法检查是否使用了同一个跑步者,所以也许情况也是如此。总的来说 - 到目前为止似乎不是一个非常可靠的方法
    • @AbdealiJK 如果您使用基于 Docker 的操作,您可能会发现我的回答很有用:stackoverflow.com/a/58752958/4095830
    【解决方案4】:
    【解决方案5】:

    如果您在工作流中使用 Docker,例如 @peterevans answered,GitHub 现在支持通过 cache 操作进行缓存,但它有其局限性。

    因此,您可能会发现有用的 this action 可以绕过 GitHub 的操作限制。

    免责声明:我在 GitHub 正式发布之前创建了支持缓存的操作,由于它的简单性和灵活性,我仍然使用它。

    【讨论】:

    • 是否有其他解决方案允许缓存层而不进行身份验证?
    【解决方案6】:

    我将总结两个选项:

    1. 缓存
    2. 码头工人

    缓存

    您可以在工作流程中添加一个命令来缓存目录。到达该步骤时,它将检查您指定的目录是否先前已保存。如果是这样,它会抓住它。如果没有,它不会。然后在进一步的步骤中,您编写检查以查看缓存的数据是否存在。例如,假设您正在编译一些很大且变化不大的依赖项。您可以在工作流程的开头添加一个缓存步骤,然后在目录内容不存在时添加一个步骤来构建目录内容。第一次运行时它不会找到文件,但随后它会找到文件,并且您的工作流程会运行得更快。

    在幕后,GitHub 正在将您的目录的 zip 文件上传到 github 自己的 AWS 存储。他们会清除一周以上或达到 2GB 限制的所有内容。

    这种技术的一些缺点是它只保存目录。因此,如果您安装到 /usr/bin 中,则必须缓存它!那会很尴尬。您应该改为安装到 $home/.local 并使用 echo set-env 将其添加到您的路径中。

    码头工人

    Docker 稍微复杂一点,这意味着您现在必须拥有一个 dockerhub 帐户并管理两件事。但它的威力更大。您将保存整台计算机,而不是只保存一个目录!你要做的是创建一个 Dockerfile,其中包含你所有的依赖项,比如 apt-get 和 python pip 行,甚至是长编译。然后,您将构建该 docker 映像并将其发布到 dockerhub 上。最后,您会将测试设置为在新的 docker 映像上运行,而不是在例如 ubuntu-latest 上运行。从现在开始,它不再安装依赖项,而是只下载图像。

    您可以通过将该 Dockerfile 存储在与项目相同的 GitHub 存储库中来进一步自动化此操作,然后编写一个带有步骤的作业,该步骤将下载最新的 docker 映像,如有必要仅重建更改的步骤,然后上传到 dockerhub。然后是一项“需要”该工作并使用该图像的工作。这样,您的工作流程将在需要时更新 docker 映像并使用它。

    缺点是您的 deps 将位于一个文件中,即 Dockerfile 和工作流中的测试,因此它们并没有放在一起。此外,如果下载图像的时间比构建依赖项的时间多,这是一个糟糕的选择。


    我认为每个人都有优点和缺点。缓存只适用于非常简单的东西,比如编译成 .local。如果你需要更广泛的东西,Docker 是最强大的。

    【讨论】:

      猜你喜欢
      • 2020-12-07
      • 2020-03-26
      • 2020-06-08
      • 2021-05-27
      • 1970-01-01
      • 2023-04-03
      • 2022-01-19
      • 2020-01-16
      • 2021-11-16
      相关资源
      最近更新 更多